Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 26 Jun 2015 14:10:14 +0200
changeset 281092 6672d61aebd404477e011bf886b739fdc5594297
parent 281091 7f56a42267ba40e69c619e5b9dadcdd2c5d857a1 (current diff)
parent 281086 56e207dbb3bda446e30c8a9fcf16050f35a58fb9 (diff)
child 281093 9e2f7a9650d960802f4211bbf357811c6e62033b
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone41.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/themes/linux/identity.png
browser/themes/osx/identity.png
browser/themes/osx/identity@2x.png
browser/themes/windows/identity-XP.png
browser/themes/windows/identity.png
js/src/jit-test/tests/SIMD/with.js
js/src/jit-test/tests/ion/recover-iterator-next.js
js/src/tests/ecma_7/SIMD/with.js
mobile/android/base/resources/drawable-hdpi/favicon_search.png
mobile/android/base/resources/drawable-hdpi/icon_last_tabs_empty.png
mobile/android/base/resources/drawable-hdpi/menu_tabs.png
mobile/android/base/resources/drawable-hdpi/suggestion_item_search.png
mobile/android/base/resources/drawable-large-hdpi-v11/menu_tabs.png
mobile/android/base/resources/drawable-large-mdpi-v11/menu_tabs.png
mobile/android/base/resources/drawable-large-v11/menu.xml
mobile/android/base/resources/drawable-large-xhdpi-v11/menu_tabs.png
mobile/android/base/resources/drawable-mdpi/favicon_search.png
mobile/android/base/resources/drawable-mdpi/icon_last_tabs_empty.png
mobile/android/base/resources/drawable-mdpi/menu_tabs.png
mobile/android/base/resources/drawable-mdpi/suggestion_item_search.png
mobile/android/base/resources/drawable-xhdpi/favicon_search.png
mobile/android/base/resources/drawable-xhdpi/icon_last_tabs_empty.png
mobile/android/base/resources/drawable-xhdpi/menu_tabs.png
mobile/android/base/resources/drawable-xhdpi/suggestion_item_search.png
mobile/android/base/resources/drawable-xxhdpi/favicon_search.png
mobile/android/base/resources/drawable-xxhdpi/suggestion_item_search.png
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/http-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-csp/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/cross-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/fetch-request/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-https/fetch-request/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-when-cross-origin/http-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-csp/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/fetch-request/same-origin-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unsafe-url/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/http-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-csp/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/cross-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/fetch-request/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-https/fetch-request/generic.swap-origin-redirect.http.html.ini
--- a/accessible/base/ARIAMap.cpp
+++ b/accessible/base/ARIAMap.cpp
@@ -93,16 +93,26 @@ static nsRoleMapEntry sWAIRoleMaps[] =
     kUseMapRole,
     eNoValue,
     ePressAction,
     eNoLiveAttr,
     eButton,
     kNoReqStates
     // eARIAPressed is auto applied on any button
   },
+  { // cell
+    &nsGkAtoms::cell,
+    roles::CELL,
+    kUseMapRole,
+    eNoValue,
+    eNoAction,
+    eNoLiveAttr,
+    eTableCell,
+    kNoReqStates
+  },
   { // checkbox
     &nsGkAtoms::checkbox,
     roles::CHECKBUTTON,
     kUseMapRole,
     eNoValue,
     eCheckUncheckAction,
     eNoLiveAttr,
     kGenericAccType,
@@ -626,16 +636,27 @@ static nsRoleMapEntry sWAIRoleMaps[] =
     kUseMapRole,
     eNoValue,
     eSwitchAction,
     eNoLiveAttr,
     kGenericAccType,
     kNoReqStates,
     eARIASelectable
   },
+  { // table
+    &nsGkAtoms::table,
+    roles::TABLE,
+    kUseMapRole,
+    eNoValue,
+    eNoAction,
+    eNoLiveAttr,
+    eTable,
+    kNoReqStates,
+    eARIASelectable
+  },
   { // tablist
     &nsGkAtoms::tablist,
     roles::PAGETABLIST,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
     eSelect,
--- a/accessible/base/Filters.cpp
+++ b/accessible/base/Filters.cpp
@@ -43,18 +43,19 @@ filters::GetRow(Accessible* aAccessible)
 
   return eSkipSubtree;
 }
 
 uint32_t
 filters::GetCell(Accessible* aAccessible)
 {
   a11y::role role = aAccessible->Role();
-  return role == roles::GRID_CELL || role == roles::ROWHEADER ||
-    role == roles::COLUMNHEADER ? eMatch : eSkipSubtree;
+  return role == roles::CELL || role == roles::GRID_CELL ||
+         role == roles::ROWHEADER || role == roles::COLUMNHEADER ?
+    eMatch : eSkipSubtree;
 }
 
 uint32_t
 filters::GetEmbeddedObject(Accessible* aAccessible)
 {
   return nsAccUtils::IsEmbeddedObject(aAccessible) ?
     eMatch | eSkipSubtree : eSkipSubtree;
 }
--- a/accessible/generic/ARIAGridAccessible.cpp
+++ b/accessible/generic/ARIAGridAccessible.cpp
@@ -63,27 +63,30 @@ ARIAGridAccessible::RowCount()
   while (rowIter.Next())
     rowCount++;
 
   return rowCount;
 }
 
 Accessible*
 ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
-{ 
+{
   Accessible* row = GetRowAt(aRowIndex);
   if (!row)
     return nullptr;
 
   return GetCellInRowAt(row, aColumnIndex);
 }
 
 bool
 ARIAGridAccessible::IsColSelected(uint32_t aColIdx)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return false;
+
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = rowIter.Next();
   if (!row)
     return false;
 
   do {
     if (!nsAccUtils::IsARIASelected(row)) {
       Accessible* cell = GetCellInRowAt(row, aColIdx);
@@ -93,16 +96,19 @@ ARIAGridAccessible::IsColSelected(uint32
   } while ((row = rowIter.Next()));
 
   return true;
 }
 
 bool
 ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return false;
+
   Accessible* row = GetRowAt(aRowIdx);
   if(!row)
     return false;
 
   if (!nsAccUtils::IsARIASelected(row)) {
     AccIterator cellIter(row, filters::GetCell);
     Accessible* cell = nullptr;
     while ((cell = cellIter.Next())) {
@@ -112,32 +118,38 @@ ARIAGridAccessible::IsRowSelected(uint32
   }
 
   return true;
 }
 
 bool
 ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return false;
+
   Accessible* row = GetRowAt(aRowIdx);
   if(!row)
     return false;
 
   if (!nsAccUtils::IsARIASelected(row)) {
     Accessible* cell = GetCellInRowAt(row, aColIdx);
     if (!cell || !nsAccUtils::IsARIASelected(cell))
       return false;
   }
 
   return true;
 }
 
 uint32_t
 ARIAGridAccessible::SelectedCellCount()
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return 0;
+
   uint32_t count = 0, colCount = ColCount();
 
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = nullptr;
 
   while ((row = rowIter.Next())) {
     if (nsAccUtils::IsARIASelected(row)) {
       count += colCount;
@@ -154,16 +166,19 @@ ARIAGridAccessible::SelectedCellCount()
   }
 
   return count;
 }
 
 uint32_t
 ARIAGridAccessible::SelectedColCount()
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return 0;
+
   uint32_t colCount = ColCount();
   if (!colCount)
     return 0;
 
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = rowIter.Next();
   if (!row)
     return 0;
@@ -188,16 +203,19 @@ ARIAGridAccessible::SelectedColCount()
   } while ((row = rowIter.Next()));
 
   return selColCount;
 }
 
 uint32_t
 ARIAGridAccessible::SelectedRowCount()
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return 0;
+
   uint32_t count = 0;
 
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = nullptr;
 
   while ((row = rowIter.Next())) {
     if (nsAccUtils::IsARIASelected(row)) {
       count++;
@@ -222,16 +240,19 @@ ARIAGridAccessible::SelectedRowCount()
   }
 
   return count;
 }
 
 void
 ARIAGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   AccIterator rowIter(this, filters::GetRow);
 
   Accessible* row = nullptr;
   while ((row = rowIter.Next())) {
     AccIterator cellIter(row, filters::GetCell);
     Accessible* cell = nullptr;
 
     if (nsAccUtils::IsARIASelected(row)) {
@@ -246,16 +267,19 @@ ARIAGridAccessible::SelectedCells(nsTArr
         aCells->AppendElement(cell);
     }
   }
 }
 
 void
 ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   uint32_t colCount = ColCount();
 
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = nullptr;
   for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
     if (nsAccUtils::IsARIASelected(row)) {
       for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
         aCells->AppendElement(rowIdx * colCount + colIdx);
@@ -270,16 +294,19 @@ ARIAGridAccessible::SelectedCellIndices(
         aCells->AppendElement(rowIdx * colCount + colIdx);
     }
   }
 }
 
 void
 ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   uint32_t colCount = ColCount();
   if (!colCount)
     return;
 
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = rowIter.Next();
   if (!row)
     return;
@@ -304,16 +331,19 @@ ARIAGridAccessible::SelectedColIndices(n
   for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
     if (isColSelArray[colIdx])
       aCols->AppendElement(colIdx);
 }
 
 void
 ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = nullptr;
   for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
     if (nsAccUtils::IsARIASelected(row)) {
       aRows->AppendElement(rowIdx);
       continue;
     }
 
@@ -333,28 +363,34 @@ ARIAGridAccessible::SelectedRowIndices(n
     if (isRowSelected)
       aRows->AppendElement(rowIdx);
   }
 }
 
 void
 ARIAGridAccessible::SelectRow(uint32_t aRowIdx)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   AccIterator rowIter(this, filters::GetRow);
 
   Accessible* row = nullptr;
   for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
     DebugOnly<nsresult> rv = SetARIASelected(row, rowIdx == aRowIdx);
     NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
   }
 }
 
 void
 ARIAGridAccessible::SelectCol(uint32_t aColIdx)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   AccIterator rowIter(this, filters::GetRow);
 
   Accessible* row = nullptr;
   while ((row = rowIter.Next())) {
     // Unselect all cells in the row.
     DebugOnly<nsresult> rv = SetARIASelected(row, false);
     NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
 
@@ -363,25 +399,30 @@ ARIAGridAccessible::SelectCol(uint32_t a
     if (cell)
       SetARIASelected(cell, true);
   }
 }
 
 void
 ARIAGridAccessible::UnselectRow(uint32_t aRowIdx)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   Accessible* row = GetRowAt(aRowIdx);
-
   if (row)
     SetARIASelected(row, false);
 }
 
 void
 ARIAGridAccessible::UnselectCol(uint32_t aColIdx)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return;
+
   AccIterator rowIter(this, filters::GetRow);
 
   Accessible* row = nullptr;
   while ((row = rowIter.Next())) {
     Accessible* cell = GetCellInRowAt(row, aColIdx);
     if (cell)
       SetARIASelected(cell, false);
   }
@@ -416,16 +457,19 @@ ARIAGridAccessible::GetCellInRowAt(Acces
 
   return cell;
 }
 
 nsresult
 ARIAGridAccessible::SetARIASelected(Accessible* aAccessible,
                                     bool aIsSelected, bool aNotify)
 {
+  if (IsARIARole(nsGkAtoms::table))
+    return NS_OK;
+
   nsIContent *content = aAccessible->GetContent();
   NS_ENSURE_STATE(content);
 
   nsresult rv = NS_OK;
   if (aIsSelected)
     rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
                           NS_LITERAL_STRING("true"), aNotify);
   else
@@ -519,18 +563,18 @@ ARIAGridCellAccessible::ColIdx() const
   if (!row)
     return 0;
 
   int32_t indexInRow = IndexInParent();
   uint32_t colIdx = 0;
   for (int32_t idx = 0; idx < indexInRow; idx++) {
     Accessible* cell = row->GetChildAt(idx);
     roles::Role role = cell->Role();
-    if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
-        role == roles::COLUMNHEADER)
+    if (role == roles::CELL || role == roles::GRID_CELL ||
+        role == roles::ROWHEADER || role == roles::COLUMNHEADER)
       colIdx++;
   }
 
   return colIdx;
 }
 
 uint32_t
 ARIAGridCellAccessible::RowIdx() const
@@ -588,18 +632,18 @@ ARIAGridCellAccessible::NativeAttributes
   int32_t colIdx = 0, colCount = 0;
   uint32_t childCount = thisRow->ChildCount();
   for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
     Accessible* child = thisRow->GetChildAt(childIdx);
     if (child == this)
       colIdx = colCount;
 
     roles::Role role = child->Role();
-    if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
-        role == roles::COLUMNHEADER)
+    if (role == roles::CELL || role == roles::GRID_CELL ||
+        role == roles::ROWHEADER || role == roles::COLUMNHEADER)
       colCount++;
   }
 
   int32_t rowIdx = RowIndexFor(thisRow);
 
   nsAutoString stringIdx;
   stringIdx.AppendInt(rowIdx * colCount + colIdx);
   nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -980,16 +980,19 @@ HyperTextAccessible::NativeAttributes()
     GetAccService()->MarkupAttributes(mContent, attributes);
 
   return attributes.forget();
 }
 
 nsIAtom*
 HyperTextAccessible::LandmarkRole() const
 {
+  if (!HasOwnContent())
+    return nullptr;
+
   // For the html landmark elements we expose them like we do ARIA landmarks to
   // make AT navigation schemes "just work".
   if (mContent->IsHTMLElement(nsGkAtoms::nav)) {
     return nsGkAtoms::navigation;
   }
 
   if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header,
                                     nsGkAtoms::footer)) {
@@ -1747,16 +1750,52 @@ HyperTextAccessible::RemoveChild(Accessi
   int32_t childIndex = aAccessible->IndexInParent();
   int32_t count = mOffsets.Length() - childIndex;
   if (count > 0)
     mOffsets.RemoveElementsAt(childIndex, count);
 
   return Accessible::RemoveChild(aAccessible);
 }
 
+Relation
+HyperTextAccessible::RelationByType(RelationType aType)
+{
+  Relation rel = Accessible::RelationByType(aType);
+
+  switch (aType) {
+    case RelationType::NODE_CHILD_OF:
+      if (mContent->IsMathMLElement()) {
+        Accessible* parent = Parent();
+        if (parent) {
+          nsIContent* parentContent = parent->GetContent();
+          if (parentContent->IsMathMLElement(nsGkAtoms::mroot_)) {
+            // Add a relation pointing to the parent <mroot>.
+            rel.AppendTarget(parent);
+          }
+        }
+      }
+      break;
+    case RelationType::NODE_PARENT_OF:
+      if (mContent->IsMathMLElement(nsGkAtoms::mroot_)) {
+        Accessible* base = GetChildAt(0);
+        Accessible* index = GetChildAt(1);
+        if (base && index) {
+          // Append the <mroot> children in the order index, base.
+          rel.AppendTarget(index);
+          rel.AppendTarget(base);
+        }
+      }
+      break;
+    default:
+      break;
+  }
+
+  return rel;
+}
+
 void
 HyperTextAccessible::CacheChildren()
 {
   // Trailing HTML br element don't play any difference. We don't need to expose
   // it to AT (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=899433#c16
   // for details).
 
   TreeWalker walker(this, mContent);
--- a/accessible/generic/HyperTextAccessible.h
+++ b/accessible/generic/HyperTextAccessible.h
@@ -57,16 +57,17 @@ public:
   virtual nsIAtom* LandmarkRole() const override;
   virtual int32_t GetLevelInternal() override;
   virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
   virtual mozilla::a11y::role NativeRole() override;
   virtual uint64_t NativeState() override;
 
   virtual void InvalidateChildren() override;
   virtual bool RemoveChild(Accessible* aAccessible) override;
+  virtual Relation RelationByType(RelationType aType) override;
 
   // HyperTextAccessible (static helper method)
 
   // Convert content offset to rendered text offset
   nsresult ContentToRenderedOffset(nsIFrame *aFrame, int32_t aContentOffset,
                                    uint32_t *aRenderedOffset) const;
 
   // Convert rendered text offset to content offset
--- a/accessible/mac/mozAccessible.h
+++ b/accessible/mac/mozAccessible.h
@@ -119,16 +119,19 @@ static const uintptr_t IS_PROXY = 1;
 // returns NO if for some reason we were unable to focus the element.
 - (BOOL)focus;
 
 // notifications sent out to listening accessible providers.
 - (void)didReceiveFocus;
 - (void)valueDidChange;
 - (void)selectedTextDidChange;
 
+// internal method to retrieve a child at a given index.
+- (id)childAt:(uint32_t)i;
+
 #pragma mark -
 
 // invalidates and removes all our children from our cached array.
 - (void)invalidateChildren;
 
 /**
  * Append a child if they are already cached.
  */
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -22,16 +22,33 @@
 #include "nsCocoaUtils.h"
 #include "nsCoord.h"
 #include "nsObjCExceptions.h"
 #include "nsWhitespaceTokenizer.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
+#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand"
+#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex"
+#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator"
+#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator"
+#define NSAccessibilityMathBaseAttribute @"AXMathBase"
+#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript"
+#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript"
+#define NSAccessibilityMathUnderAttribute @"AXMathUnder"
+#define NSAccessibilityMathOverAttribute @"AXMathOver"
+// XXX WebKit also defines the following attributes.
+// See bugs 1176970, 1176973 and 1176983.
+// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
+// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
+// - NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
+// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
+// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
+
 // returns the passed in object if it is not ignored. if it's ignored, will return
 // the first unignored ancestor.
 static inline id
 GetClosestInterestingAccessible(id anObject)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   // this object is not ignored, so let's return it.
@@ -116,16 +133,62 @@ GetClosestInterestingAccessible(id anObj
 
   AccessibleWrap* accWrap = [self getGeckoAccessible];
   return !accWrap || ([[self role] isEqualToString:NSAccessibilityUnknownRole] &&
                                !(accWrap->InteractiveState() & states::FOCUSABLE));
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
 }
 
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+  NSMutableArray* additional = [NSMutableArray array];
+  switch (mRole) {
+    case roles::MATHML_ROOT:
+      [additional addObject:NSAccessibilityMathRootIndexAttribute];
+      [additional addObject:NSAccessibilityMathRootRadicandAttribute];
+      break;
+    case roles::MATHML_SQUARE_ROOT:
+      [additional addObject:NSAccessibilityMathRootRadicandAttribute];
+      break;
+    case roles::MATHML_FRACTION:
+      [additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
+      [additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
+      // XXX bug 1176973
+      // WebKit also defines NSAccessibilityMathLineThicknessAttribute
+      break;
+    case roles::MATHML_SUB:
+    case roles::MATHML_SUP:
+    case roles::MATHML_SUB_SUP:
+      [additional addObject:NSAccessibilityMathBaseAttribute];
+      [additional addObject:NSAccessibilityMathSubscriptAttribute];
+      [additional addObject:NSAccessibilityMathSuperscriptAttribute];
+      break;
+    case roles::MATHML_UNDER:
+    case roles::MATHML_OVER:
+    case roles::MATHML_UNDER_OVER:
+      [additional addObject:NSAccessibilityMathBaseAttribute];
+      [additional addObject:NSAccessibilityMathUnderAttribute];
+      [additional addObject:NSAccessibilityMathOverAttribute];
+      break;
+    // XXX bug 1176983
+    // roles::MATHML_MULTISCRIPTS should also have the following attributes:
+    // - NSAccessibilityMathPrescriptsAttribute
+    // - NSAccessibilityMathPostscriptsAttribute
+    // XXX bug 1176970
+    // roles::MATHML_FENCED should also have the following attributes:
+    // - NSAccessibilityMathFencedOpenAttribute
+    // - NSAccessibilityMathFencedCloseAttribute
+    default:
+      break;
+  }
+
+  return additional;
+}
+
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   // if we're expired, we don't support any attributes.
   if (![self getGeckoAccessible])
     return [NSArray array];
 
@@ -150,17 +213,37 @@ GetClosestInterestingAccessible(id anObj
                                                            NSAccessibilityTopLevelUIElementAttribute,
                                                            NSAccessibilityDescriptionAttribute,
 #if DEBUG
                                                            @"AXMozDescription",
 #endif
                                                            nil];
   }
 
-  return generalAttributes;
+  NSArray* objectAttributes = generalAttributes;
+  NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames];
+  if ([additionalAttributes count])
+    objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
+
+  return objectAttributes;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)childAt:(uint32_t)i
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+  AccessibleWrap* accWrap = [self getGeckoAccessible];
+  if (accWrap) {
+    Accessible* acc = accWrap->GetChildAt(i);
+    return acc ? GetNativeFromGeckoAccessible(acc) : nil;
+  }
+
+  return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (id)accessibilityAttributeValue:(NSString*)attribute
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
@@ -209,16 +292,103 @@ GetClosestInterestingAccessible(id anObj
     Relation rel =
       [self getGeckoAccessible]->RelationByType(RelationType::LABELLED_BY);
     Accessible* tempAcc = rel.Next();
     return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
   }
   if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
     return [self help];
 
+  switch (mRole) {
+  case roles::MATHML_ROOT:
+    if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
+      return [self childAt:0];
+    if ([attribute isEqualToString:NSAccessibilityMathRootIndexAttribute])
+      return [self childAt:1];
+    break;
+  case roles::MATHML_SQUARE_ROOT:
+    if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
+      return [self childAt:0];
+    break;
+  case roles::MATHML_FRACTION:
+    if ([attribute isEqualToString:NSAccessibilityMathFractionNumeratorAttribute])
+      return [self childAt:0];
+    if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
+      return [self childAt:1];
+    // XXX bug 1176973
+    // WebKit also defines NSAccessibilityMathLineThicknessAttribute
+    break;
+  case roles::MATHML_SUB:
+    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+      return [self childAt:0];
+    if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+      return [self childAt:1];
+#ifdef DEBUG
+    if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+      return nil;
+#endif
+    break;
+  case roles::MATHML_SUP:
+    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+      return [self childAt:0];
+#ifdef DEBUG
+    if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+      return nil;
+#endif
+    if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+      return [self childAt:1];
+    break;
+  case roles::MATHML_SUB_SUP:
+    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+      return [self childAt:0];
+    if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+      return [self childAt:1];
+    if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+      return [self childAt:2];
+    break;
+  case roles::MATHML_UNDER:
+    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+      return [self childAt:0];
+    if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+      return [self childAt:1];
+#ifdef DEBUG
+    if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+      return nil;
+#endif
+    break;
+  case roles::MATHML_OVER:
+    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+      return [self childAt:0];
+#ifdef DEBUG
+    if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+      return nil;
+#endif
+    if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+      return [self childAt:1];
+    break;
+  case roles::MATHML_UNDER_OVER:
+    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+      return [self childAt:0];
+    if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+      return [self childAt:1];
+    if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+      return [self childAt:2];
+    break;
+  // XXX bug 1176983
+  // roles::MATHML_MULTISCRIPTS should also have the following attributes:
+  // - NSAccessibilityMathPrescriptsAttribute
+  // - NSAccessibilityMathPostscriptsAttribute
+  // XXX bug 1176970
+  // roles::MATHML_FENCED should also have the following attributes:
+  // - NSAccessibilityMathFencedOpenAttribute
+  // - NSAccessibilityMathFencedCloseAttribute
+  default:
+    break;
+  }
+
 #ifdef DEBUG
  NSLog (@"!!! %@ can't respond to attribute %@", self, attribute);
 #endif
   return nil;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
@@ -505,17 +675,18 @@ GetClosestInterestingAccessible(id anObj
 
     case roles::MATHML_MATH:
       return @"AXDocumentMath";
 
     case roles::MATHML_FRACTION:
       return @"AXMathFraction";
 
     case roles::MATHML_FENCED:
-      // XXX This should be AXMathFence, but doing so without implementing the
+      // XXX bug 1176970
+      // This should be AXMathFence, but doing so without implementing the
       // whole fence interface seems to make VoiceOver crash, so we present it
       // as a row for now.
       return @"AXMathRow";
 
     case roles::MATHML_SUB:
     case roles::MATHML_SUP:
     case roles::MATHML_SUB_SUP:
       return @"AXMathSubscriptSuperscript";
@@ -552,16 +723,18 @@ GetClosestInterestingAccessible(id anObj
     case roles::MATHML_CELL:
       return @"AXMathTableCell";
 
     // XXX: NSAccessibility also uses subroles AXMathSeparatorOperator and
     // AXMathFenceOperator. We should use the NS_MATHML_OPERATOR_FENCE and
     // NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they
     // are only available from the MathML layout code. Hence we just fallback
     // to subrole AXMathOperator for now.
+    // XXX bug 1175747 WebKit also creates anonymous operators for <mfenced>
+    // which have subroles AXMathSeparatorOperator and AXMathFenceOperator.
     case roles::MATHML_OPERATOR:
       return @"AXMathOperator";
 
     case roles::MATHML_MULTISCRIPTS:
       return @"AXMathMultiscript";
 
     case roles::SWITCH:
       return @"AXSwitch";
@@ -602,20 +775,22 @@ struct RoleDescrComparator
 
 - (NSString*)roleDescription
 {
   if (mRole == roles::DOCUMENT)
     return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
 
   NSString* subrole = [self subrole];
 
-  size_t idx = 0;
-  if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
-                     RoleDescrComparator(subrole), &idx)) {
-    return utils::LocalizedString(sRoleDescrMap[idx].description);
+  if (subrole) {
+    size_t idx = 0;
+    if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
+                       RoleDescrComparator(subrole), &idx)) {
+      return utils::LocalizedString(sRoleDescrMap[idx].description);
+    }
   }
 
   return NSAccessibilityRoleDescription([self role], subrole);
 }
 
 - (NSString*)title
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
--- a/accessible/tests/mochitest/elm/test_MathMLSpec.html
+++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html
@@ -120,16 +120,29 @@
       };
       testElm("msqrt", obj);
 
       //////////////////////////////////////////////////////////////////////////
       // mroot
 
       obj = {
         role: ROLE_MATHML_ROOT,
+        relations: {
+          RELATION_NODE_PARENT_OF: ["mroot_index", "mroot_base"]
+        },
+        children: [
+          {
+            role: ROLE_MATHML_IDENTIFIER,
+            relations: { RELATION_NODE_CHILD_OF: "mroot" }
+          },
+          {
+            role: ROLE_MATHML_NUMBER,
+            relations: { RELATION_NODE_CHILD_OF: "mroot" }
+          }
+        ]
       };
       testElm("mroot", obj);
 
       //////////////////////////////////////////////////////////////////////////
       // mfenced
 
       obj = {
         role: ROLE_MATHML_FENCED,
@@ -381,18 +394,18 @@
     <mi>
       <mglyph id="mglyph" src="../letters.gif" alt="letters"/>
     </mi>
      <mfrac id="mfrac" bevelled="true" linethickness="thick">
       <mi>x</mi>
       <mn>2</mn>
     </mfrac>
     <mroot id="mroot">
-      <mi>x</mi>
-      <mn>5</mn>
+      <mi id="mroot_base">x</mi>
+      <mn id="mroot_index">5</mn>
     </mroot>
     <mspace width="1em"/>
     <mfenced id="mfenced" close="[" open="]" separators=".">
       <mrow>
         <mi>x</mi>
         <mi>y</mi>
       </mrow>
     </mfenced>
--- a/accessible/tests/mochitest/table/a11y.ini
+++ b/accessible/tests/mochitest/table/a11y.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 
 [test_headers_ariagrid.html]
+[test_headers_ariatable.html]
 [test_headers_listbox.xul]
 [test_headers_table.html]
 [test_headers_tree.xul]
 [test_indexes_ariagrid.html]
 [test_indexes_listbox.xul]
 [test_indexes_table.html]
 [test_indexes_tree.xul]
 [test_layoutguess.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_ariatable.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+  <title>Table header information cells for ARIA table</title>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../table.js"></script>
+
+  <script type="application/javascript">
+
+    function doTest()
+    {
+      //////////////////////////////////////////////////////////////////////////
+      // column and row headers from markup
+
+      headerInfoMap = [
+        {
+          cell: "table_dc_1",
+          rowHeaderCells: [ "table_rh_1" ],
+          columnHeaderCells: [ "table_ch_2" ]
+        },
+        {
+          cell: "table_dc_2",
+          rowHeaderCells: [ "table_rh_1" ],
+          columnHeaderCells: [ "table_ch_3" ]
+        },
+        {
+          cell: "table_dc_3",
+          rowHeaderCells: [ "table_rh_2" ],
+          columnHeaderCells: [ "table_ch_2" ]
+        },
+        {
+          cell: "table_dc_4",
+          rowHeaderCells: [ "table_rh_2" ],
+          columnHeaderCells: [ "table_ch_3" ]
+        },
+        {
+          cell: "table_rh_1",
+          rowHeaderCells: [],
+          columnHeaderCells: [ "table_ch_1" ]
+        },
+        {
+          cell: "table_rh_2",
+          rowHeaderCells: [],
+          columnHeaderCells: [ "table_ch_1" ]
+        }
+      ];
+
+      testHeaderCells(headerInfoMap);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+
+<body>
+  <a target="_blank"
+     title="support ARIA table and cell roles"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">Bug 1173364</a>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <div role="table">
+    <div role="row">
+      <span id="table_ch_1" role="columnheader">col_1</span>
+      <span id="table_ch_2" role="columnheader">col_2</span>
+      <span id="table_ch_3" role="columnheader">col_3</span>
+    </div>
+    <div role="row">
+      <span id="table_rh_1" role="rowheader">row_1</span>
+      <span id="table_dc_1" role="cell">cell1</span>
+      <span id="table_dc_2" role="cell">cell2</span>
+    </div>
+    <div role="row">
+      <span id="table_rh_2" role="rowheader">row_2</span>
+      <span id="table_dc_3" role="cell">cell3</span>
+      <span id="table_dc_4" role="cell">cell4</span>
+    </div>
+  </div>
+
+</body>
+</html>
--- a/accessible/tests/mochitest/tree/a11y.ini
+++ b/accessible/tests/mochitest/tree/a11y.ini
@@ -6,16 +6,17 @@ support-files =
 [test_applicationacc.xul]
 skip-if = true # Bug 561508
 [test_aria_globals.html]
 [test_aria_grid.html]
 [test_aria_imgmap.html]
 [test_aria_list.html]
 [test_aria_menu.html]
 [test_aria_presentation.html]
+[test_aria_table.html]
 [test_brokencontext.html]
 [test_button.xul]
 [test_canvas.html]
 [test_combobox.xul]
 [test_cssoverflow.html]
 [test_dochierarchy.html]
 [test_dockids.html]
 [test_filectrl.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_table.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>ARIA table tests</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+
+  <script type="application/javascript">
+    function doTest()
+    {
+      //////////////////////////////////////////////////////////////////////////
+      // table having rowgroups
+
+      var accTree =
+        { TABLE: [
+          { GROUPING: [
+            { ROW: [
+              { CELL: [
+                { TEXT_LEAF: [ ] }
+              ] }
+            ] }
+          ] },
+        ] };
+
+      testAccessibleTree("table", accTree);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="support ARIA table and cell roles"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">
+    Bug 1173364
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <div id="table" role="table">
+    <div role="rowgroup">
+      <div role="row">
+        <div role="cell">cell</div>
+      </div>
+    </div>
+  </div>
+
+</body>
+</html>
--- a/addon-sdk/mach_commands.py
+++ b/addon-sdk/mach_commands.py
@@ -1,17 +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/.
 
 # Integrates the xpcshell test runner with mach.
 
+from __future__ import absolute_import
+
 import os
-import re
-import sys
 
 import mozpack.path as mozpath
 
 from mozbuild.base import (
     MachCommandBase,
     MozbuildObject,
 )
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -335,25 +335,30 @@ pref("browser.urlbar.delay", 50);
 // The special characters below can be typed into the urlbar to either restrict
 // the search to visited history, bookmarked, tagged pages; or force a match on
 // just the title text or url.
 pref("browser.urlbar.restrict.history", "^");
 pref("browser.urlbar.restrict.bookmark", "*");
 pref("browser.urlbar.restrict.tag", "+");
 pref("browser.urlbar.restrict.openpage", "%");
 pref("browser.urlbar.restrict.typed", "~");
+pref("browser.urlbar.restrict.searches", "$");
 pref("browser.urlbar.match.title", "#");
 pref("browser.urlbar.match.url", "@");
 
 // The default behavior for the urlbar can be configured to use any combination
 // of the match filters with each additional filter adding more results (union).
 pref("browser.urlbar.suggest.history",              true);
 pref("browser.urlbar.suggest.bookmark",             true);
 pref("browser.urlbar.suggest.openpage",             true);
+#ifdef NIGHTLY_BUILD
 pref("browser.urlbar.suggest.searches",             true);
+#else
+pref("browser.urlbar.suggest.searches",             false);
+#endif
 
 // Restrictions to current suggestions can also be applied (intersection).
 // Typed suggestion works only if history is set to true.
 pref("browser.urlbar.suggest.history.onlyTyped",    false);
 
 pref("browser.urlbar.formatting.enabled", true);
 pref("browser.urlbar.trimURLs", true);
 
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -472,27 +472,28 @@ var FullScreen = {
     gNavToolbox.removeAttribute("fullscreenShouldAnimate");
     gNavToolbox.style.marginTop = "";
 
     if (!this._isChromeCollapsed) {
       return;
     }
 
     // Track whether mouse is near the toolbox
-    this._isChromeCollapsed = false;
     if (trackMouse && !this.useLionFullScreen) {
       let rect = gBrowser.mPanelContainer.getBoundingClientRect();
       this._mouseTargetRect = {
         top: rect.top + 50,
         bottom: rect.bottom,
         left: rect.left,
         right: rect.right
       };
       MousePosTracker.addListener(this);
     }
+
+    this._isChromeCollapsed = false;
   },
 
   hideNavToolbox: function (aAnimate = false) {
     if (this._isChromeCollapsed || !this._safeToCollapse())
       return;
 
     this._fullScrToggler.hidden = false;
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -285,23 +285,20 @@ toolbar[customizing] > .overflow-button 
 %ifdef XP_WIN
 #main-window[sizemode="maximized"] #titlebar-buttonbox {
   -moz-appearance: -moz-window-button-box-maximized;
 }
 %endif
 
 %endif
 
-#main-window[inDOMFullscreen] #navigator-toolbox,
-#main-window[inDOMFullscreen] #fullscr-toggler,
-#main-window[inDOMFullscreen] #sidebar-box,
-#main-window[inDOMFullscreen] #sidebar-splitter {
-  visibility: collapse;
-}
-
+#main-window[inFullscreen][inDOMFullscreen] #navigator-toolbox,
+#main-window[inFullscreen][inDOMFullscreen] #fullscr-toggler,
+#main-window[inFullscreen][inDOMFullscreen] #sidebar-box,
+#main-window[inFullscreen][inDOMFullscreen] #sidebar-splitter,
 #main-window[inFullscreen]:not([OSXLionFullscreen]) toolbar:not([fullscreentoolbar=true]),
 #main-window[inFullscreen] #global-notificationbox,
 #main-window[inFullscreen] #high-priority-global-notificationbox {
   visibility: collapse;
 }
 
 #navigator-toolbox[fullscreenShouldAnimate] {
   transition: 1.5s margin-top ease-out;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2566,24 +2566,26 @@ let gMenuButtonUpdateBadge = {
     if (this.enabled) {
       try {
         this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime");
       } catch (e) {
         this.badgeWaitTime = 345600; // 4 days
       }
       PanelUI.menuButton.classList.add("badged-button");
       Services.obs.addObserver(this, "update-staged", false);
+      Services.obs.addObserver(this, "update-downloaded", false);
     }
   },
 
   uninit: function () {
     if (this.timer)
       this.timer.cancel();
     if (this.enabled) {
       Services.obs.removeObserver(this, "update-staged");
+      Services.obs.removeObserver(this, "update-downloaded");
       PanelUI.panel.removeEventListener("popupshowing", this, true);
       this.enabled = false;
     }
   },
 
   onMenuPanelCommand: function(event) {
     if (event.originalTarget.getAttribute("update-status") === "succeeded") {
       // restart the app
@@ -2597,82 +2599,65 @@ let gMenuButtonUpdateBadge = {
     } else {
       // open the page for manual update
       let url = Services.urlFormatter.formatURLPref("app.update.url.manual");
       openUILinkIn(url, "tab");
     }
   },
 
   observe: function (subject, topic, status) {
-    const STATE_DOWNLOADING     = "downloading";
-    const STATE_PENDING         = "pending";
-    const STATE_PENDING_SVC     = "pending-service";
-    const STATE_APPLIED         = "applied";
-    const STATE_APPLIED_SVC     = "applied-service";
-    const STATE_FAILED          = "failed";
-
-    let updateButton = document.getElementById("PanelUI-update-status");
-
-    let updateButtonText;
-    let stringId;
-
-    // Update the UI when the background updater is finished.
-    switch (status) {
-      case STATE_APPLIED:
-      case STATE_APPLIED_SVC:
-      case STATE_PENDING:
-      case STATE_PENDING_SVC:
-        if (this.timer) {
-          return;
-        }
-        // Give the user badgeWaitTime seconds to react before prompting.
-        this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-        this.timer.initWithCallback(this, this.badgeWaitTime * 1000,
-                                    this.timer.TYPE_ONE_SHOT);
-        // The timer callback will call uninit() when it completes.
-        break;
-      case STATE_FAILED:
-        // Background update has failed, let's show the UI responsible for
-        // prompting the user to update manually.
-        PanelUI.menuButton.setAttribute("update-status", "failed");
-        PanelUI.menuButton.setAttribute("badge", "!");
-
-        stringId = "appmenu.updateFailed.description";
-        updateButtonText = gNavigatorBundle.getString(stringId);
-
-        updateButton.setAttribute("label", updateButtonText);
-        updateButton.setAttribute("update-status", "failed");
-        updateButton.hidden = false;
-
-        PanelUI.panel.addEventListener("popupshowing", this, true);
-
-        this.uninit();
-        break;
-    }
+    if (status == "failed") {
+      // Background update has failed, let's show the UI responsible for
+      // prompting the user to update manually.
+      this.displayBadge(false);
+      this.uninit();
+      return;
+    }
+
+    // Give the user badgeWaitTime seconds to react before prompting.
+    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    this.timer.initWithCallback(this, this.badgeWaitTime * 1000,
+                                this.timer.TYPE_ONE_SHOT);
+    // The timer callback will call uninit() when it completes.
   },
 
   notify: function () {
     // If the update is successfully applied, or if the updater has fallen back
     // to non-staged updates, add a badge to the hamburger menu to indicate an
     // update will be applied once the browser restarts.
-    PanelUI.menuButton.setAttribute("update-status", "succeeded");
-
-    let brandBundle = document.getElementById("bundle_brand");
-    let brandShortName = brandBundle.getString("brandShortName");
-    stringId = "appmenu.restartNeeded.description";
-    updateButtonText = gNavigatorBundle.getFormattedString(stringId,
-                                                           [brandShortName]);
+    this.displayBadge(true);
+    this.uninit();
+  },
+
+  displayBadge: function (succeeded) {
+    let status = succeeded ? "succeeded" : "failed";
+    PanelUI.menuButton.setAttribute("update-status", status);
+    if (!succeeded) {
+      PanelUI.menuButton.setAttribute("badge", "!");
+    }
+
+    let stringId;
+    let updateButtonText;
+    if (succeeded) {
+      let brandBundle = document.getElementById("bundle_brand");
+      let brandShortName = brandBundle.getString("brandShortName");
+      stringId = "appmenu.restartNeeded.description";
+      updateButtonText = gNavigatorBundle.getFormattedString(stringId,
+                                                             [brandShortName]);
+    } else {
+      stringId = "appmenu.updateFailed.description";
+      updateButtonText = gNavigatorBundle.getString(stringId);
+    }
 
     let updateButton = document.getElementById("PanelUI-update-status");
     updateButton.setAttribute("label", updateButtonText);
-    updateButton.setAttribute("update-status", "succeeded");
+    updateButton.setAttribute("update-status", status);
     updateButton.hidden = false;
 
     PanelUI.panel.addEventListener("popupshowing", this, true);
-    this.uninit();
   },
 
   handleEvent: function(e) {
     if (e.type === "popupshowing") {
       PanelUI.menuButton.removeAttribute("badge");
       PanelUI.panel.removeEventListener("popupshowing", this, true);
     }
   }
@@ -3142,19 +3127,22 @@ function BrowserReloadWithFlags(reloadFl
 }
 
 var PrintPreviewListener = {
   _printPreviewTab: null,
   _tabBeforePrintPreview: null,
 
   getPrintPreviewBrowser: function () {
     if (!this._printPreviewTab) {
+      let browser = gBrowser.selectedTab.linkedBrowser;
+      let forceNotRemote = gMultiProcessBrowser && !browser.isRemoteBrowser;
       this._tabBeforePrintPreview = gBrowser.selectedTab;
       this._printPreviewTab = gBrowser.loadOneTab("about:blank",
-                                                  { inBackground: false });
+                                                  { inBackground: false,
+                                                    forceNotRemote });
       gBrowser.selectedTab = this._printPreviewTab;
     }
     return gBrowser.getBrowserForTab(this._printPreviewTab);
   },
   getSourceBrowser: function () {
     return this._tabBeforePrintPreview ?
       this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
   },
@@ -6600,21 +6588,16 @@ var gIdentityHandler = {
   get _identityPopup () {
     delete this._identityPopup;
     return this._identityPopup = document.getElementById("identity-popup");
   },
   get _identityBox () {
     delete this._identityBox;
     return this._identityBox = document.getElementById("identity-box");
   },
-  get _identityPopupContentBox () {
-    delete this._identityPopupContentBox;
-    return this._identityPopupContentBox =
-      document.getElementById("identity-popup-content-box");
-  },
   get _identityPopupContentHost () {
     delete this._identityPopupContentHost;
     return this._identityPopupContentHost =
       document.getElementById("identity-popup-content-host");
   },
   get _identityPopupContentOwner () {
     delete this._identityPopupContentOwner;
     return this._identityPopupContentOwner =
@@ -6625,16 +6608,26 @@ var gIdentityHandler = {
     return this._identityPopupContentSupp =
       document.getElementById("identity-popup-content-supplemental");
   },
   get _identityPopupContentVerif () {
     delete this._identityPopupContentVerif;
     return this._identityPopupContentVerif =
       document.getElementById("identity-popup-content-verifier");
   },
+  get _identityPopupSecurityContent () {
+    delete this._identityPopupSecurityContent;
+    return this._identityPopupSecurityContent =
+      document.getElementById("identity-popup-security-content");
+  },
+  get _identityPopupSecurityView () {
+    delete this._identityPopupSecurityView;
+    return this._identityPopupSecurityView =
+      document.getElementById("identity-popup-securityView");
+  },
   get _identityIconLabel () {
     delete this._identityIconLabel;
     return this._identityIconLabel = document.getElementById("identity-icon-label");
   },
   get _overrideService () {
     delete this._overrideService;
     return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
                                      .getService(Ci.nsICertOverrideService);
@@ -6680,16 +6673,21 @@ var gIdentityHandler = {
    * "identity-popup" panel.
    */
   handleMoreInfoClick : function(event) {
     displaySecurityInfo();
     event.stopPropagation();
     this._identityPopup.hidePopup();
   },
 
+  showSubView(name, anchor) {
+    let view = document.getElementById("identity-popup-multiView");
+    view.showSubView(`identity-popup-${name}View`, anchor);
+  },
+
   /**
    * Helper to parse out the important parts of _lastStatus (of the SSL cert in
    * particular) for use in constructing identity UI strings
   */
   getIdentityData : function() {
     var result = {};
     var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
     var cert = status.serverCert;
@@ -6854,18 +6852,20 @@ var gIdentityHandler = {
       return;
     }
 
     this._identityPopup.className = newMode;
     this._identityBox.className = newMode;
     this.setIdentityMessages(newMode);
 
     // Update the popup too, if it's open
-    if (this._identityPopup.state == "open")
+    if (this._identityPopup.state == "open") {
       this.setPopupMessages(newMode);
+      this.updateSitePermissions();
+    }
 
     this._mode = newMode;
   },
 
   /**
    * Set up the messages for the primary identity UI based on the specified mode,
    * and the details of the SSL cert, where applicable
    *
@@ -6940,34 +6940,30 @@ var gIdentityHandler = {
    * based on the specified mode, and the details of the SSL cert, where
    * applicable
    *
    * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
    */
   setPopupMessages : function(newMode) {
 
     this._identityPopup.className = newMode;
-    this._identityPopupContentBox.className = newMode;
+    this._identityPopupSecurityView.className = newMode;
+    this._identityPopupSecurityContent.className = newMode;
 
     // Initialize the optional strings to empty values
     let supplemental = "";
     let verifier = "";
     let host = "";
     let owner = "";
 
-    if (newMode == this.IDENTITY_MODE_CHROMEUI) {
-      let brandBundle = document.getElementById("bundle_brand");
-      host = brandBundle.getString("brandFullName");
-    } else {
-      try {
-        host = this.getEffectiveHost();
-      } catch (e) {
-        // Some URIs might have no hosts.
-        host = this._lastUri.specIgnoringRef;
-      }
+    try {
+      host = this.getEffectiveHost();
+    } catch (e) {
+      // Some URIs might have no hosts.
+      host = this._lastUri.specIgnoringRef;
     }
 
     switch (newMode) {
     case this.IDENTITY_MODE_DOMAIN_VERIFIED:
       verifier = this._identityBox.tooltipText;
       break;
     case this.IDENTITY_MODE_IDENTIFIED: {
       // If it's identified, then we can populate the dialog with credentials
@@ -6994,20 +6990,23 @@ var gIdentityHandler = {
     case this.IDENTITY_MODE_MIXED_ACTIVE_LOADED:
       supplemental = gNavigatorBundle.getString("identity.mixed_active_loaded2");
       break;
     }
 
     // Push the appropriate strings out to the UI. Need to use |value| for the
     // host as it's a <label> that will be cropped if too long. Using
     // |textContent| would simply wrap the value.
-    this._identityPopupContentHost.value = host;
+    this._identityPopupContentHost.setAttribute("value", host);
     this._identityPopupContentOwner.textContent = owner;
     this._identityPopupContentSupp.textContent = supplemental;
     this._identityPopupContentVerif.textContent = verifier;
+
+    // Hide subviews when updating panel information.
+    document.getElementById("identity-popup-multiView").showMainView();
   },
 
   /**
    * Click handler for the identity-box element in primary chrome.
    */
   handleIdentityButtonEvent : function(event) {
     event.stopPropagation();
 
@@ -7122,16 +7121,17 @@ var gIdentityHandler = {
     menulist.appendChild(menupopup);
     menulist.setAttribute("value", aState);
     menulist.setAttribute("oncommand", "gIdentityHandler.setPermission('" +
                                        aPermission + "', this.value)");
     menulist.setAttribute("id", "identity-popup-permission:" + aPermission);
 
     let label = document.createElement("label");
     label.setAttribute("flex", "1");
+    label.setAttribute("class", "identity-popup-permission-label");
     label.setAttribute("control", menulist.getAttribute("id"));
     label.setAttribute("value", SitePermissions.getPermissionLabel(aPermission));
 
     let container = document.createElement("hbox");
     container.setAttribute("align", "center");
     container.appendChild(label);
     container.appendChild(menulist);
     return container;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -126,16 +126,17 @@ skip-if = e10s # Bug 1093153 - no about:
 [browser_action_keyword_override.js]
 [browser_action_searchengine.js]
 [browser_action_searchengine_alias.js]
 [browser_addKeywordSearch.js]
 [browser_search_favicon.js]
 [browser_alltabslistener.js]
 [browser_autocomplete_a11y_label.js]
 skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir (works on its own)
+[browser_autocomplete_cursor.js]
 [browser_autocomplete_enter_race.js]
 [browser_autocomplete_no_title.js]
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_oldschool_wrap.js]
 [browser_autocomplete_tag_star_visibility.js]
 [browser_backButtonFitts.js]
 skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
 [browser_beforeunload_duplicate_dialogs.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_autocomplete_cursor.js
@@ -0,0 +1,25 @@
+add_task(function*() {
+  // This test is only relevant if UnifiedComplete is enabled.
+  let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
+  Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true);
+  registerCleanupFunction(() => {
+    Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref);
+  });
+
+  let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
+  yield promiseTabLoaded(tab);
+  yield promiseAutocompleteResultPopup("www.mozilla.org");
+
+  gURLBar.selectTextRange(4, 4);
+
+  is(gURLBar.popup.state, "open", "Popup should be open");
+  is(gURLBar.popup.richlistbox.selectedIndex, 0, "Should have selected something");
+
+  EventUtils.synthesizeKey("VK_RIGHT", {});
+  yield promisePopupHidden(gURLBar.popup);
+
+  is(gURLBar.selectionStart, 5, "Should have moved the cursor");
+  is(gURLBar.selectionEnd, 5, "And not selected anything");
+
+  gBrowser.removeTab(tab);
+});
--- a/browser/base/content/test/general/browser_sanitizeDialog.js
+++ b/browser/base/content/test/general/browser_sanitizeDialog.js
@@ -397,27 +397,32 @@ var gAllTests = [
                  "formdata pref should be true after accepting dialog with " +
                  "formdata checkbox checked");
 
 
       // Even though the formdata pref is true, because there is no history
       // left to clear, the checkbox will be disabled.
       var cb = this.win.document.querySelectorAll(
                  "#itemList > [preference='privacy.cpd.formdata']");
-      ok(cb.length == 1 && cb[0].disabled && !cb[0].checked,
-         "There is no formdata history, checkbox should be disabled and be " +
-         "cleared to reduce user confusion (bug 497664).");
+
+      // Wait until the checkbox is disabled. This is done asynchronously
+      // from Sanitizer.init() as FormHistory.count() is a purely async API.
+      promiseWaitForCondition(() => cb[0].disabled).then(() => {
+        ok(cb.length == 1 && cb[0].disabled && !cb[0].checked,
+           "There is no formdata history, checkbox should be disabled and be " +
+           "cleared to reduce user confusion (bug 497664).");
 
-      var cb = this.win.document.querySelectorAll(
-                 "#itemList > [preference='privacy.cpd.history']");
-      ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
-         "There is no history, but history checkbox should always be enabled " +
-         "and will be checked from previous preference.");
+        cb = this.win.document.querySelectorAll(
+                   "#itemList > [preference='privacy.cpd.history']");
+        ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
+           "There is no history, but history checkbox should always be enabled " +
+           "and will be checked from previous preference.");
 
-      this.acceptDialog();
+        this.acceptDialog();
+      });
     }
     wh.open();
   },
 
   /**
    * Add form history entry for the next test.
    */
   function () {
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -179,16 +179,32 @@ file, You can obtain one at http://mozil
             this.setAttribute("actiontype", action.type);
           } else {
             this.removeAttribute("actiontype");
           }
           return returnValue;
         ]]></body>
       </method>
 
+      <method name="onKeyPress">
+        <parameter name="aEvent"/>
+        <body><![CDATA[
+          switch (aEvent.keyCode) {
+            case KeyEvent.DOM_VK_LEFT:
+            case KeyEvent.DOM_VK_RIGHT:
+            case KeyEvent.DOM_VK_HOME:
+              this.popup.hidePopup();
+              return;
+              break;
+          }
+
+          return this.handleKeyPress(aEvent);
+        ]]></body>
+      </method>
+
       <field name="_mayTrimURLs">true</field>
       <method name="trimValue">
         <parameter name="aURL"/>
         <body><![CDATA[
           // This method must not modify the given URL such that calling
           // nsIURIFixup::createFixupURI with the result will produce a different URI.
           return this._mayTrimURLs ? trimURL(aURL) : aURL;
         ]]></body>
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -5,40 +5,79 @@
 <panel id="identity-popup"
        type="arrow"
        hidden="true"
        noautofocus="true"
        onpopupshown="if (event.target == this)
                        gIdentityHandler.onPopupShown(event);"
        orient="vertical"
        level="top">
-  <hbox id="identity-popup-container" align="top">
-    <image id="identity-popup-icon"/>
-    <vbox id="identity-popup-content-box">
-      <label id="identity-popup-content-host"
-             class="identity-popup-description"
-             crop="end"/>
-      <label id="identity-popup-connection-secure"
-             class="identity-popup-label"
-             value="&identity.connectionSecure;"/>
-      <label id="identity-popup-connection-not-secure"
-             class="identity-popup-label"
-             value="&identity.connectionNotSecure;"/>
+
+  <broadcasterset>
+    <broadcaster id="identity-popup-content-host" value=""/>
+  </broadcasterset>
+
+  <panelmultiview id="identity-popup-multiView"
+                  mainViewId="identity-popup-mainView">
+    <panelview id="identity-popup-mainView" flex="1">
+
+      <!-- Security Section -->
+      <hbox class="identity-popup-section">
+        <vbox id="identity-popup-security-content" flex="1">
+          <label class="identity-popup-headline" crop="end">
+            <observes element="identity-popup-content-host" attribute="value"/>
+          </label>
+          <label class="identity-popup-connection-secure identity-popup-text"
+                 value="&identity.connectionSecure;"/>
+          <label class="identity-popup-connection-not-secure identity-popup-text"
+                 value="&identity.connectionNotSecure;"/>
+          <label class="identity-popup-connection-internal identity-popup-text"
+                 value="&identity.connectionInternal;"/>
+        </vbox>
+        <button class="identity-popup-expander"
+                oncommand="gIdentityHandler.showSubView('security', this)"/>
+      </hbox>
+
+      <!-- Permissions Section -->
+      <hbox id="identity-popup-permissions" class="identity-popup-section">
+        <vbox id="identity-popup-permissions-content" flex="1">
+          <label class="identity-popup-text identity-popup-headline"
+                 value="&identity.permissions;"/>
+          <vbox id="identity-popup-permission-list"/>
+        </vbox>
+      </hbox>
+
+      <spacer flex="1"/>
+
+      <!-- More Information Button -->
+      <hbox id="identity-popup-button-container" align="center">
+        <button id="identity-popup-more-info-button" flex="1"
+                label="&identity.moreInfoLinkText2;"
+                oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
+      </hbox>
+    </panelview>
+
+    <!-- Security SubView -->
+    <panelview id="identity-popup-securityView" flex="1">
+      <vbox id="identity-popup-securityView-header">
+        <label class="identity-popup-headline" crop="end">
+          <observes element="identity-popup-content-host" attribute="value"/>
+        </label>
+        <label class="identity-popup-connection-secure identity-popup-text"
+               value="&identity.connectionSecure;"/>
+        <label class="identity-popup-connection-not-secure identity-popup-text"
+               value="&identity.connectionNotSecure;"/>
+        <label class="identity-popup-connection-internal identity-popup-text"
+               value="&identity.connectionInternal;"/>
+      </vbox>
+
+      <description id="identity-popup-securityView-connection"
+                   class="identity-popup-text">&identity.connectionVerified;</description>
+
       <description id="identity-popup-content-owner"
-                   class="identity-popup-description"/>
+                   class="identity-popup-text"/>
       <description id="identity-popup-content-supplemental"
-                   class="identity-popup-description"/>
+                   class="identity-popup-text"/>
       <description id="identity-popup-content-verifier"
-                   class="identity-popup-description"/>
-      <vbox id="identity-popup-permissions">
-        <label class="identity-popup-label header"
-               value="&identity.permissions;"/>
-        <vbox id="identity-popup-permission-list" class="indent"/>
-      </vbox>
-    </vbox>
-  </hbox>
-  <!-- Footer button to open security page info -->
-  <hbox id="identity-popup-button-container" align="center">
-    <button id="identity-popup-more-info-button" flex="1"
-            label="&identity.moreInfoLinkText2;"
-            oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
-  </hbox>
+                   class="identity-popup-text"/>
+    </panelview>
+  </panelmultiview>
 </panel>
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -158,23 +158,17 @@
             evt.initCustomEvent("ViewHiding", true, true, viewNode);
             viewNode.dispatchEvent(evt);
 
             viewNode.removeAttribute("current");
             this._currentSubView = null;
 
             this._subViewObserver.disconnect();
 
-            this._transitioning = true;
-
-            this._viewContainer.addEventListener("transitionend", function trans() {
-              this._viewContainer.removeEventListener("transitionend", trans);
-              this._transitioning = false;
-            }.bind(this));
-            this._viewContainer.style.height = this._mainViewHeight + "px";
+            this._setViewContainerHeight(this._mainViewHeight);
 
             this.setAttribute("viewtype", "main");
           }
 
           this._shiftMainView();
         ]]></body>
       </method>
 
@@ -206,54 +200,73 @@
           //
           // All three of these actions make use of CSS transformations, so they
           // should all occur simultaneously.
           this.setAttribute("viewtype", "subview");
           this._shiftMainView(aAnchor);
 
           this._mainViewHeight = this._viewStack.clientHeight;
 
-          this._transitioning = true;
-          this._viewContainer.addEventListener("transitionend", function trans() {
-            this._viewContainer.removeEventListener("transitionend", trans);
-            this._transitioning = false;
-          }.bind(this));
-
           let newHeight = this._heightOfSubview(viewNode, this._subViews);
-          this._viewContainer.style.height = newHeight + "px";
+          this._setViewContainerHeight(newHeight);
 
           this._subViewObserver.observe(viewNode, {
             attributes: true,
             characterData: true,
             childList: true,
             subtree: true
           });
         ]]></body>
       </method>
 
+      <method name="_setViewContainerHeight">
+        <parameter name="aHeight"/>
+        <body><![CDATA[
+          let container = this._viewContainer;
+          this._transitioning = true;
+
+          let onTransitionEnd = () => {
+            container.removeEventListener("transitionend", onTransitionEnd);
+            this._transitioning = false;
+          };
+
+          container.addEventListener("transitionend", onTransitionEnd);
+          container.style.height = `${aHeight}px`;
+        ]]></body>
+      </method>
+
       <method name="_shiftMainView">
         <parameter name="aAnchor"/>
         <body><![CDATA[
           if (aAnchor) {
             // We need to find the edge of the anchor, relative to the main panel.
             // Then we need to add half the width of the anchor. This is the target
             // that we need to transition to.
             let anchorRect = aAnchor.getBoundingClientRect();
             let mainViewRect = this._mainViewContainer.getBoundingClientRect();
             let center = aAnchor.clientWidth / 2;
             let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
-            let edge, target;
+            let edge;
             if (direction == "ltr") {
               edge = anchorRect.left - mainViewRect.left;
-              target = "-" + (edge + center);
             } else {
               edge = mainViewRect.right - anchorRect.right;
-              target = edge + center;
             }
-            this._mainViewContainer.style.transform = "translateX(" + target + "px)";
+
+            // If the anchor is an element on the far end of the mainView we
+            // don't want to shift the mainView too far, we would reveal empty
+            // space otherwise.
+            let cstyle = window.getComputedStyle(document.documentElement, null);
+            let exitSubViewGutterWidth =
+              cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
+            let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
+            let target = Math.min(maxShift, edge + center);
+
+            let neg = direction == "ltr" ? "-" : "";
+            this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
             aAnchor.setAttribute("panel-multiview-anchor", true);
           } else {
             this._mainViewContainer.style.transform = "";
             if (this.anchorElement)
               this.anchorElement.removeAttribute("panel-multiview-anchor");
           }
           this.anchorElement = aAnchor;
         ]]></body>
--- a/browser/components/loop/.eslintrc
+++ b/browser/components/loop/.eslintrc
@@ -57,27 +57,25 @@
     "no-unused-vars": 0,          // TODO: Remove (use default)
     "no-use-before-define": 0,    // TODO: Remove (use default)
     "quotes": [2, "double", "avoid-escape"],
     "strict": 0,                  // [2, "function"],
     // eslint-plugin-react rules. These are documented at
     // <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>
     "react/jsx-quotes": [2, "double", "avoid-escape"],
     "react/jsx-no-undef": 2,
-    // Need to fix instances where this is failing.
     "react/jsx-sort-props": 2,
     "react/jsx-sort-prop-types": 2,
     "react/jsx-uses-vars": 2,
     // Need to fix the couple of instances which don't
     // currently pass this rule.
     "react/no-did-mount-set-state": 0,
     "react/no-did-update-set-state": 2,
     "react/no-unknown-property": 2,
-    // Need to fix instances where this is currently failing
-    "react/prop-types": 0,
+    "react/prop-types": 2,
     "react/self-closing-comp": 2,
     "react/wrap-multilines": 2,
     // Not worth it: React is defined globally
     "react/jsx-uses-react": 0,
     "react/react-in-jsx-scope": 0,
     // These ones we don't want to ever enable
     "react/display-name": 0,
     "react/jsx-boolean-value": 0,
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -141,16 +141,18 @@ loop.contacts = (function(_, mozL10n) {
           )
         )
       );
     }
   });
 
   const ContactDropdown = React.createClass({displayName: "ContactDropdown",
     propTypes: {
+      // If the contact is blocked or not.
+      blocked: React.PropTypes.bool.isRequired,
       canEdit: React.PropTypes.bool,
       handleAction: React.PropTypes.func.isRequired
     },
 
     getInitialState: function () {
       return {
         openDirUp: false
       };
@@ -329,17 +331,19 @@ loop.contacts = (function(_, mozL10n) {
   const ContactsList = React.createClass({displayName: "ContactsList",
     mixins: [
       React.addons.LinkedStateMixin,
       loop.shared.mixins.WindowCloseMixin
     ],
 
     propTypes: {
       notifications: React.PropTypes.instanceOf(
-        loop.shared.models.NotificationCollection).isRequired
+        loop.shared.models.NotificationCollection).isRequired,
+        // Callback to handle entry to the add/edit contact form.
+        startForm: React.PropTypes.func.isRequired
     },
 
     /**
      * Contacts collection object
      */
     contacts: null,
 
     /**
@@ -619,17 +623,19 @@ loop.contacts = (function(_, mozL10n) {
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({displayName: "ContactDetailsForm",
     mixins: [React.addons.LinkedStateMixin],
 
     propTypes: {
-      mode: React.PropTypes.string
+      mode: React.PropTypes.string,
+      // Callback used to change the selected tab - it is passed the tab name.
+      selectTab: React.PropTypes.func.isRequired
     },
 
     getInitialState: function() {
       return {
         contact: null,
         pristine: true,
         name: "",
         email: "",
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -141,16 +141,18 @@ loop.contacts = (function(_, mozL10n) {
           </ButtonGroup>
         </div>
       );
     }
   });
 
   const ContactDropdown = React.createClass({
     propTypes: {
+      // If the contact is blocked or not.
+      blocked: React.PropTypes.bool.isRequired,
       canEdit: React.PropTypes.bool,
       handleAction: React.PropTypes.func.isRequired
     },
 
     getInitialState: function () {
       return {
         openDirUp: false
       };
@@ -329,17 +331,19 @@ loop.contacts = (function(_, mozL10n) {
   const ContactsList = React.createClass({
     mixins: [
       React.addons.LinkedStateMixin,
       loop.shared.mixins.WindowCloseMixin
     ],
 
     propTypes: {
       notifications: React.PropTypes.instanceOf(
-        loop.shared.models.NotificationCollection).isRequired
+        loop.shared.models.NotificationCollection).isRequired,
+        // Callback to handle entry to the add/edit contact form.
+        startForm: React.PropTypes.func.isRequired
     },
 
     /**
      * Contacts collection object
      */
     contacts: null,
 
     /**
@@ -619,17 +623,19 @@ loop.contacts = (function(_, mozL10n) {
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({
     mixins: [React.addons.LinkedStateMixin],
 
     propTypes: {
-      mode: React.PropTypes.string
+      mode: React.PropTypes.string,
+      // Callback used to change the selected tab - it is passed the tab name.
+      selectTab: React.PropTypes.func.isRequired
     },
 
     getInitialState: function() {
       return {
         contact: null,
         pristine: true,
         name: "",
         email: "",
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -110,16 +110,20 @@ loop.conversationViews = (function(mozL1
    * Displays details of the incoming/outgoing conversation
    * (name, link, audio/video type etc).
    *
    * Allows the view to be extended with different buttons and progress
    * via children properties.
    */
   var ConversationDetailView = React.createClass({displayName: "ConversationDetailView",
     propTypes: {
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.element,
+        React.PropTypes.arrayOf(React.PropTypes.element)
+      ]).isRequired,
       contact: React.PropTypes.object
     },
 
     render: function() {
       var contactName = _getContactDisplayName(this.props.contact);
 
       return (
         React.createElement("div", {className: "call-window"}, 
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -110,16 +110,20 @@ loop.conversationViews = (function(mozL1
    * Displays details of the incoming/outgoing conversation
    * (name, link, audio/video type etc).
    *
    * Allows the view to be extended with different buttons and progress
    * via children properties.
    */
   var ConversationDetailView = React.createClass({
     propTypes: {
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.element,
+        React.PropTypes.arrayOf(React.PropTypes.element)
+      ]).isRequired,
       contact: React.PropTypes.object
     },
 
     render: function() {
       var contactName = _getContactDisplayName(this.props.contact);
 
       return (
         <div className="call-window">
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -15,16 +15,17 @@ loop.panel = (function(_, mozL10n) {
   var ButtonGroup = sharedViews.ButtonGroup;
   var Checkbox = sharedViews.Checkbox;
   var ContactsList = loop.contacts.ContactsList;
   var ContactDetailsForm = loop.contacts.ContactDetailsForm;
 
   var TabView = React.createClass({displayName: "TabView",
     propTypes: {
       buttonsHidden: React.PropTypes.array,
+      children: React.PropTypes.arrayOf(React.PropTypes.element),
       mozLoop: React.PropTypes.object,
       // The selectedTab prop is used by the UI showcase.
       selectedTab: React.PropTypes.string
     },
 
     getDefaultProps: function() {
       return {
         buttonsHidden: []
@@ -463,16 +464,20 @@ loop.panel = (function(_, mozL10n) {
       );
     }
   });
 
   /**
    * FxA user identity (guest/authenticated) component.
    */
   var UserIdentity = React.createClass({displayName: "UserIdentity",
+    propTypes: {
+      displayName: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       return (
         React.createElement("p", {className: "user-identity"}, 
           this.props.displayName
         )
       );
     }
   });
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -15,16 +15,17 @@ loop.panel = (function(_, mozL10n) {
   var ButtonGroup = sharedViews.ButtonGroup;
   var Checkbox = sharedViews.Checkbox;
   var ContactsList = loop.contacts.ContactsList;
   var ContactDetailsForm = loop.contacts.ContactDetailsForm;
 
   var TabView = React.createClass({
     propTypes: {
       buttonsHidden: React.PropTypes.array,
+      children: React.PropTypes.arrayOf(React.PropTypes.element),
       mozLoop: React.PropTypes.object,
       // The selectedTab prop is used by the UI showcase.
       selectedTab: React.PropTypes.string
     },
 
     getDefaultProps: function() {
       return {
         buttonsHidden: []
@@ -463,16 +464,20 @@ loop.panel = (function(_, mozL10n) {
       );
     }
   });
 
   /**
    * FxA user identity (guest/authenticated) component.
    */
   var UserIdentity = React.createClass({
+    propTypes: {
+      displayName: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       return (
         <p className="user-identity">
           {this.props.displayName}
         </p>
       );
     }
   });
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -273,16 +273,18 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   var DesktopRoomContextView = React.createClass({displayName: "DesktopRoomContextView",
     mixins: [React.addons.LinkedStateMixin],
 
     propTypes: {
+      // Only used for tests.
+      availableContext: React.PropTypes.object,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       editMode: React.PropTypes.bool,
       error: React.PropTypes.object,
       mozLoop: React.PropTypes.object.isRequired,
       onEditModeChange: React.PropTypes.func,
       // This data is supplied by the activeRoomStore.
       roomData: React.PropTypes.object.isRequired,
       savingContext: React.PropTypes.bool.isRequired,
@@ -797,17 +799,17 @@ loop.roomViews = (function(mozL10n) {
               ), 
               React.createElement(DesktopRoomContextView, {
                 dispatcher: this.props.dispatcher, 
                 error: this.state.error, 
                 mozLoop: this.props.mozLoop, 
                 roomData: roomData, 
                 savingContext: this.state.savingContext, 
                 show: !shouldRenderInvitationOverlay && shouldRenderContextView}), 
-              React.createElement(sharedViews.TextChatView, {
+              React.createElement(sharedViews.chat.TextChatView, {
                 dispatcher: this.props.dispatcher, 
                 showAlways: false, 
                 showRoomName: false})
             )
           );
         }
       }
     }
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -273,16 +273,18 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   var DesktopRoomContextView = React.createClass({
     mixins: [React.addons.LinkedStateMixin],
 
     propTypes: {
+      // Only used for tests.
+      availableContext: React.PropTypes.object,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       editMode: React.PropTypes.bool,
       error: React.PropTypes.object,
       mozLoop: React.PropTypes.object.isRequired,
       onEditModeChange: React.PropTypes.func,
       // This data is supplied by the activeRoomStore.
       roomData: React.PropTypes.object.isRequired,
       savingContext: React.PropTypes.bool.isRequired,
@@ -797,17 +799,17 @@ loop.roomViews = (function(mozL10n) {
               </div>
               <DesktopRoomContextView
                 dispatcher={this.props.dispatcher}
                 error={this.state.error}
                 mozLoop={this.props.mozLoop}
                 roomData={roomData}
                 savingContext={this.state.savingContext}
                 show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
-              <sharedViews.TextChatView
+              <sharedViews.chat.TextChatView
                 dispatcher={this.props.dispatcher}
                 showAlways={false}
                 showRoomName={false} />
             </div>
           );
         }
       }
     }
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -1471,72 +1471,226 @@ html[dir="rtl"] .standalone .room-conver
   display: flex;
   flex-flow: column nowrap;
 }
 
 .fx-embedded .text-chat-entries {
   flex: 1 1 auto;
   max-height: 120px;
   min-height: 60px;
-  padding: .7em .5em 0;
 }
 
 .text-chat-box {
   flex: 0 0 auto;
   max-height: 40px;
   min-height: 40px;
   width: 100%;
 }
 
 .text-chat-entries {
   overflow: auto;
 }
 
 .text-chat-entry {
+  display: flex;
+  flex-direction: row;
+  margin-bottom: .5em;
   text-align: end;
-  margin-bottom: 1.5em;
+  flex-wrap: nowrap;
+  justify-content: flex-start;
+  align-content: stretch;
+  align-items: flex-start;
 }
 
 .text-chat-entry > p {
-  border-width: 1px;
-  border-style: solid;
-  border-color: #0095dd;
-  border-radius: 10000px;
-  padding: .5em 1em;
+  position: relative;
+  z-index: 10;
   /* Drop the default margins from the 'p' element. */
   margin: 0;
-  /* inline-block stops the elements taking 100% of the text-chat-view width */
-  display: inline-block;
-  /* Split really long strings with no spaces appropriately, whilst limiting the
-     width to 100%. */
-  max-width: 100%;
+  padding: .7em;
+  /* leave some room for the chat bubble arrow */
+  max-width: 80%;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #2ea4ff;
+  background: #fff;
+  word-wrap: break-word;
   word-wrap: break-word;
+  flex: 0 1 auto;
+  align-self: auto;
+}
+
+.text-chat-entry.sent > p,
+.text-chat-entry.received > p {
+  background: #fff;
+}
+
+.text-chat-entry.sent > p {
+  border-radius: 15px;
+  border-bottom-right-radius: 0;
+}
+
+.text-chat-entry.received > p {
+  border-radius: 15px;
+  border-top-left-radius: 0;
+}
+
+html[dir="rtl"] .text-chat-entry.sent > p {
+  border-radius: 15px;
+  border-bottom-left-radius: 0;
+}
+
+html[dir="rtl"] .text-chat-entry.received > p {
+  border-radius: 15px;
+  border-top-right-radius: 0;
+}
+
+.text-chat-entry.received > p {
+  order: 1;
+}
+
+.text-chat-entry.sent > p {
+  order: 1;
 }
 
 .text-chat-entry.received {
   text-align: start;
 }
 
 .text-chat-entry.received > p {
   border-color: #d8d8d8;
 }
 
-.text-chat-entry.special > p {
-  border: none;
+/* Text chat entry timestamp */
+.text-chat-entry-timestamp {
+  margin: 0 .2em;
+  color: #aaa;
+  font-style: italic;
+  font-size: .8em;
+  order: 0;
+  flex: 0 1 auto;
+  align-self: center;
+}
+
+/* Sent text chat entries should be on the right */
+.text-chat-entry.sent {
+  justify-content: flex-end;
+}
+
+.received > .text-chat-entry-timestamp {
+  order: 2;
+}
+
+.sent > .text-chat-entry-timestamp {
+  order: 0;
+}
+
+/* Pseudo element used to cover part between chat bubble and chat arrow. */
+.text-chat-entry > p:after {
+  position: absolute;
+  background: #fff;
+  content: "";
+}
+
+.text-chat-entry.sent > p:after {
+  right: -2px;
+  bottom: 0;
+  width: 15px;
+  height: 9px;
+  border-top-left-radius: 15px;
+  border-top-right-radius: 22px;
+}
+
+.text-chat-entry.received > p:after {
+  top: 0;
+  left: -2px;
+  width: 15px;
+  height: 9px;
+  border-bottom-left-radius: 22px;
+  border-bottom-right-radius: 15px;
+}
+
+html[dir="rtl"] .text-chat-entry.sent > p:after {
+  /* Reset */
+  right: auto;
+  left: -1px;
+  bottom: 0;
+  width: 15px;
+  height: 9px;
+}
+
+html[dir="rtl"] .text-chat-entry.received > p:after {
+  /* Reset */
+  left: auto;
+  top: 0;
+  right: -1px;
+  width: 15px;
+  height: 6px;
+}
+
+/* Text chat entry arrow */
+.text-chat-arrow {
+  width: 18px;
+  background-repeat: no-repeat;
+  flex: 0 1 auto;
+  position: relative;
+  z-index: 5;
+}
+
+.text-chat-entry.sent .text-chat-arrow {
+  margin-bottom: -1px;
+  margin-left: -11px;
+  height: 10px;
+  background-image: url("../img/chatbubble-arrow-right.svg");
+  order: 2;
+  align-self: flex-end;
+}
+
+.text-chat-entry.received .text-chat-arrow {
+  margin-left: 0;
+  margin-right: -9px;
+  height: 10px;
+  background-image: url("../img/chatbubble-arrow-left.svg");
+  order: 0;
+  align-self: auto;
+}
+
+html[dir="rtl"] .text-chat-arrow {
+  transform: scaleX(-1);
+}
+
+html[dir="rtl"] .text-chat-entry.sent .text-chat-arrow {
+  /* Reset margin. */
+  margin-left: 0;
+  margin-right: -11px;
+}
+
+html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
+  /* Reset margin. */
+  margin-right: 0;
+  margin-left: -10px;
 }
 
 .text-chat-entry.special.room-name {
   color: black;
   font-weight: bold;
   text-align: start;
   background-color: #E8F6FE;
   padding-bottom: 0;
   margin-bottom: 0;
 }
 
+.text-chat-entry.special.room-name p {
+  background: #E8F6FE;
+}
+
+.text-chat-entry.special > p {
+  border: none;
+}
+
 .text-chat-box {
   margin: auto;
 }
 
 .text-chat-box > form > input {
   width: 100%;
   height: 40px;
   padding: 0 .5em .5em;
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/chatbubble-arrow-left.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<svg width="20" height="8" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+ <title>chatbubble-arrow</title>
+ <desc>Created with Sketch.</desc>
+ <g>
+  <title>Layer 1</title>
+  <g transform="rotate(180 6.2844319343566895,3.8364052772521973) " id="svg_1" fill="none">
+   <path id="svg_2" fill="#d8d8d8" d="m12.061934,7.656905l-9.299002,0l0,-1l6.088001,0c-2.110002,-0.967001 -4.742001,-2.818 -6.088001,-6.278l0.932,-0.363c2.201999,5.664001 8.377999,6.637 8.439999,6.646c0.259001,0.039 0.444,0.27 0.426001,0.531c-0.019001,0.262 -0.237,0.464 -0.498999,0.464l-12.072001,-0.352001"/>
+  </g>
+  <line id="svg_13" y2="0.529488" x2="13.851821" y1="0.529488" x1="17.916953" stroke="#d8d8d8" fill="none"/>
+  <line id="svg_26" y2="0.529488" x2="9.79687" y1="0.529488" x1="13.862002" stroke="#d8d8d8" fill="none"/>
+  <line id="svg_27" y2="0.529488" x2="15.908413" y1="0.529488" x1="19.973545" stroke="#d8d8d8" fill="none"/>
+ </g>
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/chatbubble-arrow-right.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<svg width="20" height="9" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+ <title>chatbubble-arrow</title>
+ <desc>Created with Sketch.</desc>
+ <g>
+  <title>Layer 1</title>
+  <g id="svg_1" fill="none">
+   <path id="svg_2" fill="#2EA4FF" d="m19.505243,8.972466l-9.299002,0l0,-1l6.088001,0c-2.110002,-0.967 -4.742001,-2.818 -6.088001,-6.278l0.932,-0.363c2.202,5.664 8.377999,6.637 8.44,6.646c0.259001,0.039 0.444,0.27 0.426001,0.531c-0.019001,0.262 -0.237,0.464 -0.498999,0.464l-12.072001,-0.352"/>
+  </g>
+  <line id="svg_13" y2="8.474788" x2="6.200791" y1="8.474788" x1="10.265923" stroke="#22a4ff" fill="none"/>
+  <line id="svg_26" y2="8.474788" x2="2.14584" y1="8.474788" x1="6.210972" stroke="#22a4ff" fill="none"/>
+  <line id="svg_27" y2="8.474788" x2="0.000501" y1="8.474788" x1="4.065633" stroke="#22a4ff" fill="none"/>
+ </g>
+</svg>
\ No newline at end of file
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -172,25 +172,28 @@ loop.shared.actions = (function() {
       available: Boolean
     }),
 
     /**
      * Used to send a message to the other peer.
      */
     SendTextChatMessage: Action.define("sendTextChatMessage", {
       contentType: String,
-      message: String
+      message: String,
+      sentTimestamp: String
     }),
 
     /**
      * Notifies that a message has been received from the other peer.
      */
     ReceivedTextChatMessage: Action.define("receivedTextChatMessage", {
       contentType: String,
-      message: String
+      message: String,
+      receivedTimestamp: String
+      // sentTimestamp: String (optional)
     }),
 
     /**
      * Used by the ongoing views to notify stores about the elements
      * required for the sdk.
      */
     SetupStreamElements: Action.define("setupStreamElements", {
       // The configuration for the publisher/subscribe options
--- a/browser/components/loop/content/shared/js/feedbackViews.js
+++ b/browser/components/loop/content/shared/js/feedbackViews.js
@@ -18,17 +18,17 @@ loop.shared.views.FeedbackView = (functi
   /**
    * Feedback outer layout.
    *
    * Props:
    * -
    */
   var FeedbackLayout = React.createClass({displayName: "FeedbackLayout",
     propTypes: {
-      children: React.PropTypes.component.isRequired,
+      children: React.PropTypes.element,
       reset: React.PropTypes.func, // if not specified, no Back btn is shown
       title: React.PropTypes.string.isRequired
     },
 
     render: function() {
       var backButton = React.createElement("div", null);
       if (this.props.reset) {
         backButton = (
--- a/browser/components/loop/content/shared/js/feedbackViews.jsx
+++ b/browser/components/loop/content/shared/js/feedbackViews.jsx
@@ -18,17 +18,17 @@ loop.shared.views.FeedbackView = (functi
   /**
    * Feedback outer layout.
    *
    * Props:
    * -
    */
   var FeedbackLayout = React.createClass({
     propTypes: {
-      children: React.PropTypes.component.isRequired,
+      children: React.PropTypes.element,
       reset: React.PropTypes.func, // if not specified, no Back btn is shown
       title: React.PropTypes.string.isRequired
     },
 
     render: function() {
       var backButton = <div />;
       if (this.props.reset) {
         backButton = (
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -600,17 +600,19 @@ loop.shared.mixins = (function() {
     _isLoopDesktop: function() {
       return rootObject.navigator &&
              typeof rootObject.navigator.mozLoop === "object";
     },
 
     /**
      * Starts playing an audio file, stopping any audio that is already in progress.
      *
-     * @param {String} name The filename to play (excluding the extension).
+     * @param {String} name    The filename to play (excluding the extension).
+     * @param {Object} options A list of options for the sound:
+     *                         - {Boolean} loop Whether or not to loop the sound.
      */
     play: function(name, options) {
       if (this._isLoopDesktop() && rootObject.navigator.mozLoop.doNotDisturb) {
         return;
       }
 
       options = options || {};
       options.loop = options.loop || false;
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -693,18 +693,22 @@ loop.OTSdkDriver = (function() {
         if (err) {
           console.error(err);
           return;
         }
 
         channel.on({
           message: function(ev) {
             try {
+              var message = JSON.parse(ev.data);
+              /* Append the timestamp. This is the time that gets shown. */
+              message.receivedTimestamp = (new Date()).toISOString();
+
               this.dispatcher.dispatch(
-                new sharedActions.ReceivedTextChatMessage(JSON.parse(ev.data)));
+                new sharedActions.ReceivedTextChatMessage(message));
             } catch (ex) {
               console.error("Failed to process incoming chat message", ex);
             }
           }.bind(this),
 
           close: function(e) {
             // XXX We probably want to dispatch and handle this somehow.
             console.log("Subscribed data channel closed!");
--- a/browser/components/loop/content/shared/js/textChatStore.js
+++ b/browser/components/loop/content/shared/js/textChatStore.js
@@ -91,17 +91,19 @@ loop.store.TextChatStore = (function() {
      */
     _appendTextChatMessage: function(type, messageData) {
       // We create a new list to avoid updating the store's state directly,
       // which confuses the views.
       var message = {
         type: type,
         contentType: messageData.contentType,
         message: messageData.message,
-        extraData: messageData.extraData
+        extraData: messageData.extraData,
+        sentTimestamp: messageData.sentTimestamp,
+        receivedTimestamp: messageData.receivedTimestamp
       };
       var newList = this._storeState.messageList.concat(message);
       this.setStoreState({ messageList: newList });
 
       // Notify MozLoopService if appropriate that a message has been appended
       // and it should therefore check if we need a different sized window or not.
       if (type != CHAT_MESSAGE_TYPES.SPECIAL) {
         window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
--- a/browser/components/loop/content/shared/js/textChatView.js
+++ b/browser/components/loop/content/shared/js/textChatView.js
@@ -1,44 +1,69 @@
 /* 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/. */
 
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = loop.shared.views || {};
-loop.shared.views.TextChatView = (function(mozL10n) {
+loop.shared.views.chat = (function(mozL10n) {
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var sharedViews = loop.shared.views;
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
   var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
 
   /**
    * Renders an individual entry for the text chat entries view.
    */
   var TextChatEntry = React.createClass({displayName: "TextChatEntry",
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
       contentType: React.PropTypes.string.isRequired,
       message: React.PropTypes.string.isRequired,
+      showTimestamp: React.PropTypes.bool.isRequired,
+      timestamp: React.PropTypes.string.isRequired,
       type: React.PropTypes.string.isRequired
     },
 
+    /**
+     * Pretty print timestamp. From time in milliseconds to HH:MM
+     * (or L10N equivalent).
+     *
+     */
+    _renderTimestamp: function() {
+      var date = new Date(this.props.timestamp);
+      var language = mozL10n.language ? mozL10n.language.code
+                                      : mozL10n.getLanguage();
+
+      return (
+        React.createElement("span", {className: "text-chat-entry-timestamp"}, 
+          date.toLocaleTimeString(language,
+                                   {hour: "numeric", minute: "numeric",
+                                   hour12: false})
+        )
+      );
+    },
+
     render: function() {
       var classes = React.addons.classSet({
         "text-chat-entry": true,
         "received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
+        "sent": this.props.type === CHAT_MESSAGE_TYPES.SENT,
         "special": this.props.type === CHAT_MESSAGE_TYPES.SPECIAL,
         "room-name": this.props.contentType === CHAT_CONTENT_TYPES.ROOM_NAME
       });
 
       return (
         React.createElement("div", {className: classes}, 
-          React.createElement("p", null, this.props.message)
+          React.createElement("p", null, this.props.message), 
+          React.createElement("span", {className: "text-chat-arrow"}), 
+          this.props.showTimestamp ? this._renderTimestamp() : null
         )
       );
     }
   });
 
   var TextChatRoomName = React.createClass({displayName: "TextChatRoomName",
     mixins: [React.addons.PureRenderMixin],
 
@@ -56,47 +81,75 @@ loop.shared.views.TextChatView = (functi
   });
 
   /**
    * Manages the text entries in the chat entries view. This is split out from
    * TextChatView so that scrolling can be managed more efficiently - this
    * component only updates when the message list is changed.
    */
   var TextChatEntriesView = React.createClass({displayName: "TextChatEntriesView",
-    mixins: [React.addons.PureRenderMixin],
+    mixins: [
+      React.addons.PureRenderMixin,
+      sharedMixins.AudioMixin
+    ],
+
+    statics: {
+      ONE_MINUTE: 60
+    },
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       messageList: React.PropTypes.array.isRequired
     },
 
+    getInitialState: function() {
+      return {
+        receivedMessageCount: 0
+      };
+    },
+
     componentWillUpdate: function() {
       var node = this.getDOMNode();
       if (!node) {
         return;
       }
       // Scroll only if we're right at the bottom of the display.
       this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
     },
 
+    componentWillReceiveProps: function(nextProps) {
+      var receivedMessageCount = nextProps.messageList.filter(function(message) {
+        return message.type === CHAT_MESSAGE_TYPES.RECEIVED;
+      }).length;
+
+      // If the number of received messages has increased, we play a sound.
+      if (receivedMessageCount > this.state.receivedMessageCount) {
+        this.play("message");
+        this.setState({receivedMessageCount: receivedMessageCount});
+      }
+    },
+
     componentDidUpdate: function() {
       if (this.shouldScroll) {
         // This ensures the paint is complete.
         window.requestAnimationFrame(function() {
           try {
             var node = this.getDOMNode();
             node.scrollTop = node.scrollHeight - node.clientHeight;
           } catch (ex) {
             console.error("TextChatEntriesView.componentDidUpdate exception", ex);
           }
         }.bind(this));
       }
     },
 
     render: function() {
+      /* Keep track of the last printed timestamp. */
+      var lastTimestamp = 0;
+
       if (!this.props.messageList.length) {
         return null;
       }
 
       return (
         React.createElement("div", {className: "text-chat-entries"}, 
           React.createElement("div", {className: "text-chat-scroller"}, 
             
@@ -114,33 +167,90 @@ loop.shared.views.TextChatView = (functi
                             dispatcher: this.props.dispatcher, 
                             showContextTitle: true, 
                             thumbnail: entry.extraData.thumbnail, 
                             url: entry.extraData.location, 
                             useDesktopPaths: false})
                         )
                       );
                     default:
-                      console.error("Unsupported contentType", entry.contentType);
+                      console.error("Unsupported contentType",
+                                    entry.contentType);
                       return null;
                   }
                 }
 
+                /* For SENT messages there is no received timestamp. */
+                var timestamp = entry.receivedTimestamp || entry.sentTimestamp;
+
+                var timeDiff = this._isOneMinDelta(timestamp, lastTimestamp);
+                var shouldShowTimestamp = this._shouldShowTimestamp(i,
+                                                                    timeDiff);
+
+                if (shouldShowTimestamp) {
+                  lastTimestamp = timestamp;
+                }
+
                 return (
-                  React.createElement(TextChatEntry, {
-                    contentType: entry.contentType, 
-                    key: i, 
-                    message: entry.message, 
-                    type: entry.type})
-                );
+                  React.createElement(TextChatEntry, {contentType: entry.contentType, 
+                                 key: i, 
+                                 message: entry.message, 
+                                 showTimestamp: shouldShowTimestamp, 
+                                 timestamp: timestamp, 
+                                 type: entry.type})
+                  );
               }, this)
             
           )
         )
       );
+    },
+
+    /**
+     * Decide to show timestamp or not on a message.
+     * If the time difference between two consecutive messages is bigger than
+     * one minute or if message types are different.
+     *
+     * @param {number} idx       Index of message in the messageList.
+     * @param {boolean} timeDiff If difference between consecutive messages is
+     *                           bigger than one minute.
+     */
+    _shouldShowTimestamp: function(idx, timeDiff) {
+      if (!idx) {
+        return true;
+      }
+
+      /* If consecutive messages are from different senders */
+      if (this.props.messageList[idx].type !==
+          this.props.messageList[idx - 1].type) {
+        return true;
+      }
+
+      return timeDiff;
+    },
+
+    /**
+     * Determines if difference between the two timestamp arguments
+     * is bigger that 60 (1 minute)
+     *
+     * Timestamps are using ISO8601 format.
+     *
+     * @param {string} currTime Timestamp of message yet to be rendered.
+     * @param {string} prevTime Last timestamp printed in the chat view.
+     */
+    _isOneMinDelta: function(currTime, prevTime) {
+      var date1 = new Date(currTime);
+      var date2 = new Date(prevTime);
+      var delta = date1 - date2;
+
+      if (delta / 1000 >= this.constructor.ONE_MINUTE) {
+        return true;
+      }
+
+      return false;
     }
   });
 
   /**
    * Displays a text chat entry input box for sending messages.
    *
    * @property {loop.Dispatcher} dispatcher
    * @property {Boolean} showPlaceholder    Set to true to show the placeholder message.
@@ -187,17 +297,18 @@ loop.shared.views.TextChatView = (functi
 
       // Don't send empty messages.
       if (!this.state.messageDetail) {
         return;
       }
 
       this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
-        message: this.state.messageDetail
+        message: this.state.messageDetail,
+        sentTimestamp: (new Date()).toISOString()
       }));
 
       // Reset the form to empty, ready for the next message.
       this.setState({ messageDetail: "" });
     },
 
     render: function() {
       if (!this.props.textChatEnabled) {
@@ -280,10 +391,14 @@ loop.shared.views.TextChatView = (functi
             dispatcher: this.props.dispatcher, 
             showPlaceholder: !hasNonSpecialMessages, 
             textChatEnabled: this.state.textChatEnabled})
         )
       );
     }
   });
 
-  return TextChatView;
+  return {
+    TextChatEntriesView: TextChatEntriesView,
+    TextChatEntry: TextChatEntry,
+    TextChatView: TextChatView
+  };
 })(navigator.mozL10n || document.mozL10n);
--- a/browser/components/loop/content/shared/js/textChatView.jsx
+++ b/browser/components/loop/content/shared/js/textChatView.jsx
@@ -1,44 +1,69 @@
 /* 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/. */
 
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = loop.shared.views || {};
-loop.shared.views.TextChatView = (function(mozL10n) {
+loop.shared.views.chat = (function(mozL10n) {
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var sharedViews = loop.shared.views;
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
   var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
 
   /**
    * Renders an individual entry for the text chat entries view.
    */
   var TextChatEntry = React.createClass({
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
       contentType: React.PropTypes.string.isRequired,
       message: React.PropTypes.string.isRequired,
+      showTimestamp: React.PropTypes.bool.isRequired,
+      timestamp: React.PropTypes.string.isRequired,
       type: React.PropTypes.string.isRequired
     },
 
+    /**
+     * Pretty print timestamp. From time in milliseconds to HH:MM
+     * (or L10N equivalent).
+     *
+     */
+    _renderTimestamp: function() {
+      var date = new Date(this.props.timestamp);
+      var language = mozL10n.language ? mozL10n.language.code
+                                      : mozL10n.getLanguage();
+
+      return (
+        <span className="text-chat-entry-timestamp">
+          {date.toLocaleTimeString(language,
+                                   {hour: "numeric", minute: "numeric",
+                                   hour12: false})}
+        </span>
+      );
+    },
+
     render: function() {
       var classes = React.addons.classSet({
         "text-chat-entry": true,
         "received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
+        "sent": this.props.type === CHAT_MESSAGE_TYPES.SENT,
         "special": this.props.type === CHAT_MESSAGE_TYPES.SPECIAL,
         "room-name": this.props.contentType === CHAT_CONTENT_TYPES.ROOM_NAME
       });
 
       return (
         <div className={classes}>
           <p>{this.props.message}</p>
+          <span className="text-chat-arrow" />
+          {this.props.showTimestamp ? this._renderTimestamp() : null}
         </div>
       );
     }
   });
 
   var TextChatRoomName = React.createClass({
     mixins: [React.addons.PureRenderMixin],
 
@@ -56,47 +81,75 @@ loop.shared.views.TextChatView = (functi
   });
 
   /**
    * Manages the text entries in the chat entries view. This is split out from
    * TextChatView so that scrolling can be managed more efficiently - this
    * component only updates when the message list is changed.
    */
   var TextChatEntriesView = React.createClass({
-    mixins: [React.addons.PureRenderMixin],
+    mixins: [
+      React.addons.PureRenderMixin,
+      sharedMixins.AudioMixin
+    ],
+
+    statics: {
+      ONE_MINUTE: 60
+    },
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       messageList: React.PropTypes.array.isRequired
     },
 
+    getInitialState: function() {
+      return {
+        receivedMessageCount: 0
+      };
+    },
+
     componentWillUpdate: function() {
       var node = this.getDOMNode();
       if (!node) {
         return;
       }
       // Scroll only if we're right at the bottom of the display.
       this.shouldScroll = node.scrollHeight === node.scrollTop + node.clientHeight;
     },
 
+    componentWillReceiveProps: function(nextProps) {
+      var receivedMessageCount = nextProps.messageList.filter(function(message) {
+        return message.type === CHAT_MESSAGE_TYPES.RECEIVED;
+      }).length;
+
+      // If the number of received messages has increased, we play a sound.
+      if (receivedMessageCount > this.state.receivedMessageCount) {
+        this.play("message");
+        this.setState({receivedMessageCount: receivedMessageCount});
+      }
+    },
+
     componentDidUpdate: function() {
       if (this.shouldScroll) {
         // This ensures the paint is complete.
         window.requestAnimationFrame(function() {
           try {
             var node = this.getDOMNode();
             node.scrollTop = node.scrollHeight - node.clientHeight;
           } catch (ex) {
             console.error("TextChatEntriesView.componentDidUpdate exception", ex);
           }
         }.bind(this));
       }
     },
 
     render: function() {
+      /* Keep track of the last printed timestamp. */
+      var lastTimestamp = 0;
+
       if (!this.props.messageList.length) {
         return null;
       }
 
       return (
         <div className="text-chat-entries">
           <div className="text-chat-scroller">
             {
@@ -114,33 +167,90 @@ loop.shared.views.TextChatView = (functi
                             dispatcher={this.props.dispatcher}
                             showContextTitle={true}
                             thumbnail={entry.extraData.thumbnail}
                             url={entry.extraData.location}
                             useDesktopPaths={false} />
                         </div>
                       );
                     default:
-                      console.error("Unsupported contentType", entry.contentType);
+                      console.error("Unsupported contentType",
+                                    entry.contentType);
                       return null;
                   }
                 }
 
+                /* For SENT messages there is no received timestamp. */
+                var timestamp = entry.receivedTimestamp || entry.sentTimestamp;
+
+                var timeDiff = this._isOneMinDelta(timestamp, lastTimestamp);
+                var shouldShowTimestamp = this._shouldShowTimestamp(i,
+                                                                    timeDiff);
+
+                if (shouldShowTimestamp) {
+                  lastTimestamp = timestamp;
+                }
+
                 return (
-                  <TextChatEntry
-                    contentType={entry.contentType}
-                    key={i}
-                    message={entry.message}
-                    type={entry.type} />
-                );
+                  <TextChatEntry contentType={entry.contentType}
+                                 key={i}
+                                 message={entry.message}
+                                 showTimestamp={shouldShowTimestamp}
+                                 timestamp={timestamp}
+                                 type={entry.type} />
+                  );
               }, this)
             }
           </div>
         </div>
       );
+    },
+
+    /**
+     * Decide to show timestamp or not on a message.
+     * If the time difference between two consecutive messages is bigger than
+     * one minute or if message types are different.
+     *
+     * @param {number} idx       Index of message in the messageList.
+     * @param {boolean} timeDiff If difference between consecutive messages is
+     *                           bigger than one minute.
+     */
+    _shouldShowTimestamp: function(idx, timeDiff) {
+      if (!idx) {
+        return true;
+      }
+
+      /* If consecutive messages are from different senders */
+      if (this.props.messageList[idx].type !==
+          this.props.messageList[idx - 1].type) {
+        return true;
+      }
+
+      return timeDiff;
+    },
+
+    /**
+     * Determines if difference between the two timestamp arguments
+     * is bigger that 60 (1 minute)
+     *
+     * Timestamps are using ISO8601 format.
+     *
+     * @param {string} currTime Timestamp of message yet to be rendered.
+     * @param {string} prevTime Last timestamp printed in the chat view.
+     */
+    _isOneMinDelta: function(currTime, prevTime) {
+      var date1 = new Date(currTime);
+      var date2 = new Date(prevTime);
+      var delta = date1 - date2;
+
+      if (delta / 1000 >= this.constructor.ONE_MINUTE) {
+        return true;
+      }
+
+      return false;
     }
   });
 
   /**
    * Displays a text chat entry input box for sending messages.
    *
    * @property {loop.Dispatcher} dispatcher
    * @property {Boolean} showPlaceholder    Set to true to show the placeholder message.
@@ -187,17 +297,18 @@ loop.shared.views.TextChatView = (functi
 
       // Don't send empty messages.
       if (!this.state.messageDetail) {
         return;
       }
 
       this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
-        message: this.state.messageDetail
+        message: this.state.messageDetail,
+        sentTimestamp: (new Date()).toISOString()
       }));
 
       // Reset the form to empty, ready for the next message.
       this.setState({ messageDetail: "" });
     },
 
     render: function() {
       if (!this.props.textChatEnabled) {
@@ -280,10 +391,14 @@ loop.shared.views.TextChatView = (functi
             dispatcher={this.props.dispatcher}
             showPlaceholder={!hasNonSpecialMessages}
             textChatEnabled={this.state.textChatEnabled} />
         </div>
       );
     }
   });
 
-  return TextChatView;
+  return {
+    TextChatEntriesView: TextChatEntriesView,
+    TextChatEntry: TextChatEntry,
+    TextChatView: TextChatView
+  };
 })(navigator.mozL10n || document.mozL10n);
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -257,16 +257,17 @@ loop.shared.views = (function(_, l10n) {
       sharedMixins.AudioMixin,
       sharedMixins.MediaSetupMixin
     ],
 
     propTypes: {
       audio: React.PropTypes.object,
       initiate: React.PropTypes.bool,
       isDesktop: React.PropTypes.bool,
+      model: React.PropTypes.object.isRequired,
       sdk: React.PropTypes.object.isRequired,
       video: React.PropTypes.object
     },
 
     getDefaultProps: function() {
       return {
         initiate: true,
         isDesktop: false,
@@ -552,16 +553,17 @@ loop.shared.views = (function(_, l10n) {
       );
     }
   });
 
   var Button = React.createClass({displayName: "Button",
     propTypes: {
       additionalClass: React.PropTypes.string,
       caption: React.PropTypes.string.isRequired,
+      children: React.PropTypes.element,
       disabled: React.PropTypes.bool,
       htmlId: React.PropTypes.string,
       onClick: React.PropTypes.func.isRequired
     },
 
     getDefaultProps: function() {
       return {
         disabled: false,
@@ -584,18 +586,22 @@ loop.shared.views = (function(_, l10n) {
           React.createElement("span", {className: "button-caption"}, this.props.caption), 
           this.props.children
         )
       );
     }
   });
 
   var ButtonGroup = React.createClass({displayName: "ButtonGroup",
-    PropTypes: {
-      additionalClass: React.PropTypes.string
+    propTypes: {
+      additionalClass: React.PropTypes.string,
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.element,
+        React.PropTypes.arrayOf(React.PropTypes.element)
+      ])
     },
 
     getDefaultProps: function() {
       return {
         additionalClass: ""
       };
     },
 
@@ -609,17 +615,17 @@ loop.shared.views = (function(_, l10n) {
         React.createElement("div", {className: cx(classObject)}, 
           this.props.children
         )
       );
     }
   });
 
   var Checkbox = React.createClass({displayName: "Checkbox",
-    PropTypes: {
+    propTypes: {
       additionalClass: React.PropTypes.string,
       checked: React.PropTypes.bool,
       disabled: React.PropTypes.bool,
       label: React.PropTypes.string,
       onChange: React.PropTypes.func.isRequired,
       // If `value` is not supplied, the consumer should rely on the boolean
       // `checked` state changes.
       value: React.PropTypes.string
@@ -729,17 +735,17 @@ loop.shared.views = (function(_, l10n) {
    * @property {String}  url                The url to be displayed. If not present or invalid,
    *                                        then this view won't be displayed.
    * @property {Boolean} useDesktopPaths    Whether or not to use the desktop paths for for the
    *                                        fallback url.
    */
   var ContextUrlView = React.createClass({displayName: "ContextUrlView",
     mixins: [React.addons.PureRenderMixin],
 
-    PropTypes: {
+    propTypes: {
       allowClick: React.PropTypes.bool.isRequired,
       description: React.PropTypes.string.isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
       showContextTitle: React.PropTypes.bool.isRequired,
       thumbnail: React.PropTypes.string,
       url: React.PropTypes.string,
       useDesktopPaths: React.PropTypes.bool.isRequired
     },
@@ -808,22 +814,22 @@ loop.shared.views = (function(_, l10n) {
    * Renders a media element for display. This also handles displaying an avatar
    * instead of the video, and attaching a video stream to the video element.
    */
   var MediaView = React.createClass({displayName: "MediaView",
     // srcVideoObject should be ok for a shallow comparison, so we are safe
     // to use the pure render mixin here.
     mixins: [React.addons.PureRenderMixin],
 
-    PropTypes: {
+    propTypes: {
       displayAvatar: React.PropTypes.bool.isRequired,
       isLoading: React.PropTypes.bool.isRequired,
+      mediaType: React.PropTypes.string.isRequired,
       posterUrl: React.PropTypes.string,
       // Expecting "local" or "remote".
-      mediaType: React.PropTypes.string.isRequired,
       srcVideoObject: React.PropTypes.object
     },
 
     componentDidMount: function() {
       if (!this.props.displayAvatar) {
         this.attachVideo(this.props.srcVideoObject);
       }
     },
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -257,16 +257,17 @@ loop.shared.views = (function(_, l10n) {
       sharedMixins.AudioMixin,
       sharedMixins.MediaSetupMixin
     ],
 
     propTypes: {
       audio: React.PropTypes.object,
       initiate: React.PropTypes.bool,
       isDesktop: React.PropTypes.bool,
+      model: React.PropTypes.object.isRequired,
       sdk: React.PropTypes.object.isRequired,
       video: React.PropTypes.object
     },
 
     getDefaultProps: function() {
       return {
         initiate: true,
         isDesktop: false,
@@ -552,16 +553,17 @@ loop.shared.views = (function(_, l10n) {
       );
     }
   });
 
   var Button = React.createClass({
     propTypes: {
       additionalClass: React.PropTypes.string,
       caption: React.PropTypes.string.isRequired,
+      children: React.PropTypes.element,
       disabled: React.PropTypes.bool,
       htmlId: React.PropTypes.string,
       onClick: React.PropTypes.func.isRequired
     },
 
     getDefaultProps: function() {
       return {
         disabled: false,
@@ -584,18 +586,22 @@ loop.shared.views = (function(_, l10n) {
           <span className="button-caption">{this.props.caption}</span>
           {this.props.children}
         </button>
       );
     }
   });
 
   var ButtonGroup = React.createClass({
-    PropTypes: {
-      additionalClass: React.PropTypes.string
+    propTypes: {
+      additionalClass: React.PropTypes.string,
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.element,
+        React.PropTypes.arrayOf(React.PropTypes.element)
+      ])
     },
 
     getDefaultProps: function() {
       return {
         additionalClass: ""
       };
     },
 
@@ -609,17 +615,17 @@ loop.shared.views = (function(_, l10n) {
         <div className={cx(classObject)}>
           {this.props.children}
         </div>
       );
     }
   });
 
   var Checkbox = React.createClass({
-    PropTypes: {
+    propTypes: {
       additionalClass: React.PropTypes.string,
       checked: React.PropTypes.bool,
       disabled: React.PropTypes.bool,
       label: React.PropTypes.string,
       onChange: React.PropTypes.func.isRequired,
       // If `value` is not supplied, the consumer should rely on the boolean
       // `checked` state changes.
       value: React.PropTypes.string
@@ -729,17 +735,17 @@ loop.shared.views = (function(_, l10n) {
    * @property {String}  url                The url to be displayed. If not present or invalid,
    *                                        then this view won't be displayed.
    * @property {Boolean} useDesktopPaths    Whether or not to use the desktop paths for for the
    *                                        fallback url.
    */
   var ContextUrlView = React.createClass({
     mixins: [React.addons.PureRenderMixin],
 
-    PropTypes: {
+    propTypes: {
       allowClick: React.PropTypes.bool.isRequired,
       description: React.PropTypes.string.isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
       showContextTitle: React.PropTypes.bool.isRequired,
       thumbnail: React.PropTypes.string,
       url: React.PropTypes.string,
       useDesktopPaths: React.PropTypes.bool.isRequired
     },
@@ -808,22 +814,22 @@ loop.shared.views = (function(_, l10n) {
    * Renders a media element for display. This also handles displaying an avatar
    * instead of the video, and attaching a video stream to the video element.
    */
   var MediaView = React.createClass({
     // srcVideoObject should be ok for a shallow comparison, so we are safe
     // to use the pure render mixin here.
     mixins: [React.addons.PureRenderMixin],
 
-    PropTypes: {
+    propTypes: {
       displayAvatar: React.PropTypes.bool.isRequired,
       isLoading: React.PropTypes.bool.isRequired,
+      mediaType: React.PropTypes.string.isRequired,
       posterUrl: React.PropTypes.string,
       // Expecting "local" or "remote".
-      mediaType: React.PropTypes.string.isRequired,
       srcVideoObject: React.PropTypes.object
     },
 
     componentDidMount: function() {
       if (!this.props.displayAvatar) {
         this.attachVideo(this.props.srcVideoObject);
       }
     },
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..920b458254b5b346671441461120b0ecb7973c7b
GIT binary patch
literal 14063
zc%1EecTiNpns3hxISx1=Nd(CVNY3Dpv%ruul7o^_5WyiS0xBX|a?T<_K_wVKa*!xU
zkf<Ptl0{+94Box>zS^qYs`uW1+tsJ1Pv<Z6*T1jN=)1Za0XX;@-nqwE;C}VAJq}^4
zuxs9a4(|TA0=R1BNegTP4FB5*)5rA$|JoB*vqyjpqCp@3dmu)fj6?}F(7k-kMM6?Y
zL_$bJSQOXeEc7^gIeI%e`zb(`j<`xOs8X2iio3%}KN#W3FPE~4DGZ<i;PhwUS1<CX
zk3upj8!#@gDEE7QV^r3qiC_`cwd&#WPhc<1RgSUZ4IfM}0k{r#l!8!Eu}UQDQCte<
ziIF@CP%cIWDs#t`A(j7;sL8lNwMrysq85auDI%Ao_gSDab0k#ug)NF&xiKTMQU+?r
zDWPc)NhI@yCF+d;Z*0Un;YRu>0tw#uh(+Pfkw|jon2bm*iG8LhSEV5OlQ}q*G!r6;
zlsXxr)RmwW5Xm&6qI#4{nWFe*NX|yFi96AO1ORgLd1Lc=$&gI7WS|oOB-#-5-j}88
zAxqa&7IG3w3qk;3fCOr%8C$4%e^E1$(;|@zAD8cz3-r|DqbR<+CIAM86l%UGVthX*
z07!DBV~fmVi`-&YV(ybf-L8ZKEC2-1G~y~o@)}911I8xf&#q4WOSDRHX2f@gc!>Pq
z?ovh+BaC1q!pi|#29_pP_#O5iOvW;3Q{|IZ$;@Uh<XTW=Xg`%Xld0JdocK7lNoFO}
zzrSlaQ=}it<!4$@WI*j!GT-G|ke%dso(2zQ@Z)m6{a@`fX*!@BcU5pg#=Qn8M_A>4
zKSSp!l+$bq#yr+(z>oKjWVQV(OXewDuC%!+<?&!Ul+*9{I+Ka)|F?E{CPVb1k+<(%
z{=`S}Va1J2oyLJl^sCa|7hzJS$V}zcp)YQQNwOqd$-WqEo)d<{0h)w88impPN8%8}
zew)Fso3A7{LYu#=G(TRnN1(13pU8fztfP{n5P64GIbZb-V{}zw%-nNBW+fxC-qg~w
zssW?TSCoVy!}E^~T8lCfqf>(6$)J2CnSNyar6=rU!-)m}{husDG-v)9f3OVDa2e7<
zQE*sPV%SK{B+z+0CD7t|sMSQY&0gp<6!!x!o2NKi{98OB9opw?ke;6q!q>sz-z)Ql
zGYR<*G9iHiACv}O-?VBubv|(ReGt&!64yVaV2Dy17B?B9voVyqYAESEEMa3LX=7q~
z)hy+z)w95<ROhL^(Aj^9HpF-mX#tQ;<lsv@$CtPueV<bc?bgW;&_p<8iJj-YzaX8+
zsg=m(p6D8!QktIeH9ecZ9Nz>WaZPX`wm2xZEGV`#=>C_OM7Q*m(z1;6-NoNtRImMa
zksPn!oB#xHYWQ(#U^vw<PzKLkECr6cEN?|P)%*l{K8T;tICM$@VyIa__`eDO0E(^}
zRL>BL9xc}>M$p7aa1^69YGm@?1-)T$y;BflNkIY(Bp{6h1v@fG<2W5YcvR#%QeeIn
zJ=NrQZFt@-63x(H%XBBn5E)X&=@_fjD_A(l$6YmMtQ?IhOmpO?!%$%$;Y9;v25ioU
z1zjru#lJQ;L7Z|>p1MYy`tLiZ9zsALAwGZr|Mvm?K^(m$55>Rl&_tW^G@Gh4f8PZL
z5aL4!c|(H#4|CN(bE(n{%Kyt;&_wb>|1aqJ-|)ZTzX?bv&OwhoJBq3H9Edpud^Bk}
zNfQbB5Sl53dXdGi#bJrgO2fJC!g~Mg#R*j?4YS1pEvRBpJm%a#s}hTGjge~DLa4%x
z=|A@n6b8iEW07iU+-+idG3Qiiib?<LB|(;%!3X*g&ZGFxHA%-h!vJ<cIt8-b_ZQ6R
z0WeSDiUq*lB7+zHUk~#?5C1+PEr@{wA6{s}_fG}ppcy&4YC#I&pggu122lew=EXIx
zQ8IGy#X^Q34h#VxYJV!AcS<d>&^?1EmQ!n3ULWP2u@ao^{$@)aM|Qqj22#yXTraKI
zy_{cI&jdQ34Df7^EPgi+qHk9c0A)B}iFL5ZSPstSi7hV6I2lE4NL<gzL@Tj4I2!^>
z?it19wE}uu^0*_)*u*6IZ*h6~Dl`$kwaIAUXWR(TjOD8@@RJT3>zM@Fu*DX;XOx$h
zSCgyiqo6Tu#skAw?&A7Zybwa_;f4#eu^_^&uzams6m0^HWMh^B!*yAEQA=T9G7i<J
zI0M|9Ko7)qDi-J<jn91)&mUJa6h%&r&SO6h$MOQu#!!qh>K5i^FGMnRa7Lk&xw(oy
zV9MDFF&I9&C}VAYu7V_t2cHQN!&jC>rp(Wki~(RG905Ji25z0&fQ=eph1GF~_N8-$
zD93&o9vV!n$V%oTZINXN-e~bLLZ|skVG<tgV6<^%GJHjN+>?aShRQB!^KnJ>EAbR1
zeSpeN0bq>hc0uPd0f!Qv-5404xbKK#H^iwFPQ0{uGm5#jP6SZi^$&g~ul(gP|2Q3l
z3-nA(Y-$DYyj*=DjAI+bmJ_DE5V~T8<C-($vnog;5YMc~1D!vngbTc^{x(?)z$O7P
z$b~bTYDn`jkqd}BapBHOG(%(pgvOnb%4F<ONf;8+|3e!!A(fARI)(#OV`x_>fpXYh
z{Odmx%)h_>j}k!$oZ%7Lza)g<mx(mkzc^7N`$lWX+#ZrB4C{lFXc2=yg)(1}F$pAr
z0AtIzr^+!RE1ZQRmRH*S`;~avlgAtcvi+%)`S{vR9S!-SFc@vYXXbI*c?F3W?W$4p
zc#*QgG0V8B4u^OxzWNc%I1ze|D|%%O6CM>$85}+N1?w3c<J(E>oURBKPkK;xkZ@QD
z2##8M3U@F}Tg4SU%ZL+E+@Hh<h>TeSa6J+MR3q+*RdQz0-6uMYV-v(THYB7Zkj_AA
zP>6|_fwY23nIz8`D#Pd4lj3pOgLi!}+8uv$37FNtIixmU7QP*X8u^MM|CaF<6{7xn
zOl5^h_`b>zwUD6j?YIgdgo`U9$&11<F&b)Dl$3~LC#^OqR}?LVkFN-&4NZ>GhRTe!
zN%Nv;A?3s|8$Tr^T^4Bw95X2KFq+U`E+9S4dSX=|A0{C`3BxC-7IChKp^!mGozCCD
zGPcx@0@GK9>1SK!Oo<VRN_u20x{Ok$P+nyT)a4@*OlDB-U?`mM)5-Ivf=rtdz+xd!
z3qe3A2HNJpXMbb>oIryaVA)h%VpQKM`ve(~B590ZXom=8Az*B7U1(#CPK615nXg8$
z6R?vg2~g!?30N|H-k$OfdP8|>kdHr*4R16l!6vjoYd}LY)O*><?TSx8&`oSqOnd?f
zzM2u-gpLU=w5qDOKLaBZGs_v)vuy0=I5;6Qe&T|&VF3Zb38KQi6A<8iYy#=h6Qhjt
zv2o?owp@?oMMZ=zh=_<tNw3fTR=g;$FxWdSCodzXB0b#oruAL_#D`CxX2vI1f9xOp
z{5(6i{IO?q^+VsU;i2WN7U!tWGcJ2Os$Rv*tsLCmRW((H<Twfv4&4UVO|}L)Q!gI2
z%k{i1@k(^C1^W|Zq44-lg|RUAm`~y@&qkZAZdN}Cw3yltO@lh2Ywl)s7}H%iHvMF;
zZPzlgF6{_oi~{kUOCHay&Pm08|2EhBM7Xkl%jZuC42fI5;ggs7<YB}Efi2lY)@NAa
zD9AG7JDbN8pR0PIW+x{D+KaaW6}h2?$i(~EKW73vWfM_j8%uIP@L1lt4omR{8Q)3g
zbk_2mRIKgs<$60&>tAEX=Y3$11G=!-(?Ra>Wjpnam)rdN3tRDhFcP}W0}$1jHFo3`
zV|{wIQ+vaEk?ZWI-?@&swQwnBJnH!5`Ext0S?a-al8dvq9bI8u=Hf}62z5KTb5fOF
zvowEHa_(OC-#=8vPa)nicgQBga$SFa*1{88-|8!jF2xQIy+fNg-k?2n5p$}x;t>@y
zde=)4g}!U<Zu+<}>az!mFg(ML#p~0g)zw#>M`!!K?aeiQ^L$`&7Nv6QO(2axTyKNk
zgBa1mwv3nG<Qd8*O~eL%eW))iFyKBD6)u;Pn!OOC;ac3XubaGabmzk~2jk>3>;3v>
z5y4QchO=tFmFhfaS0`&LhIiL2dt(NUX)X>6wZt9cZdX!xHQ77}z<F&3PC9Ft#YGMn
zfN?^*45waLgJ6Cb8qUv@!cCV9kro@vxK+G?ycZiA8^IQR9o++pU6$#W=>-L*>0kbS
zEnggF>G^!#x5>uK>|ovGcEgP-TXN6O=m7hWP#Xm`=k@IBufdXxu1Yqp$1cBSA5KzG
z5%X615CZ%2!UXRrc|Gb#mo`UV`a#R0YMb8`2jI(kcsIOMW-DIS;j_;i6EMIM5xB4c
zN`wz7FldbH%-5^EDohDj-^(|i`w_Rp9Cmy}y`xe(llrS8^FZs3-PHGjU+YJwk4}xs
zsUG8Y$dfe<$hl)mGkgwIDfrJQ*#Wk-OE7>mz?@(%H^m&#7HCDSDUkby$Y7O|99L-m
zzLX5$y*<q}OXmW2&*+3GFQB^GN*}sXbu-(swZ-M#Xr(9FZLjiFmuq7b#@xFdeh^wg
zF#>E6+RxMnHB$D(j!F)pTN8pvmfPAFdUZ9mNU(0j%f+A|EX_AbtTfh(A7Z>741skL
z)kD;X@UAl2^D_EyQtH|}g53`5gWX_H!NPA{n>lZ*Lkm57N3&ZlE)}W1JXQ{6eR+-a
z2<J)r$bc^>nuMeP^*J5@B8=5=Yfo`@pQ3-$<2^+R?lW@D7)VtW%ba#krh66hxyVo4
z!~_Nm)Dee5nHNJQn2!~21ugd?xG&}?iq=#-dH#OEx5CRL&@61d?qU@O$Kj&&uJBoJ
z4zrH+KppCl>1j#tg&*m{0N4VM%Pu9{=Vn8O9c^YIs~Y+`*f-yYef~L|DVK?1E}Yfn
z6Zyauy#g<nPx(Blr>`Mua2~2AU(vRyrJpsl%@W{q_OA9T=&5@)X40JoMZ9x-{nCC8
zB#eM%ATkScBotIF(faWMWP*z~b<j}XwkE<DRiy{_?0y_&b<WCot5h`9TpWMR$%HmK
z`d!|0$6JftIhYsfy|5a3G}lxVc0Vo@5ST~MVc{?z?lw9a6@{@U=p#wJ)t=I85^nns
zn^nBts#`PftZbUx!_Ligc<8Iw8H)F8Cb-{sR|}isBx4XAym8Y<+(zfu#MJ%bGRY0L
zPKipclSdWCTIG^z8me%)VYm9`o!Y0br4XuS3!#s@hL<I#*B@Q53YXAYMPWmv!<>Ta
z%x}%Bk4+XhG{;|J2xQpdw=n-|%CB9US$flk$~qM>zJx}-Fl(lQm-VpdP-!i+?qnat
zaQ~M3`p^}#_uJf2Gvnw<jz{r?>MqW$-=uAecXZ;^(t5Z~q4XN79ZiKb9r{mnpPR|=
zw>wV;Mi76#UbkkxM@MJ$;<^Ui4z!i;3rISZ^0K||y437Czc_NUbltqgzGsG2WbK|9
zw-!^Cdc`pRY8TqMO%0wL&3NwI663B|Lg?#r;7eaEZ57MYOma0%+IWVxjhnz6QUN<k
zpB&?{RdKhPkGU`~0UcF6bn_aV5ah*Dw3#ZJHxnBF6dw-!{rdRFpNX{ZT?A{7H^Nh|
zmFzr?d~kg1N7K>w#6Euy0SeP$z?KNGMG~y0l9DjhUjX*X{1VE<=n84Ve|jJC<I0t^
z0P8>nVpwIMy(u^|1(nVnNx{L?&5`Jom!TZQz~E8DM6P?l&1V#}YP!Prk0g?b_*ZWW
zaX<a?c!Rr#=|$dY1o&2x4XqW}o5R}N0Qzm|bE+T?j%(y5vor1SFK916Zrpf?-}q=t
zYYBk20c5)x%~A7yb*5b3j*mB|gj_Q8-tQJ#^;C9GaU{naAFpsYxpKb_*w%1Kakg-A
zaY>P(1T|FC4^cz6*<N4fhcOv@5KLoXgZ%V=X&EX}ZJv4Q;k7|+I!p%`68-zL);pwK
zP-PJwh3Bh3$DaOy;o@R<&cPXL)ptm0wqEK|QjmJKqq}vXEK|$g+Av8tz+q}v4k4!U
zLW~`T-HCsswJ~{5D}lhevkk%tOz7qdlmI~l3@Ud-g`NFj)jwG-9)#Q~zB*%cPcO2T
zzXLHie-!~B^kks=k?xCmsLO=^v@&aj_m*(*WGu1*@)k&D_|Ux+xaB%pUVm$L@N9Ta
z8D=1Ip;#oJpflP2fPfROI=&Q8spzO%{kYdrLDIhn!BR<vd}V}9!+$X7i?ToCc<l}?
zFnHVeJippmLBLw8T2|^&fI1qE1mdHD2j<PU1&0IY&6^ii8CSk1nC26|4q?Qmx#KQ%
zbk&l_DLPgWw@Cs}=+awy;fC+xhhLC6!8u1IZqD>H8b6<NR9L>O(ZXbhs1UQ!B$;ss
z4$HWC+bRnsGb#^mGOIPI1LR_`+65(=$d8X8A6`J;yIlEVUyY3*)j~E6%|lTYvdSUK
zroEQT*oDE%479f|vs|DbwtZq|W+s<)X;{3gCG5|y?TMgI&sT&VUZ_VMdB9M5cHKXG
z1+t>f+U^dM&}-5${5d+Gd^b{<B+|h2OR>k8*#*5RfnQ64IVEKG(-}j34ee}SEjbWc
z1~o6|qqXga6E{2qKTEYFa#wB(yG^@fpcCzTEH~tA@+4bm@5Ilq6Q7H#<;BHJLLjY0
z1M``vLmLQV+vk9rG6^>x2<AH2==IO3P>lAVO1bIKriLnUp4Ue!sMGFi*y<XRf|@m1
zSN#ERrcVK0vFYWdu@uS{^rZQ~c%rG?iQnBKWog-sH<s3hA}JGHscm*;ex(bjhPzn2
zHQ{h@25kaxpU{VJZ1@t+q_7bWd2l;0;;ufySb{1MF4Hzzz6L-Ij5TPQN+SG4B<T+K
zZ@aa7bXTq<$)hZvBkIthZ8p~zW;veC_y~z=v$g=X%*UFe*FM)5GtCilLPxxGJ-em>
zGpsEtQwXr}G8p9T12(q^b0F*p@L!T3Opo1YB3vNjQ6$HbVsGDOK!>(Mq?hV5@Ic2i
z|Bz}tKQ_=C_k{s}h7UF}Ko%jF1`a-E5MP1z_PtQJj?~PqCCWyxEz6$PtyCKK=>v3w
zwgyFP1i8;hZX2X0MBqhqBn4nrbepES7VZ~GwUfkgxNq8-qTZ#Fw<9}9I7!L+YvG;5
zvzFwDxO?+&PlG7g^xCk}vP*C?5=njl&TtxJ!U!n4Wi^3kFpd;{0d*;Rm^eF@6!v$k
z+mm*@-z=f%cZM0j5*!%7yzvzh12ho<P~d@VXiT5GIsxG5`Fr~#LCN6Ov4*~%_xSaV
z^ZjpPR$+rv#E{<3-5|FIKwLOHS%`>$7Ixwakg`CmKz}0%hXFTM^Ja=`;V;lHjy}G-
z<=On>oEAg-d-R%fDS17i==SrcPmF|h3Kn+}Aj$|}&vSa*$E_W?1B)P1(}N4=8IS{|
z*ML6Kqz&#C!2pEW|1KW;NrIC`ixioMjh%p5uP9JL4hso5qK7Om+SEV85;mtzd19iI
zWP!suIJh6%MA4SEZD|CERo3**6nRnfQUpq!{-sUCkZVoJ3qU_Wmmt94_@?TR5qup1
z*us?|1a_p+OtA)cBwqM66*O9fsu9<oIVyi&BQ!`AlQ7WeRy7{ws#CBj{j^$s#3f}V
zj=;?Ab0sA(SQxyMtJKF2vlm>W2S^k}=@%jaEH{BLp7+?rK9(!;JS0E}<QMGFl+x~C
zAQb_;0+tpih<V8hrUY#nmSiVqW<DXn-nLpj6{P<xtRnS;<b@e;Z?ABEa#%H0JSmn4
z#0g<dzr?bhmAY)fB+u3+jOzozsHW8zXfD$h2n~T5sb-3R@N4MXOV^e-PWPD8FvZVX
z*#*iO6LiC}UrdFEQt`fd!~3ZPV7s>J2kvZ*)6In|PLZPDv$JXXq+J5$a$_uz26-Td
z<Y6EK*%nWT-|IL~-Z1(2>A-Z>S}-7m<rWS{D3{6vGl&vmbzxESiD{WwK$t78ZA1ZF
zzH<QFmlWLUQcYlOX)iZegW3x{<?BsCYZdLu5~G3$@w#)V6=B{3r$;U|#O5i8>#tBS
z;rf)A%m7eXf2R~W%x8F}sV>2<!t|#VmRilOgzA|~N^19`R|8AQ>Y0TXnUBXOesw%b
zQvdOp7-$B<K(+AxAQeEi=TRy}_+BQ)HjE960|K?8{Cxs|S1HF6je=<6mH(WeM2{r_
zy>D%4+-9yG05up2D9Oa|xKaZw)uEL;nZdX)Zo59fSs-kDmI1_M>2DRFZH4LOXF0)2
z>Zw4Lvp};|mvV5i-hV7^r_aKJQI86cI#n4^;H*ddDtwbQdB_RfdgtSic$ZZv5JrH`
zS`7rqK0Qrk68<5)_(*27RpVQ0@r9^+iCYhDoUtYBrW9Y2`f<!;l@lSs)IN0@6pnK%
zUrZf(fo7ZwckYLYQYqwiN&@l>aw{;HtNw+E0-S=U;pBt=F6Abg>9vs|IV*x7Pz4`e
zGLnsw>uWFp(M9;2(uk}v9{@#_)pDf-;K~EOU_39-?+CbECwtv|r6?Ai)azz07^zqf
zaZ<LKibB?v4Ah{2PC?R(G=lV`+Bx+>Q&<8^0)95^ek4w9DfT>|lroH1!QX~Wg3Y<9
zWaC&vx24!ZiT_^W)buusDItbf_du>gLqW|=IQD@ZXsh^@=jT8FX-aGkF|okac?l4m
zYEvXea!R`k0`8>e)DT@9^|WmOZ;MYX7^E<T@83VkoVOUK0IDEu95xv8`8)|&%p`9&
zn%g6Vqhb8+-ulFBEwd+=6zE2Pe$0gdyg}X<n{TY`?p|9xJY?+;c%0A0U%^k0fb2gw
z4I1rW_AoXZ`UYU1!AbYpKs>7;HQz%b=sVY(F@9^cyQ)ACb5EZ5F?tOY%~&1n(LDbx
zk$&z>JZ=A@?6>73!E~$XJT{DBdO<Wy5WVDWguo}7bu|EBnK%=8fG*ZCD;U^k6Njt2
z=R(#KvdN4H{GAILBJ9UkH~%D6QZ($<k2kh__L&bNp+Y|xY&(qtV70B}9>O};Hm;kV
z5L8cy4R2Qi_9{wV3`1J2wckf?fB^EItBAK;me?>$bT~|Y0f7RaWPv%A2|y^^V-~`e
zHm$rA*)R9*p9+$jk{S4dw8gsDz@bAEh;I`Wz=1tNaYddeZn1If#g_ak2e;iLK1{S>
zt-{y9K^;pou1h7B4nHNbX?e}R?h=B_t8vD*^piW!Xsz>3ZQUdWxqSJAy4bNE0{b`R
z%>-B?2|rc<Aj9W)A~VR&qRXX&w1gDacmXfWV9TF}hPPy^o6HH6b`wVZm^X#BA#A-C
z>c7Ei%BmM(pbalNd&vx5aE1C|>eboCkDQcJ-(P5j1w1A&6r(VJ836n5LJVN0FOH9G
z=FbH^4+Q~}@|QD3D`FIH>BwK{^8#jQ1T`~CkZhI(Ss>PN`JY<(whbFC2Ya#KHB^iW
zqYW{v+p<Wmc9X6>TiYK98fzp^fln9=Eqy)tngYkFtts~Wws0tf4%ouoVY!_GZ4ozD
zApMpVRfNDGk{tZSKF}lCYwWV;(l;uPC4wdV_?QBh<za^bV;JCf3}6F{35mZwGR}c-
z#}UMkLyl*O36Cl)mn*ZfVbZC#wBim?n;*td1W=L<!-4b}tg~waCESsD4Wp3lSZnZp
zk2B!Koe~TSOQH1Vdl39@Ga(0~*(f+bBaS*)?(Ez;Yj)lH^X&Qg<a6ds(W46EdHheW
zT3+9f8jrcoCCy0K0(X)1cQ8p7Q^N+6C!mH9vAXbYGC-9v7^ERjSPhV!iDV%E!CM9*
zo{pf6!FO+ijBYC3BqcFnLm+D{f;%-qd6-J<04V&<+?<QMl1IpV?)OSHO%GasRw6O#
zamq0HMw}*capWSeTN>wDBN)U%4H$h*_M(kTRrbcv{mCu@Iq<MqIi>8Co>Eh~)x<;V
zR}3hKBfu~1%O=2m&S5BrAV^#MmjGx@VJVr!<%@OhYhFKFxkmKPoHu{?GBNZmr>|}G
zj@-Lr9f$LT;ub$|!h&Exoru(l5Sw_{`noJo)lxg%rfjba#BM-_2{KREQUaKgpP%<O
z1dp%@@ltc)jYN)I_^-5B4;FN#i<TrIh%*iCkhO~pHuAr>Zdy1l7}smFFgttw@miMG
zar^TfF1pY@^QoeE-y4r>zP({68;J;-9e%`KEKKMY%maY)_*lO@I{~l;Ol=Kh*oKKG
zyuw1~d(+4SU7d|TKK!y2%PR3qk9+v}i_pi3?}qyhm;hD?<i?I#LZ>MT%p_k~{b}d8
zZ`^oC?;#b<(fQ!00`vs7aZPA-VDk)_gvP78&WkO>nuK3Uj4-N{s6c}Jcx&QHB0_s*
zO$ev`9xo4UU473H{$0ucxUw5yLt3U(i8^r~yhuF!X=_*-l`cIIciMO4FuGIrHsSbX
zwp>!tMa#DhdPoPodo%sE+bf}7*Fq_huP^ePKl`h7z*0}%%w4o`nJh%qpalJ1ikdbE
zci`nV3uB$5$4e*KY9Do50V?w`R)!mHot4_7dLKN8#<fUp_DrJ8acu3Q*LZFg9uHp}
zhj`x3ZO#pHNqXAb$(VCJ-!|WlhbyL7-!<uerLC2;B35C>?^Y<BBA;ZWZPWBKb29Y9
zcY+@zl7n}-t{+*kuOTiyFm%$+l_noFoE*so@lkkm{l?FudUy2Q1it}xR)6nC5gI1M
zQxJ>?e%iOoTFpH#iLzd3J}M51e_*mx^{H!nCZuFrarKU+{C11U=&L1R1gmUGwV(U%
zwGBmios0V8V?|{*Lz+}QJw9gbW)YDIoCdu!mCeJ$dV#|_0ofah0oMcO46ct&toT2w
z%jyqtnekSUf2<I)dvo<n{8dNYk4_E)`%#*SoHW@&vIN+Lu<NO=d$!XFu%K@?gaD0_
zhc*m~jYKi!O3?Wxo5sP6XWw^U1~N0hcI*<orFqx0yjf}>eO<hTEL(W!QZJo%>(7d#
z9Z4oHH)K1nC|8^6>2ez$0%3OCB><3`UHG)#eXjj6X2X<cDuIW`SKCECO=p*rG93@5
z(&7%5_JSo+KdtDAY5n>LJ3o>&H2v{lO7{1`PUkI+h`#NS4flG_4B}joU(?HDR(5;C
zYmG*qoxOr@#PSr6Sb3wvV>AC`a>sVRk-q$Mc>Lpy95eqhv7XH9IXhR!H>RJ5`tP!j
z6;yxRn4?x=i?sLk74cvuL@OQru%tInc`D!+fi6%g10eP^w3y2f<-&C^#gtcVDs1O-
zJqk#_XWbY$ljqnK-Kce!+|m2<x80YH;_OxRa4;9nM91dn>xLygSSP|3x^$pmX+KVG
zk?emY(&v(e^l<6;yoyK|pbnuy%4b`TJR+4@vqW9H*$F)Ds&Qtujllv3Hgm5TeBbg>
z`$+u3rttOP*`AGjB>jk6RqFk|DHhSyDa1}zU+Mv;$)ruT(-~T_($rr+;@Jysbe6*z
z@bCgX!HG(*oJOj=Ia28U!6{#{Vi#C8-W?)77*a?7Qg5}YHlFLUPOWe)2f5C~iyE#P
zBoU15Rv1wi<cgXQaLn<-Y6&5V3yf-Q&90v6*ZrePz})|IF<QQGKhUh7en#%<Fc&qS
z9qi@d@4fN8mjZ&D=&mgiO>J%N_=^OP0ZT^dlGs!H^>{j47BR1mLu@y0&ctW<7tKt3
z+X>yuaAc1XVGh~K<Gl*f79(yI>r)egcXl5`1P#o>9u5#G#x_?RG6(8j&@0BmEA{Vw
z_?p18j5<?J0v>SR$Pj{d5D0+vIp(I@^(#DGSmoOV6yLJn1V1#1Kl<a<zIrN{`_~^u
zdfR>8GW%EemL@mb&ylHi`*O_z(83G@=$Xk;fEQ_PqmQHb=U|M66kXl~*0GEHd9t67
zo=nvDORZX69k(G21P8rmVVw+Iy7vm(gh<HR82*xl{z1!~^M-O6>o~(kLeRO-vO%oC
z(a*}Z^NBU?+hG5smT1?ORYT6RY)K;_y1hh|b}hQw*)Ak!${Zv~g-o(t<?k^&O@84k
zaVtU}-w_=_3|^kNQQzP~R{!#N|L_ImbJQ>n|MtF8d^>$rBd6tZTJ>#hH_85Hsk;qN
zR(5j=TE&{ju8dYpk}Xn@6u!Deill`+fvZFYt;g1I><w!;j=VgZTD!E`lAwY5EcI6;
z9B)Gff>$o_t;?PdNh>K@7f*^HE-o-@JA3bJlJ~o>G->15!osL3N^#NJxCpo~(O)`q
z=Xo|T-|{+cs2aIZ?acczMGU~kEB3-Fd*ZCsA&j&UK;@=zEu{U$V82%jX;a5qk5c1d
zW@aUC$#~4`yOmt4xvsKJ^VLrJL$SYd`VmYfDz~?dbl##MpDE;(z9dC=>GJLFPcy^s
z)UGl>j=r7KuJNJZmuR?dD^F3|9{pZQdLW8W^UpO#aD@Uev+#n#70B_>#?cp1<<xAj
zARir4yb+~0XT+q#`pKl*|8h5XLl*y&H}8@U_YXhUbQ5fyO^8lR?C&gQ;{_aenuM>P
z`4O`DB=1^Jg1>$ATu78PoDpDWZY1oi+_khSRi#cL7r#|2bnNip)ZU8AWWkN2n_PGH
z5{!$hE)VFXTvpr{yvVT3FyK}9XvJJCHAN=qTr{N<856j%)FX#LiJpmlVHju}*MF`e
zvHSOdoiG1}z^&5WbB}E_US=1nE(z6!SxH%3;n&dOaLl2z-2RnjwsV&=IcYO2rbPDD
z;D|a)TWSs+08b!QQ%t$JV8cdQ+HbSC*ZW)c&4t`<$1XWvZAG(kiVq=$Of36p((fLZ
zJ*VQK?Y)R_5+JH$>g4sK4bKI-Cw8Y(QWkS<GvpW9(LVKN(05WBK0=FIN0cX^R=C+u
zi?R92^S}>TQ&o~)gzE`6nI$}n(i+_Mx>kP6OlQ$aNOWH^OtQ9rf5#L&JRBy4d(iTN
z)3g5BGQ^-5HfM%1R>2JR^>t|r%$@&iTb^}P6E{NT?>hX)Kt-x;CIS8>UX@J>qQ(AT
z9ovzR2eTW79I+V+JYjQ2iMCIlmh7`y6qAy>y}QqKc9DH)rd&kJ)iqQpTz!xlbu75l
z1;goO{A-ljLqfRjKh*MrviDY|w3tDo)mOqU#!dD<MqrWok*~dJtl!JJU8Z*-vP%NZ
z$Rl8IdSH9{Ynhlu<<Z))Rsnry!tP$2RX@5~O9Fu+1%LKSbQ%$Df8Osc;K#M80QDa&
zv!2DWUd{OM>G9|K>pT}aoLAKuQp#N@j4mVwcq->{y_A7m?zuxUU=QX_gUt{eDOku-
z#s=JK*t;|(KrJ2pbdgFZ(abNzK3m`veCyYr7vaoaoLrIlV&S~Qc}fJr5qPJQR{OZU
zZ!x@XJwV@|=m+&5gG(<Tb(l@A$d3wdk^|NU5U}j5+dr!FbbiX~Y^WQVdEM4*``~7a
z&8%Wb$;`$s-^=p;Gc#FPWBGZ?G8Jz>&aR0iMQpWR;(oLT>)7;}wy%NR5Csfvn}jBA
z6x36BDlYKNNF&dNg`5g9shIOdWm$5>_C=Sb6`iLf)1LnLp}BM1N)JJRrGwMC<>lq8
zIiL3%H}~wKp8CRs%`ZZDv-3PifHC3d%{B6~so7MVd|o&!E+#qOk;o&@WwtTd(Nx#W
zZ8j%->Q3+>b=7kD1Z<g9GgkVH0R|%`nhQzq#F-kVZn`*5clyu1{)>FYq`Ancz2t3n
zl`y+6H<TjGAoS&%ez>AEZlRO>B#e1E{X@RxrJthG=d+%#`mcQKUp>t~&bP6582dA$
zEa~yCe|XD+<%a|vp(av*+_(veano{b^6p-xQESfO_tWd4DH)G61TWm~|CM9?B;@S0
z#h`F<G7qoGM!C{E-`@^P&V){du%y#QWyWMk4BzA{^QbKNi?%ud_C;VgN<XV$tjQcb
zXS1Gtq^%`$?7ASXQ@xg-`MNcIJ*2)NM(53`M`4E@!#gyMpA9r)OR=;rFg$=KL?dDG
zcyimOtr+gLHpQnPl~E&C_sS2VecNWK(O8Ej#Vs2myPrpWtS6TCp$zVlY3AYqcZ6s<
zMfsWSmX=3%yg5Ajm&F4w-%C0CIySN<UgoW4yq*Jq^<LH}?i5P2Bc1!+XXeP%eCKBQ
z7rl5s`-u|c^@O;hOa23{J#3_}c-L_^8+M`qjFjAhaLjLl3eILB%g5CHBYi2frx|p@
zXyX=xx!$}ay(KrQ0pqgf{8C`}`#oijYs4dzuUl>mNRvP(8P}6F0()Al2!5rSKZ*V*
zX;fP$tH9dI?u=Udv7bjDRsVtml;bum=S0W>Fsy~^+U+MYyb3FecX9?K7bmN<Lv_u(
zBR<MVnJaEr4}Gc>dvE=&Az<_~W~l@n9&+{)?k0g|kYW`(Zg%ao?vmJWpRxWMiu^3I
zqgS>=;)j+?)bD7~WAq?~46K#lj=0~Prd^>u9TUs?I@67+9#QwX<fgT4vp@NibOT-8
zh4sbj**RS1vRrB-ZJPWh)pV~J@b|Evmixr#7XyQhrGQM=ed-{{cf*E!56PF)YRS?_
zoxhODv^E!TJ8(4A_xt&cfu^(aF2BEC?k9e*@aO4P?-H%XS04Q!-f2Er8JU%)mB@=c
z>HZg*E*@8e6v4JjON)DMEqL|HEVHm`(3_cHWRfkcO|<T=ZZGm__(w_l3bW*NRDB!y
zy%JdR)A&nR#dFHU)F1Ab+1Bf=lY<<Je!Y+tq$tcvL3A~wZ`hqBM|>tp7+)!jaz8E6
zbXIaOObd3KhzLiydfnnP8fVteaz+}D&nY@~+12R8k|$ek?KjnBUmLjg#2^f24zQ32
zRpLb-#iqfLsK3I4!go2YXdP?8HUdAFSDl6DokU0ND}tZT{~nmVWm{K(91}9SaMf`A
zmK$TC{@IND?B2rSXi-B%>#o9R`JHr2(XZFLsyX+{r^6MH)Pus1h7Xk|MngM~iRsw?
z#6F(}!@bxqVUIF@20RUMxtzlHN}+$i(#Rsc;K-6SgPI8egO2}KxA6Z9IM9G$_u%-w
zW`mg`S>e5Pz5HHqt|&xe!Q!I|$=l<5{l6xCrcBDjf3%g%ckiD?SkI|rIvBK!Yyb%v
z8b2Z1P_2UZetm-LeKEUT615}mPC`H2wZMAI@$vY#2bX(W4rCRlC3==JL%ClXnn~Rq
zNYo$jI{Y+o8jX-{DJd%(V%}Cz5Fgf68D@Uff53YN(4!H*L&6}9RK{H9vSFsEp3F>j
z)4sD-8P-r*HM3oM$M)-6j>`6*sYhlJi_UUvxLt;|YsM{RZmkw3jDTcBsiE~N=9>4q
zYq30&xh7;K6}bj_i$oT$eYkyzMynbCX)P@pSduaSX!$8P1Kv}>wkd=>2k8lSY&WxU
z2pSzJH9OX`x3^5z^YZv1QhnpYs^Dzzdd{pbzuLVuLio4z?1r4nRXZ!azf;y1{5vT`
zD^{yNrsoJB40|Q1^AY~6&O{ZV>he7n%P0X+<iSEveiDXlB{N-jb%pj;z}3<b|DQ$g
z>$mD|zv~RBh|JIpD0sTA2PIif9|GHt2Y)eJ9>0w$=TGb$pUfMPETbyA#@mGZ6caWJ
znOdBQ{7uNM^^|VwU{S92RYyCSXbQ_$zF^y8W8TAbN#0W3E54&MEq^qKy?EIelOR$}
zs!bT!aiYPN@pPrVm)_5CUri$5&}3d7PbJX_#oc)c+Fc0BOw`%%&*teo%E_+t+$)&8
zvu(1KDUkJ%M`8Hr)yI+PPr~n6zF(O2&|P+0wa#&QWG%{h9+qw`Dh|W;#THnLE+&S5
zcrWF<eYGO^Fl*+FT(y^%#N+WM#lD}Nfu3~-!{2Wme(>FvJ7ClN(KcUa8$5Pifp+Q0
zYRTe>{93Z>beQDfNB#kq3e*8csVF1Vkr>j~PqT!zbGLH=Ys#n0QrZsn%#iXuE%%f=
zK^=*=3{Gjn)f&I2esX_2hcroRyz@ARdTn`}c&?)kLe+)wrN(GDQWdD2fU=;(@6Orv
zbS}G!0ZNv)rt4|>+ZX}cisWf(Qxj)F9yzd>nSasR`?^x1$u}2jyXN7;ys@M2;?X;=
z^w3&Ql2(bpRZ7s9w4~6i2Pus~)z9{`Nf<~0DZt`1DS{>Fb!~@3O`NB}`!bIRI0`C1
z#{DDD4ssD#Z~|Mg2or~~*b5xYTC~Q)J7fsLc=M}B=-RMC;GY2t`+8Nj=ct5S?#bS7
zcXF`p`F`lhjnd)`GvG!wIue>uVE4Gd?t8DaSbt?9G%{P<s|y4ATR*-IrS-!81O1$h
ABLDyZ
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -35,16 +35,18 @@ browser.jar:
   content/browser/loop/shared/img/sad.png                       (content/shared/img/sad.png)
   content/browser/loop/shared/img/icon_32.png                   (content/shared/img/icon_32.png)
   content/browser/loop/shared/img/icon_64.png                   (content/shared/img/icon_64.png)
   content/browser/loop/shared/img/spinner.svg                   (content/shared/img/spinner.svg)
   # XXX could get rid of the png spinner usages and replace them with the svg
   # one?
   content/browser/loop/shared/img/spinner.png                   (content/shared/img/spinner.png)
   content/browser/loop/shared/img/spinner@2x.png                (content/shared/img/spinner@2x.png)
+  content/browser/loop/shared/img/chatbubble-arrow-left.svg     (content/shared/img/chatbubble-arrow-left.svg)
+  content/browser/loop/shared/img/chatbubble-arrow-right.svg    (content/shared/img/chatbubble-arrow-right.svg)
   content/browser/loop/shared/img/audio-inverse-14x14.png       (content/shared/img/audio-inverse-14x14.png)
   content/browser/loop/shared/img/audio-inverse-14x14@2x.png    (content/shared/img/audio-inverse-14x14@2x.png)
   content/browser/loop/shared/img/facemute-14x14.png            (content/shared/img/facemute-14x14.png)
   content/browser/loop/shared/img/facemute-14x14@2x.png         (content/shared/img/facemute-14x14@2x.png)
   content/browser/loop/shared/img/hangup-inverse-14x14.png      (content/shared/img/hangup-inverse-14x14.png)
   content/browser/loop/shared/img/hangup-inverse-14x14@2x.png   (content/shared/img/hangup-inverse-14x14@2x.png)
   content/browser/loop/shared/img/mute-inverse-14x14.png        (content/shared/img/mute-inverse-14x14.png)
   content/browser/loop/shared/img/mute-inverse-14x14@2x.png     (content/shared/img/mute-inverse-14x14@2x.png)
@@ -107,16 +109,17 @@ browser.jar:
   content/browser/loop/shared/sounds/ringtone.ogg       (content/shared/sounds/ringtone.ogg)
   content/browser/loop/shared/sounds/connecting.ogg     (content/shared/sounds/connecting.ogg)
   content/browser/loop/shared/sounds/connected.ogg      (content/shared/sounds/connected.ogg)
   content/browser/loop/shared/sounds/terminated.ogg     (content/shared/sounds/terminated.ogg)
   content/browser/loop/shared/sounds/room-joined.ogg    (content/shared/sounds/room-joined.ogg)
   content/browser/loop/shared/sounds/room-joined-in.ogg (content/shared/sounds/room-joined-in.ogg)
   content/browser/loop/shared/sounds/room-left.ogg      (content/shared/sounds/room-left.ogg)
   content/browser/loop/shared/sounds/failure.ogg        (content/shared/sounds/failure.ogg)
+  content/browser/loop/shared/sounds/message.ogg        (content/shared/sounds/message.ogg)
 
   # Partner SDK assets
   content/browser/loop/libs/sdk.js                                                    (content/shared/libs/sdk.js)
   content/browser/loop/sdk-content/css/ot.css                                 (content/shared/libs/sdk-content/css/ot.css)
   content/browser/loop/sdk-content/js/dynamic_config.min.js                   (content/shared/libs/sdk-content/js/dynamic_config.min.js)
   content/browser/loop/sdk-content/images/rtc/access-denied-chrome.png        (content/shared/libs/sdk-content/images/rtc/access-denied-chrome.png)
   content/browser/loop/sdk-content/images/rtc/access-denied-copy-firefox.png  (content/shared/libs/sdk-content/images/rtc/access-denied-copy-firefox.png)
   content/browser/loop/sdk-content/images/rtc/access-denied-firefox.png       (content/shared/libs/sdk-content/images/rtc/access-denied-firefox.png)
--- a/browser/components/loop/standalone/content/js/fxOSMarketplace.js
+++ b/browser/components/loop/standalone/content/js/fxOSMarketplace.js
@@ -10,16 +10,21 @@ loop.fxOSMarketplaceViews = (function() 
    * The Firefox Marketplace exposes a web page that contains a postMesssage
    * based API that wraps a small set of functionality from the WebApps API
    * that allow us to request the installation of apps given their manifest
    * URL. We will be embedding the content of this web page within an hidden
    * iframe in case that we need to request the installation of the FxOS Loop
    * client.
    */
   var FxOSHiddenMarketplaceView = React.createClass({displayName: "FxOSHiddenMarketplaceView",
+    propTypes: {
+      marketplaceSrc: React.PropTypes.string,
+      onMarketplaceMessage: React.PropTypes.func
+    },
+
     render: function() {
       return React.createElement("iframe", {hidden: true, id: "marketplace", src: this.props.marketplaceSrc});
     },
 
     componentDidUpdate: function() {
       // This happens only once when we change the 'src' property of the iframe.
       if (this.props.onMarketplaceMessage) {
         // The reason for listening on the global window instead of on the
--- a/browser/components/loop/standalone/content/js/fxOSMarketplace.jsx
+++ b/browser/components/loop/standalone/content/js/fxOSMarketplace.jsx
@@ -10,16 +10,21 @@ loop.fxOSMarketplaceViews = (function() 
    * The Firefox Marketplace exposes a web page that contains a postMesssage
    * based API that wraps a small set of functionality from the WebApps API
    * that allow us to request the installation of apps given their manifest
    * URL. We will be embedding the content of this web page within an hidden
    * iframe in case that we need to request the installation of the FxOS Loop
    * client.
    */
   var FxOSHiddenMarketplaceView = React.createClass({
+    propTypes: {
+      marketplaceSrc: React.PropTypes.string,
+      onMarketplaceMessage: React.PropTypes.func
+    },
+
     render: function() {
       return <iframe hidden id="marketplace" src={this.props.marketplaceSrc} />;
     },
 
     componentDidUpdate: function() {
       // This happens only once when we change the 'src' property of the iframe.
       if (this.props.onMarketplaceMessage) {
         // The reason for listening on the global window instead of on the
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -15,17 +15,21 @@ loop.standaloneRoomViews = (function(moz
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({displayName: "StandaloneRoomInfoArea",
     propTypes: {
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
-      isFirefox: React.PropTypes.bool.isRequired
+      failureReason: React.PropTypes.string,
+      isFirefox: React.PropTypes.bool.isRequired,
+      joinRoom: React.PropTypes.func.isRequired,
+      roomState: React.PropTypes.string.isRequired,
+      roomUsed: React.PropTypes.bool.isRequired
     },
 
     onFeedbackSent: function() {
       // We pass a tick to prevent React warnings regarding nested updates.
       setTimeout(function() {
         this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
       }.bind(this));
     },
@@ -242,16 +246,17 @@ loop.standaloneRoomViews = (function(moz
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       isFirefox: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development
       localPosterUrl: React.PropTypes.string,
       remotePosterUrl: React.PropTypes.string,
+      roomState: React.PropTypes.string,
       screenSharePosterUrl: React.PropTypes.string
     },
 
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
@@ -451,17 +456,17 @@ loop.standaloneRoomViews = (function(moz
               ), 
               React.createElement("div", {className: screenShareStreamClasses}, 
                 React.createElement(sharedViews.MediaView, {displayAvatar: false, 
                   isLoading: this._shouldRenderScreenShareLoading(), 
                   mediaType: "screen-share", 
                   posterUrl: this.props.screenSharePosterUrl, 
                   srcVideoObject: this.state.screenShareVideoObject})
               ), 
-              React.createElement(sharedViews.TextChatView, {
+              React.createElement(sharedViews.chat.TextChatView, {
                 dispatcher: this.props.dispatcher, 
                 showAlways: true, 
                 showRoomName: true}), 
               React.createElement("div", {className: "local"}, 
                 React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
                   isLoading: this._shouldRenderLocalLoading(), 
                   mediaType: "local", 
                   posterUrl: this.props.localPosterUrl, 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -15,17 +15,21 @@ loop.standaloneRoomViews = (function(moz
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({
     propTypes: {
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
-      isFirefox: React.PropTypes.bool.isRequired
+      failureReason: React.PropTypes.string,
+      isFirefox: React.PropTypes.bool.isRequired,
+      joinRoom: React.PropTypes.func.isRequired,
+      roomState: React.PropTypes.string.isRequired,
+      roomUsed: React.PropTypes.bool.isRequired
     },
 
     onFeedbackSent: function() {
       // We pass a tick to prevent React warnings regarding nested updates.
       setTimeout(function() {
         this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
       }.bind(this));
     },
@@ -242,16 +246,17 @@ loop.standaloneRoomViews = (function(moz
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       isFirefox: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development
       localPosterUrl: React.PropTypes.string,
       remotePosterUrl: React.PropTypes.string,
+      roomState: React.PropTypes.string,
       screenSharePosterUrl: React.PropTypes.string
     },
 
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
@@ -451,17 +456,17 @@ loop.standaloneRoomViews = (function(moz
               </div>
               <div className={screenShareStreamClasses}>
                 <sharedViews.MediaView displayAvatar={false}
                   isLoading={this._shouldRenderScreenShareLoading()}
                   mediaType="screen-share"
                   posterUrl={this.props.screenSharePosterUrl}
                   srcVideoObject={this.state.screenShareVideoObject} />
               </div>
-              <sharedViews.TextChatView
+              <sharedViews.chat.TextChatView
                 dispatcher={this.props.dispatcher}
                 showAlways={true}
                 showRoomName={true} />
               <div className="local">
                 <sharedViews.MediaView displayAvatar={this.state.videoMuted}
                   isLoading={this._shouldRenderLocalLoading()}
                   mediaType="local"
                   posterUrl={this.props.localPosterUrl}
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -207,16 +207,20 @@ loop.webapp = (function($, _, OT, mozL10
           // process.
           this.setupOutgoingCall();
           break;
       }
     }
   });
 
   var ConversationHeader = React.createClass({displayName: "ConversationHeader",
+    propTypes: {
+      urlCreationDateString: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       var cx = React.addons.classSet;
       var conversationUrl = location.href;
 
       var urlCreationDateClasses = cx({
         "light-color-font": true,
         "call-url-date": true, /* Used as a handler in the tests */
         /*hidden until date is available*/
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -207,16 +207,20 @@ loop.webapp = (function($, _, OT, mozL10
           // process.
           this.setupOutgoingCall();
           break;
       }
     }
   });
 
   var ConversationHeader = React.createClass({
+    propTypes: {
+      urlCreationDateString: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       var cx = React.addons.classSet;
       var conversationUrl = location.href;
 
       var urlCreationDateClasses = cx({
         "light-color-font": true,
         "call-url-date": true, /* Used as a handler in the tests */
         /*hidden until date is available*/
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -1336,32 +1336,39 @@ describe("loop.OTSdkDriver", function ()
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.DataChannelsAvailable({
             available: true
           }));
       });
 
       it("should dispatch `ReceivedTextChatMessage` when a text message is received", function() {
         var fakeChannel = _.extend({}, Backbone.Events);
+        var data = '{"contentType":"' + CHAT_CONTENT_TYPES.TEXT +
+                   '","message":"Are you there?","receivedTimestamp": "2015-06-25T00:29:14.197Z"}';
+        var clock = sinon.useFakeTimers();
 
         subscriber._.getDataChannel.callsArgWith(2, null, fakeChannel);
 
         session.trigger("signal:readyForDataChannel");
 
         // Now send the message.
         fakeChannel.trigger("message", {
-          data: '{"contentType":"' + CHAT_CONTENT_TYPES.TEXT + '","message":"Are you there?"}'
+          data: data
         });
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.ReceivedTextChatMessage({
             contentType: CHAT_CONTENT_TYPES.TEXT,
-            message: "Are you there?"
+            message: "Are you there?",
+            receivedTimestamp: "1970-01-01T00:00:00.000Z"
           }));
+
+        /* Restore the time. */
+        clock.restore();
       });
     });
 
     describe("exception", function() {
       describe("Unable to publish (GetUserMedia)", function() {
         it("should destroy the publisher", function() {
           sdk.trigger("exception", {
             code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
--- a/browser/components/loop/test/shared/textChatStore_test.js
+++ b/browser/components/loop/test/shared/textChatStore_test.js
@@ -65,24 +65,29 @@ describe("loop.store.TextChatStore", fun
   });
 
   describe("#receivedTextChatMessage", function() {
     it("should add the message to the list", function() {
       var message = "Hello!";
 
       store.receivedTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
-        message: message
+        message: message,
+        extraData: undefined,
+        sentTimestamp: "2015-06-24T23:58:53.848Z",
+        receivedTimestamp: "1970-01-01T00:00:00.000Z"
       });
 
       expect(store.getStoreState("messageList")).eql([{
         type: CHAT_MESSAGE_TYPES.RECEIVED,
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: message,
-        extraData: undefined
+        extraData: undefined,
+        sentTimestamp: "2015-06-24T23:58:53.848Z",
+        receivedTimestamp: "1970-01-01T00:00:00.000Z"
       }]);
     });
 
     it("should not add messages for unknown content types", function() {
       store.receivedTextChatMessage({
         contentType: "invalid type",
         message: "Hi"
       });
@@ -113,26 +118,30 @@ describe("loop.store.TextChatStore", fun
 
       sinon.assert.calledOnce(fakeSdkDriver.sendTextChatMessage);
       sinon.assert.calledWithExactly(fakeSdkDriver.sendTextChatMessage, messageData);
     });
 
     it("should add the message to the list", function() {
       var messageData = {
         contentType: CHAT_CONTENT_TYPES.TEXT,
-        message: "It's awesome!"
+        message: "It's awesome!",
+        sentTimestamp: "2015-06-24T23:58:53.848Z",
+        receivedTimestamp: "2015-06-24T23:58:53.848Z"
       };
 
       store.sendTextChatMessage(messageData);
 
       expect(store.getStoreState("messageList")).eql([{
         type: CHAT_MESSAGE_TYPES.SENT,
         contentType: messageData.contentType,
         message: messageData.message,
-        extraData: undefined
+        extraData: undefined,
+        sentTimestamp: "2015-06-24T23:58:53.848Z",
+        receivedTimestamp: "2015-06-24T23:58:53.848Z"
       }]);
     });
 
     it("should dipatch a LoopChatMessageAppended event", function() {
       store.sendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: "Hello!"
       });
@@ -150,17 +159,19 @@ describe("loop.store.TextChatStore", fun
         roomOwner: "Mark",
         roomUrl: "fake"
       }));
 
       expect(store.getStoreState("messageList")).eql([{
         type: CHAT_MESSAGE_TYPES.SPECIAL,
         contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
         message: "Let's share!",
-        extraData: undefined
+        extraData: undefined,
+        sentTimestamp: undefined,
+        receivedTimestamp: undefined
       }]);
     });
 
     it("should add the context to the list", function() {
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
         roomName: "Let's share!",
         roomOwner: "Mark",
         roomUrl: "fake",
@@ -171,21 +182,25 @@ describe("loop.store.TextChatStore", fun
         }]
       }));
 
       expect(store.getStoreState("messageList")).eql([
         {
           type: CHAT_MESSAGE_TYPES.SPECIAL,
           contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
           message: "Let's share!",
-          extraData: undefined
+          extraData: undefined,
+          sentTimestamp: undefined,
+          receivedTimestamp: undefined
         }, {
           type: CHAT_MESSAGE_TYPES.SPECIAL,
           contentType: CHAT_CONTENT_TYPES.CONTEXT,
           message: "A wonderful event",
+          sentTimestamp: undefined,
+          receivedTimestamp: undefined,
           extraData: {
             location: "http://wonderful.invalid",
             thumbnail: "fake"
           }
         }
       ]);
     });
 
--- a/browser/components/loop/test/shared/textChatView_test.js
+++ b/browser/components/loop/test/shared/textChatView_test.js
@@ -6,21 +6,21 @@ describe("loop.shared.views.TextChatView
 
   var expect = chai.expect;
   var sharedActions = loop.shared.actions;
   var sharedViews = loop.shared.views;
   var TestUtils = React.addons.TestUtils;
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
   var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
 
-  var dispatcher, fakeSdkDriver, sandbox, store;
+  var dispatcher, fakeSdkDriver, sandbox, store, fakeClock;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
-    sandbox.useFakeTimers();
+    fakeClock = sandbox.useFakeTimers();
 
     dispatcher = new loop.Dispatcher();
     sandbox.stub(dispatcher, "dispatch");
 
     fakeSdkDriver = {
       sendTextChatMessage: sinon.stub()
     };
 
@@ -32,31 +32,267 @@ describe("loop.shared.views.TextChatView
       textChatStore: store
     });
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
+  describe("TextChatEntriesView", function() {
+    var view;
+
+    function mountTestComponent(extraProps) {
+      var basicProps = {
+        dispatcher: dispatcher,
+        messageList: []
+      };
+
+      return TestUtils.renderIntoDocument(
+        React.createElement(loop.shared.views.chat.TextChatEntriesView,
+          _.extend(basicProps, extraProps)));
+    }
+
+    it("should render message entries when message were sent/ received", function() {
+      view = mountTestComponent({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.RECEIVED,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Hello!"
+        }, {
+          type: CHAT_MESSAGE_TYPES.SENT,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Is it me you're looking for?"
+        }]
+      });
+
+      var node = view.getDOMNode();
+      expect(node).to.not.eql(null);
+
+      var entries = node.querySelectorAll(".text-chat-entry");
+      expect(entries.length).to.eql(2);
+      expect(entries[0].classList.contains("received")).to.eql(true);
+      expect(entries[1].classList.contains("received")).to.not.eql(true);
+    });
+
+    it("should play a sound when a message is received", function() {
+      view = mountTestComponent();
+      sandbox.stub(view, "play");
+
+      view.setProps({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.RECEIVED,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Hello!"
+        }]
+      });
+
+      sinon.assert.calledOnce(view.play);
+      sinon.assert.calledWithExactly(view.play, "message");
+    });
+
+    it("should not play a sound when a special message is displayed", function() {
+      view = mountTestComponent();
+      sandbox.stub(view, "play");
+
+      view.setProps({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.SPECIAL,
+          contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
+          message: "Hello!"
+        }]
+      });
+
+      sinon.assert.notCalled(view.play);
+    });
+
+    it("should not play a sound when a message is sent", function() {
+      view = mountTestComponent();
+      sandbox.stub(view, "play");
+
+      view.setProps({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.SENT,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Hello!"
+        }]
+      });
+
+      sinon.assert.notCalled(view.play);
+    });
+  });
+
+  describe("TextChatEntry", function() {
+    var view;
+
+    function mountTestComponent(extraProps) {
+      var props = _.extend({
+        dispatcher: dispatcher
+      }, extraProps);
+      return TestUtils.renderIntoDocument(
+        React.createElement(loop.shared.views.chat.TextChatEntry, props));
+    }
+
+    it("should not render a timestamp", function() {
+      view = mountTestComponent({
+        showTimestamp: false,
+        timestamp: "2015-06-23T22:48:39.738Z"
+      });
+      var node = view.getDOMNode();
+
+      expect(node.querySelector(".text-chat-entry-timestamp")).to.eql(null);
+    });
+
+    it("should render a timestamp", function() {
+      view = mountTestComponent({
+        showTimestamp: true,
+        timestamp: "2015-06-23T22:48:39.738Z"
+      });
+      var node = view.getDOMNode();
+
+      expect(node.querySelector(".text-chat-entry-timestamp")).to.not.eql(null);
+    });
+  });
+
+  describe("TextChatEntriesView", function() {
+    var view, node;
+
+    function mountTestComponent(extraProps) {
+      var props = _.extend({
+        dispatcher: dispatcher
+      }, extraProps);
+      return TestUtils.renderIntoDocument(
+        React.createElement(loop.shared.views.chat.TextChatEntriesView, props));
+    }
+
+    beforeEach(function() {
+      store.setStoreState({ textChatEnabled: true });
+    });
+
+    it("should show timestamps if there are different senders", function() {
+      view = mountTestComponent({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.RECEIVED,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Hello!"
+        }, {
+          type: CHAT_MESSAGE_TYPES.SENT,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Is it me you're looking for?"
+        }]
+      });
+      node = view.getDOMNode();
+
+      expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
+          .to.eql(2);
+    });
+
+    it("should show timestamps if they are 1 minute apart (SENT)", function() {
+      view = mountTestComponent({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.SENT,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Hello!",
+          sentTimestamp: "2015-06-25T17:53:55.357Z"
+        }, {
+          type: CHAT_MESSAGE_TYPES.SENT,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Is it me you're looking for?",
+          sentTimestamp: "2015-06-25T17:54:55.357Z"
+        }]
+      });
+      node = view.getDOMNode();
+
+      expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
+          .to.eql(2);
+    });
+
+    it("should show timestamps if they are 1 minute apart (RECV)", function() {
+      view = mountTestComponent({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.RECEIVED,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Hello!",
+          receivedTimestamp: "2015-06-25T17:53:55.357Z"
+        }, {
+          type: CHAT_MESSAGE_TYPES.RECEIVED,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Is it me you're looking for?",
+          receivedTimestamp: "2015-06-25T17:54:55.357Z"
+        }]
+      });
+      node = view.getDOMNode();
+
+      expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
+          .to.eql(2);
+    });
+
+    it("should not show timestamps from msgs sent in the same minute", function() {
+      view = mountTestComponent({
+        messageList: [{
+          type: CHAT_MESSAGE_TYPES.RECEIVED,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Hello!"
+        }, {
+          type: CHAT_MESSAGE_TYPES.RECEIVED,
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Is it me you're looking for?"
+        }]
+      });
+      node = view.getDOMNode();
+
+      expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
+          .to.eql(1);
+    });
+  });
+
   describe("TextChatView", function() {
     var view;
 
     function mountTestComponent(extraProps) {
       var props = _.extend({
         dispatcher: dispatcher
       }, extraProps);
       return TestUtils.renderIntoDocument(
-        React.createElement(loop.shared.views.TextChatView, props));
+        React.createElement(loop.shared.views.chat.TextChatView, props));
     }
 
     beforeEach(function() {
       store.setStoreState({ textChatEnabled: true });
     });
 
+    it("should show timestamps from msgs sent more than 1 min apart", function() {
+      view = mountTestComponent();
+
+      store.sendTextChatMessage({
+        contentType: CHAT_CONTENT_TYPES.TEXT,
+        message: "Hello!",
+        sentTimestamp: "1970-01-01T00:02:00.000Z",
+        receivedTimestamp: "1970-01-01T00:02:00.000Z"
+      });
+
+      store.sendTextChatMessage({
+        contentType: CHAT_CONTENT_TYPES.TEXT,
+        message: "Is it me you're looking for?",
+        sentTimestamp: "1970-01-01T00:03:00.000Z",
+        receivedTimestamp: "1970-01-01T00:03:00.000Z"
+      });
+      store.sendTextChatMessage({
+        contentType: CHAT_CONTENT_TYPES.TEXT,
+        message: "Is it me you're looking for?",
+        sentTimestamp: "1970-01-01T00:02:00.000Z",
+        receivedTimestamp: "1970-01-01T00:02:00.000Z"
+      });
+
+      var node = view.getDOMNode();
+
+      expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
+          .to.eql(2);
+    });
+
     it("should not display anything if no messages and text chat not enabled and showAlways is false", function() {
       store.setStoreState({ textChatEnabled: false });
 
       view = mountTestComponent({
         showAlways: false
       });
 
       expect(view.getDOMNode()).eql(null);
@@ -105,16 +341,41 @@ describe("loop.shared.views.TextChatView
       expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
 
       var entries = node.querySelectorAll(".text-chat-entry");
       expect(entries.length).to.eql(2);
       expect(entries[0].classList.contains("received")).to.eql(true);
       expect(entries[1].classList.contains("received")).to.not.eql(true);
     });
 
+    it("should add `sent` CSS class selector to msg of type SENT", function() {
+      var node = mountTestComponent().getDOMNode();
+
+      store.sendTextChatMessage({
+        contentType: CHAT_CONTENT_TYPES.TEXT,
+        message: "Foo",
+        timestamp: 0
+      });
+
+      expect(node.querySelector(".sent")).to.not.eql(null);
+    });
+
+    it("should add `received` CSS class selector to msg of type RECEIVED",
+       function() {
+         var node = mountTestComponent().getDOMNode();
+
+         store.receivedTextChatMessage({
+           contentType: CHAT_CONTENT_TYPES.TEXT,
+           message: "Foo",
+           timestamp: 0
+         });
+
+         expect(node.querySelector(".received")).to.not.eql(null);
+     });
+
     it("should render a room name special entry", function() {
       view = mountTestComponent({
         showRoomName: true
       });
 
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
         roomName: "A wonderful surprise!",
         roomOwner: "Chris",
@@ -166,17 +427,18 @@ describe("loop.shared.views.TextChatView
         key: "Enter",
         which: 13
       });
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWithExactly(dispatcher.dispatch,
         new sharedActions.SendTextChatMessage({
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Hello!"
+          message: "Hello!",
+          sentTimestamp: "1970-01-01T00:00:00.000Z"
         }));
     });
 
     it("should not dispatch SendTextChatMessage when the message is empty", function() {
       view = mountTestComponent();
 
       var entryNode = view.getDOMNode().querySelector(".text-chat-box > form > input");
 
--- a/browser/components/loop/ui/fake-l10n.js
+++ b/browser/components/loop/ui/fake-l10n.js
@@ -17,10 +17,15 @@ navigator.mozL10n = document.mozL10n = {
 
     // upcase the first letter
     var readableStringId = stringId.replace(/^./, function(match) {
       "use strict";
       return match.toUpperCase();
     }).replace(/_/g, " ");  // and convert _ chars to spaces
 
     return "" + readableStringId + (vars ? ";" + JSON.stringify(vars) : "");
+  },
+
+  /* For timestamp formatting reasons. */
+  language: {
+    code: "en-US"
   }
 };
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -29,17 +29,17 @@
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
   var StandaloneRoomView      = loop.standaloneRoomViews.StandaloneRoomView;
 
   // 3. Shared components
   var ConversationToolbar = loop.shared.views.ConversationToolbar;
   var FeedbackView = loop.shared.views.FeedbackView;
   var Checkbox = loop.shared.views.Checkbox;
-  var TextChatView = loop.shared.views.TextChatView;
+  var TextChatView = loop.shared.views.chat.TextChatView;
 
   // Store constants
   var ROOM_STATES = loop.store.ROOM_STATES;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
 
   // Local helpers
   function returnTrue() {
@@ -82,17 +82,17 @@
     "https://input.allizom.org/api/v1/feedback", {
       product: "Loop"
     }
   );
 
   var mockSDK = _.extend({
     sendTextChatMessage: function(message) {
       dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
-        message: message
+        message: message.message
       }));
     }
   }, Backbone.Events);
 
   /**
    * Every view that uses an activeRoomStore needs its own; if they shared
    * an active store, they'd interfere with each other.
    *
@@ -304,40 +304,56 @@
       // use the fallback thumbnail
     }]
   }));
 
   textChatStore.setStoreState({textChatEnabled: true});
 
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "Rheet!"
+    message: "Rheet!",
+    sentTimestamp: "2015-06-23T22:21:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "Hi there"
+    message: "Hi there",
+    receivedTimestamp: "2015-06-23T22:21:45.590Z"
+  }));
+  dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Hello",
+    receivedTimestamp: "2015-06-23T23:24:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
     message: "Check out this menu from DNA Pizza:" +
     " http://example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/" +
-    "%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%"
+    "%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%",
+    sentTimestamp: "2015-06-23T22:23:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
     message: "Nowforareallylongwordwithoutspacesorpunctuationwhichshouldcause" +
-    "linewrappingissuesifthecssiswrong"
+    "linewrappingissuesifthecssiswrong",
+    sentTimestamp: "2015-06-23T22:23:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "That avocado monkey-brains pie sounds tasty!"
+    message: "That avocado monkey-brains pie sounds tasty!",
+    receivedTimestamp: "2015-06-23T22:25:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "What time should we meet?"
+    message: "What time should we meet?",
+    sentTimestamp: "2015-06-23T22:27:45.590Z"
+  }));
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Cool",
+    sentTimestamp: "2015-06-23T22:27:45.590Z"
   }));
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStore,
     feedbackStore: feedbackStore,
     textChatStore: textChatStore
   });
@@ -376,28 +392,37 @@
   errNotifications.add({
     level: "error",
     message: "Could Not Authenticate",
     details: "Did you change your password?",
     detailsButtonLabel: "Retry"
   });
 
   var SVGIcon = React.createClass({displayName: "SVGIcon",
+    propTypes: {
+      shapeId: React.PropTypes.string.isRequired,
+      size: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       var sizeUnit = this.props.size.split("x");
       return (
         React.createElement("img", {className: "svg-icon", 
              height: sizeUnit[1], 
              src: "../content/shared/img/icons-" + this.props.size + ".svg#" + this.props.shapeId, 
              width: sizeUnit[0]})
       );
     }
   });
 
   var SVGIcons = React.createClass({displayName: "SVGIcons",
+    propTypes: {
+      size: React.PropTypes.string.isRequired
+    },
+
     shapes: {
       "10x10": ["close", "close-active", "close-disabled", "dropdown",
         "dropdown-white", "dropdown-active", "dropdown-disabled", "edit",
         "edit-active", "edit-disabled", "expand", "expand-active", "expand-disabled",
         "minimize", "minimize-active", "minimize-disabled"
       ],
       "14x14": ["audio", "audio-active", "audio-disabled", "facemute",
         "facemute-active", "facemute-disabled", "hangup", "hangup-active",
@@ -430,20 +455,22 @@
       return (
         React.createElement("ul", {className: "svg-icon-list"}, icons)
       );
     }
   });
 
   var FramedExample = React.createClass({displayName: "FramedExample",
     propTypes: {
+      children: React.PropTypes.element,
       cssClass: React.PropTypes.string,
       dashed: React.PropTypes.bool,
       height: React.PropTypes.number,
       onContentsRendered: React.PropTypes.func,
+      summary: React.PropTypes.string.isRequired,
       width: React.PropTypes.number
     },
 
     makeId: function(prefix) {
       return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
     },
 
     render: function() {
@@ -473,16 +500,26 @@
             )
           )
         )
       );
     }
   });
 
   var Example = React.createClass({displayName: "Example",
+    propTypes: {
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.element,
+        React.PropTypes.arrayOf(React.PropTypes.element)
+      ]).isRequired,
+      dashed: React.PropTypes.bool,
+      style: React.PropTypes.object,
+      summary: React.PropTypes.string.isRequired
+    },
+
     makeId: function(prefix) {
       return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
     },
 
     render: function() {
       var cx = React.addons.classSet;
       return (
         React.createElement("div", {className: "example"}, 
@@ -495,27 +532,40 @@
             this.props.children
           )
         )
       );
     }
   });
 
   var Section = React.createClass({displayName: "Section",
+    propTypes: {
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.arrayOf(React.PropTypes.element),
+        React.PropTypes.element
+      ]).isRequired,
+      className: React.PropTypes.string,
+      name: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       return (
         React.createElement("section", {className: this.props.className, id: this.props.name}, 
           React.createElement("h1", null, this.props.name), 
           this.props.children
         )
       );
     }
   });
 
   var ShowCase = React.createClass({displayName: "ShowCase",
+    propTypes: {
+      children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired
+    },
+
     getInitialState: function() {
       // We assume for now that rtl is the only query parameter.
       //
       // Note: this check is repeated in react-frame-component to save passing
       // rtlMode down the props tree.
       var rtlMode = document.location.search === "?rtl=1";
 
       return {
@@ -567,94 +617,94 @@
 
     render: function() {
       return (
         React.createElement(ShowCase, null, 
           React.createElement(Section, {name: "PanelView"}, 
             React.createElement("p", {className: "note"}, 
               React.createElement("strong", null, "Note:"), " 332px wide."
             ), 
-            React.createElement(Example, {dashed: "true", style: {width: "332px"}, summary: "Re-sign-in view"}, 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Re-sign-in view"}, 
               React.createElement(SignInRequestView, {mozLoop: mockMozLoopRooms})
             ), 
-            React.createElement(Example, {dashed: "true", style: {width: "332px"}, summary: "Room list tab"}, 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Room list tab"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: mockMozLoopRooms, 
                          notifications: notifications, 
                          roomStore: roomStore, 
                          selectedTab: "rooms", 
                          userProfile: {email: "test@example.com"}})
             ), 
-            React.createElement(Example, {dashed: "true", style: {width: "332px"}, summary: "Contact list tab"}, 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: mockMozLoopRooms, 
                          notifications: notifications, 
                          roomStore: roomStore, 
                          selectedTab: "contacts", 
                          userProfile: {email: "test@example.com"}})
             ), 
-            React.createElement(Example, {dashed: "true", style: {width: "332px"}, summary: "Error Notification"}, 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: navigator.mozLoop, 
                          notifications: errNotifications, 
                          roomStore: roomStore})
             ), 
-            React.createElement(Example, {dashed: "true", style: {width: "332px"}, summary: "Error Notification - authenticated"}, 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification - authenticated"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: navigator.mozLoop, 
                          notifications: errNotifications, 
                          roomStore: roomStore, 
                          userProfile: {email: "test@example.com"}})
             ), 
-            React.createElement(Example, {dashed: "true", style: {width: "332px"}, summary: "Contact import success"}, 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact import success"}, 
               React.createElement(PanelView, {dispatcher: dispatcher, 
                          mozLoop: mockMozLoopRooms, 
                          notifications: new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}]), 
                          roomStore: roomStore, 
                          selectedTab: "contacts", 
                          userProfile: {email: "test@example.com"}})
             ), 
-            React.createElement(Example, {dashed: "true", style: {width: "332px"}, summary: "Contact import error"}, 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact import error"}, 
               React.createElement(PanelView, {dispatcher: dispatcher, 
                          mozLoop: mockMozLoopRooms, 
                          notifications: new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}]), 
                          roomStore: roomStore, 
                          selectedTab: "contacts", 
                          userProfile: {email: "test@example.com"}})
             )
           ), 
 
           React.createElement(Section, {name: "AcceptCallView"}, 
-            React.createElement(Example, {dashed: "true", style: {width: "300px", height: "272px"}, 
+            React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default / incoming video call"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
                                 mozLoop: mockMozLoopRooms})
               )
             ), 
 
-            React.createElement(Example, {dashed: "true", style: {width: "300px", height: "272px"}, 
+            React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default / incoming audio only call"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_ONLY, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
                                 mozLoop: mockMozLoopRooms})
               )
             )
           ), 
 
           React.createElement(Section, {name: "AcceptCallView-ActiveState"}, 
-            React.createElement(Example, {dashed: "true", style: {width: "300px", height: "272px"}, 
+            React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
                                 mozLoop: mockMozLoopRooms, 
                                 showMenu: true})
               )
@@ -703,47 +753,47 @@
                                      hangup: noop, 
                                      publishStream: noop, 
                                      video: {enabled: true}})
               )
             )
           ), 
 
           React.createElement(Section, {name: "PendingConversationView (Desktop)"}, 
-            React.createElement(Example, {dashed: "true", 
+            React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Connecting"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopPendingConversationView, {callState: "gather", 
                                                 contact: mockContact, 
                                                 dispatcher: dispatcher})
               )
             )
           ), 
 
           React.createElement(Section, {name: "CallFailedView"}, 
-            React.createElement(Example, {dashed: "true", 
+            React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Call Failed - Incoming"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(CallFailedView, {dispatcher: dispatcher, 
                                 outgoing: false, 
                                 store: conversationStore})
               )
             ), 
-            React.createElement(Example, {dashed: "true", 
+            React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Call Failed - Outgoing"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(CallFailedView, {dispatcher: dispatcher, 
                                 outgoing: true, 
                                 store: conversationStore})
               )
             ), 
-            React.createElement(Example, {dashed: "true", 
+            React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Call Failed — with call URL error"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(CallFailedView, {dispatcher: dispatcher, emailLinkError: true, 
                                 outgoing: true, 
                                 store: conversationStore})
               )
             )
@@ -810,27 +860,27 @@
 
           ), 
 
           React.createElement(Section, {name: "FeedbackView"}, 
             React.createElement("p", {className: "note"}, 
               React.createElement("strong", null, "Note:"), " For the useable demo, you can access submitted data at ", 
               React.createElement("a", {href: "https://input.allizom.org/"}, "input.allizom.org"), "."
             ), 
-            React.createElement(Example, {dashed: "true", 
+            React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Default (useable demo)"}, 
               React.createElement(FeedbackView, {feedbackStore: feedbackStore})
             ), 
-            React.createElement(Example, {dashed: "true", 
+            React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Detailed form"}, 
               React.createElement(FeedbackView, {feedbackState: FEEDBACK_STATES.DETAILS, feedbackStore: feedbackStore})
             ), 
-            React.createElement(Example, {dashed: "true", 
+            React.createElement(Example, {dashed: true, 
                      style: {width: "300px", height: "272px"}, 
                      summary: "Thank you!"}, 
               React.createElement(FeedbackView, {feedbackState: FEEDBACK_STATES.SENT, feedbackStore: feedbackStore})
             )
           ), 
 
           React.createElement(Section, {name: "AlertMessages"}, 
             React.createElement(Example, {summary: "Various alerts"}, 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -29,17 +29,17 @@
   var UnsupportedBrowserView  = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView   = loop.webapp.UnsupportedDeviceView;
   var StandaloneRoomView      = loop.standaloneRoomViews.StandaloneRoomView;
 
   // 3. Shared components
   var ConversationToolbar = loop.shared.views.ConversationToolbar;
   var FeedbackView = loop.shared.views.FeedbackView;
   var Checkbox = loop.shared.views.Checkbox;
-  var TextChatView = loop.shared.views.TextChatView;
+  var TextChatView = loop.shared.views.chat.TextChatView;
 
   // Store constants
   var ROOM_STATES = loop.store.ROOM_STATES;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
 
   // Local helpers
   function returnTrue() {
@@ -82,17 +82,17 @@
     "https://input.allizom.org/api/v1/feedback", {
       product: "Loop"
     }
   );
 
   var mockSDK = _.extend({
     sendTextChatMessage: function(message) {
       dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
-        message: message
+        message: message.message
       }));
     }
   }, Backbone.Events);
 
   /**
    * Every view that uses an activeRoomStore needs its own; if they shared
    * an active store, they'd interfere with each other.
    *
@@ -304,40 +304,56 @@
       // use the fallback thumbnail
     }]
   }));
 
   textChatStore.setStoreState({textChatEnabled: true});
 
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "Rheet!"
+    message: "Rheet!",
+    sentTimestamp: "2015-06-23T22:21:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "Hi there"
+    message: "Hi there",
+    receivedTimestamp: "2015-06-23T22:21:45.590Z"
+  }));
+  dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Hello",
+    receivedTimestamp: "2015-06-23T23:24:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
     message: "Check out this menu from DNA Pizza:" +
     " http://example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/" +
-    "%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%"
+    "%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%",
+    sentTimestamp: "2015-06-23T22:23:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
     message: "Nowforareallylongwordwithoutspacesorpunctuationwhichshouldcause" +
-    "linewrappingissuesifthecssiswrong"
+    "linewrappingissuesifthecssiswrong",
+    sentTimestamp: "2015-06-23T22:23:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "That avocado monkey-brains pie sounds tasty!"
+    message: "That avocado monkey-brains pie sounds tasty!",
+    receivedTimestamp: "2015-06-23T22:25:45.590Z"
   }));
   dispatcher.dispatch(new sharedActions.SendTextChatMessage({
     contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
-    message: "What time should we meet?"
+    message: "What time should we meet?",
+    sentTimestamp: "2015-06-23T22:27:45.590Z"
+  }));
+  dispatcher.dispatch(new sharedActions.SendTextChatMessage({
+    contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
+    message: "Cool",
+    sentTimestamp: "2015-06-23T22:27:45.590Z"
   }));
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStore,
     feedbackStore: feedbackStore,
     textChatStore: textChatStore
   });
@@ -376,28 +392,37 @@
   errNotifications.add({
     level: "error",
     message: "Could Not Authenticate",
     details: "Did you change your password?",
     detailsButtonLabel: "Retry"
   });
 
   var SVGIcon = React.createClass({
+    propTypes: {
+      shapeId: React.PropTypes.string.isRequired,
+      size: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       var sizeUnit = this.props.size.split("x");
       return (
         <img className="svg-icon"
              height={sizeUnit[1]}
              src={"../content/shared/img/icons-" + this.props.size + ".svg#" + this.props.shapeId}
              width={sizeUnit[0]} />
       );
     }
   });
 
   var SVGIcons = React.createClass({
+    propTypes: {
+      size: React.PropTypes.string.isRequired
+    },
+
     shapes: {
       "10x10": ["close", "close-active", "close-disabled", "dropdown",
         "dropdown-white", "dropdown-active", "dropdown-disabled", "edit",
         "edit-active", "edit-disabled", "expand", "expand-active", "expand-disabled",
         "minimize", "minimize-active", "minimize-disabled"
       ],
       "14x14": ["audio", "audio-active", "audio-disabled", "facemute",
         "facemute-active", "facemute-disabled", "hangup", "hangup-active",
@@ -430,20 +455,22 @@
       return (
         <ul className="svg-icon-list">{icons}</ul>
       );
     }
   });
 
   var FramedExample = React.createClass({
     propTypes: {
+      children: React.PropTypes.element,
       cssClass: React.PropTypes.string,
       dashed: React.PropTypes.bool,
       height: React.PropTypes.number,
       onContentsRendered: React.PropTypes.func,
+      summary: React.PropTypes.string.isRequired,
       width: React.PropTypes.number
     },
 
     makeId: function(prefix) {
       return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
     },
 
     render: function() {
@@ -473,16 +500,26 @@
             </Frame>
           </div>
         </div>
       );
     }
   });
 
   var Example = React.createClass({
+    propTypes: {
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.element,
+        React.PropTypes.arrayOf(React.PropTypes.element)
+      ]).isRequired,
+      dashed: React.PropTypes.bool,
+      style: React.PropTypes.object,
+      summary: React.PropTypes.string.isRequired
+    },
+
     makeId: function(prefix) {
       return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
     },
 
     render: function() {
       var cx = React.addons.classSet;
       return (
         <div className="example">
@@ -495,27 +532,40 @@
             {this.props.children}
           </div>
         </div>
       );
     }
   });
 
   var Section = React.createClass({
+    propTypes: {
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.arrayOf(React.PropTypes.element),
+        React.PropTypes.element
+      ]).isRequired,
+      className: React.PropTypes.string,
+      name: React.PropTypes.string.isRequired
+    },
+
     render: function() {
       return (
         <section className={this.props.className} id={this.props.name}>
           <h1>{this.props.name}</h1>
           {this.props.children}
         </section>
       );
     }
   });
 
   var ShowCase = React.createClass({
+    propTypes: {
+      children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired
+    },
+
     getInitialState: function() {
       // We assume for now that rtl is the only query parameter.
       //
       // Note: this check is repeated in react-frame-component to save passing
       // rtlMode down the props tree.
       var rtlMode = document.location.search === "?rtl=1";
 
       return {
@@ -567,94 +617,94 @@
 
     render: function() {
       return (
         <ShowCase>
           <Section name="PanelView">
             <p className="note">
               <strong>Note:</strong> 332px wide.
             </p>
-            <Example dashed="true" style={{width: "332px"}} summary="Re-sign-in view">
+            <Example dashed={true} style={{width: "332px"}} summary="Re-sign-in view">
               <SignInRequestView mozLoop={mockMozLoopRooms} />
             </Example>
-            <Example dashed="true" style={{width: "332px"}} summary="Room list tab">
+            <Example dashed={true} style={{width: "332px"}} summary="Room list tab">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={mockMozLoopRooms}
                          notifications={notifications}
                          roomStore={roomStore}
                          selectedTab="rooms"
                          userProfile={{email: "test@example.com"}} />
             </Example>
-            <Example dashed="true" style={{width: "332px"}} summary="Contact list tab">
+            <Example dashed={true} style={{width: "332px"}} summary="Contact list tab">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={mockMozLoopRooms}
                          notifications={notifications}
                          roomStore={roomStore}
                          selectedTab="contacts"
                          userProfile={{email: "test@example.com"}} />
             </Example>
-            <Example dashed="true" style={{width: "332px"}} summary="Error Notification">
+            <Example dashed={true} style={{width: "332px"}} summary="Error Notification">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={navigator.mozLoop}
                          notifications={errNotifications}
                          roomStore={roomStore} />
             </Example>
-            <Example dashed="true" style={{width: "332px"}} summary="Error Notification - authenticated">
+            <Example dashed={true} style={{width: "332px"}} summary="Error Notification - authenticated">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={navigator.mozLoop}
                          notifications={errNotifications}
                          roomStore={roomStore}
                          userProfile={{email: "test@example.com"}} />
             </Example>
-            <Example dashed="true" style={{width: "332px"}} summary="Contact import success">
+            <Example dashed={true} style={{width: "332px"}} summary="Contact import success">
               <PanelView dispatcher={dispatcher}
                          mozLoop={mockMozLoopRooms}
                          notifications={new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}])}
                          roomStore={roomStore}
                          selectedTab="contacts"
                          userProfile={{email: "test@example.com"}} />
             </Example>
-            <Example dashed="true" style={{width: "332px"}} summary="Contact import error">
+            <Example dashed={true} style={{width: "332px"}} summary="Contact import error">
               <PanelView dispatcher={dispatcher}
                          mozLoop={mockMozLoopRooms}
                          notifications={new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}])}
                          roomStore={roomStore}
                          selectedTab="contacts"
                          userProfile={{email: "test@example.com"}} />
             </Example>
           </Section>
 
           <Section name="AcceptCallView">
-            <Example dashed="true" style={{width: "300px", height: "272px"}}
+            <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default / incoming video call">
               <div className="fx-embedded">
                 <AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
                                 mozLoop={mockMozLoopRooms} />
               </div>
             </Example>
 
-            <Example dashed="true" style={{width: "300px", height: "272px"}}
+            <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default / incoming audio only call">
               <div className="fx-embedded">
                 <AcceptCallView callType={CALL_TYPES.AUDIO_ONLY}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
                                 mozLoop={mockMozLoopRooms} />
               </div>
             </Example>
           </Section>
 
           <Section name="AcceptCallView-ActiveState">
-            <Example dashed="true" style={{width: "300px", height: "272px"}}
+            <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default">
               <div className="fx-embedded" >
                 <AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
                                 mozLoop={mockMozLoopRooms}
                                 showMenu={true} />
               </div>
@@ -703,47 +753,47 @@
                                      hangup={noop}
                                      publishStream={noop}
                                      video={{enabled: true}} />
               </Example>
             </div>
           </Section>
 
           <Section name="PendingConversationView (Desktop)">
-            <Example dashed="true"
+            <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Connecting">
               <div className="fx-embedded">
                 <DesktopPendingConversationView callState={"gather"}
                                                 contact={mockContact}
                                                 dispatcher={dispatcher} />
               </div>
             </Example>
           </Section>
 
           <Section name="CallFailedView">
-            <Example dashed="true"
+            <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Call Failed - Incoming">
               <div className="fx-embedded">
                 <CallFailedView dispatcher={dispatcher}
                                 outgoing={false}
                                 store={conversationStore} />
               </div>
             </Example>
-            <Example dashed="true"
+            <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Call Failed - Outgoing">
               <div className="fx-embedded">
                 <CallFailedView dispatcher={dispatcher}
                                 outgoing={true}
                                 store={conversationStore} />
               </div>
             </Example>
-            <Example dashed="true"
+            <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Call Failed — with call URL error">
               <div className="fx-embedded">
                 <CallFailedView dispatcher={dispatcher} emailLinkError={true}
                                 outgoing={true}
                                 store={conversationStore} />
               </div>
             </Example>
@@ -810,27 +860,27 @@
 
           </Section>
 
           <Section name="FeedbackView">
             <p className="note">
               <strong>Note:</strong> For the useable demo, you can access submitted data at&nbsp;
               <a href="https://input.allizom.org/">input.allizom.org</a>.
             </p>
-            <Example dashed="true"
+            <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Default (useable demo)">
               <FeedbackView feedbackStore={feedbackStore} />
             </Example>
-            <Example dashed="true"
+            <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Detailed form">
               <FeedbackView feedbackState={FEEDBACK_STATES.DETAILS} feedbackStore={feedbackStore} />
             </Example>
-            <Example dashed="true"
+            <Example dashed={true}
                      style={{width: "300px", height: "272px"}}
                      summary="Thank you!">
               <FeedbackView feedbackState={FEEDBACK_STATES.SENT} feedbackStore={feedbackStore}/>
             </Example>
           </Section>
 
           <Section name="AlertMessages">
             <Example summary="Various alerts">
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -1,12 +1,14 @@
 /* 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/. */
 
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
 var gPrivacyPane = {
 
   /**
    * Whether the use has selected the auto-start private browsing mode in the UI.
    */
   _autoStartPrivateBrowsing: false,
 
   /**
@@ -98,16 +100,18 @@ var gPrivacyPane = {
     setEventListener("privateBrowsingAutoStart", "command",
                      gPrivacyPane.updateAutostart);
     setEventListener("cookieExceptions", "command",
                      gPrivacyPane.showCookieExceptions);
     setEventListener("showCookiesButton", "command",
                      gPrivacyPane.showCookies);
     setEventListener("clearDataSettings", "command",
                      gPrivacyPane.showClearPrivateDataSettings);
+
+    document.getElementById("searchesSuggestion").hidden = !AppConstants.NIGHTLY_BUILD;
   },
 
   // HISTORY MODE
 
   /**
    * The list of preferences which affect the initial history mode settings.
    * If the auto start private browsing mode pref is active, the initial
    * history mode would be set to "Don't remember anything".
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -253,12 +253,13 @@
             onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
             accesskey="&locbar.bookmarks.accesskey;"
             preference="browser.urlbar.suggest.bookmark"/>
   <checkbox id="openpageSuggestion" label="&locbar.openpage.label;"
             onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
             accesskey="&locbar.openpage.accesskey;"
             preference="browser.urlbar.suggest.openpage"/>
   <checkbox id="searchesSuggestion" label="&locbar.searches.label;"
+            hidden="true"
             onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
             accesskey="&locbar.searches.accesskey;"
             preference="browser.urlbar.suggest.searches"/>
 </groupbox>
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -124,16 +124,35 @@ let gSyncPane = {
       return Services.strings.createBundle("chrome://browser/locale/accounts.properties");
     }),
 
     this.updateWeavePrefs();
 
     this._initProfileImageUI();
   },
 
+  _toggleComputerNameControls: function(editMode) {
+    let textbox = document.getElementById("fxaSyncComputerName");
+    textbox.className = editMode ? "" : "plain";
+    textbox.disabled = !editMode;
+    document.getElementById("fxaChangeDeviceName").hidden = editMode;
+    document.getElementById("fxaCancelChangeDeviceName").hidden = !editMode;
+    document.getElementById("fxaSaveChangeDeviceName").hidden = !editMode;
+  },
+
+  _updateComputerNameValue: function(save) {
+    let textbox = document.getElementById("fxaSyncComputerName");
+    if (save) {
+      Weave.Service.clientsEngine.localName = textbox.value;
+    }
+    else {
+      textbox.value = Weave.Service.clientsEngine.localName;
+    }
+  },
+
   _setupEventListeners: function() {
     function setEventListener(aId, aEventType, aCallback)
     {
       document.getElementById(aId)
               .addEventListener(aEventType, aCallback.bind(gSyncPane));
     }
 
     setEventListener("noAccountSetup", "click", function (aEvent) {
@@ -156,16 +175,27 @@ let gSyncPane = {
     });
     setEventListener("syncEnginesList", "select", function () {
       if (this.selectedCount)
         this.clearSelection();
     });
     setEventListener("syncComputerName", "change", function (e) {
       gSyncUtils.changeName(e.target);
     });
+    setEventListener("fxaChangeDeviceName", "click", function () {
+      this._toggleComputerNameControls(true);
+    });
+    setEventListener("fxaCancelChangeDeviceName", "click", function () {
+      this._toggleComputerNameControls(false);
+      this._updateComputerNameValue(false);
+    });
+    setEventListener("fxaSaveChangeDeviceName", "click", function () {
+      this._toggleComputerNameControls(false);
+      this._updateComputerNameValue(true);
+    });
     setEventListener("unlinkDevice", "click", function () {
       gSyncPane.startOver(true);
       return false;
     });
     setEventListener("tosPP-normal-ToS", "click", gSyncPane.openToS);
     setEventListener("tosPP-normal-PP", "click", gSyncPane.openPrivacyPolicy);
     setEventListener("loginErrorUpdatePass", "click", function () {
       gSyncPane.updatePass();
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -309,24 +309,40 @@
                     preference="engine.addons"/>
           <checkbox label="&engine.prefs.label;"
                     accesskey="&engine.prefs.accesskey;"
                     preference="engine.prefs"/>
         </vbox>
         <spacer/>
       </hbox>
     </groupbox>
-    <hbox align="center">
-      <label accesskey="&syncDeviceName.accesskey;"
-             control="syncComputerName">
-        &syncDeviceName.label;
-      </label>
-      <textbox id="fxaSyncComputerName"
-                flex="1"/>
-    </hbox>
+    <vbox>
+      <caption>
+        <label accesskey="&syncDeviceName.accesskey;"
+               control="fxaSyncComputerName">
+          &fxaSyncDeviceName.label;
+        </label>
+      </caption>
+      <hbox id="fxaDeviceName">
+        <hbox flex="1">
+          <textbox id="fxaSyncComputerName" class="plain"
+                   disabled="true" flex="1"/>
+        </hbox>
+        <hbox>
+          <button id="fxaChangeDeviceName"
+                  label="&changeSyncDeviceName.label;"/>
+          <button id="fxaCancelChangeDeviceName"
+                  label="&cancelChangeSyncDeviceName.label;"
+                  hidden="true"/>
+          <button id="fxaSaveChangeDeviceName"
+                  label="&saveChangeSyncDeviceName.label;"
+                  hidden="true"/>
+        </hbox>
+      </hbox>
+    </vbox>
     <spacer flex="1"/>
     <vbox id="tosPP-small">
       <label id="tosPP-small-ToS" class="text-link">
         &prefs.tosLink.label;
       </label>
       <label id="tosPP-small-PP" class="text-link">
         &fxaPrivacyNotice.link.label;
       </label>
--- a/browser/components/preferences/in-content/tests/browser_privacypane_1.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_1.js
@@ -14,13 +14,10 @@ function test() {
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
 
   run_test_subset([
     test_pane_visibility,
     test_dependent_elements,
     test_dependent_cookie_elements,
     test_dependent_clearonclose_elements,
     test_dependent_prefs,
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]);
 }
--- a/browser/components/preferences/in-content/tests/browser_privacypane_3.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_3.js
@@ -13,13 +13,10 @@ function test() {
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
 
   run_test_subset([
     test_custom_retention("rememberHistory", "remember"),
     test_custom_retention("rememberHistory", "custom"),
     test_custom_retention("rememberForms", "remember"),
     test_custom_retention("rememberForms", "custom"),
     test_historymode_retention("remember", "remember"),
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]);
 }
--- a/browser/components/preferences/in-content/tests/browser_privacypane_4.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_4.js
@@ -22,13 +22,10 @@ function test() {
     test_custom_retention("acceptThirdPartyMenu", "custom", "always")
     ], [
     test_custom_retention("keepCookiesUntil", "remember", 1),
     test_custom_retention("keepCookiesUntil", "custom", 2),
     test_custom_retention("keepCookiesUntil", "custom", 0),
     test_custom_retention("alwaysClear", "remember"),
     test_custom_retention("alwaysClear", "custom"),
     test_historymode_retention("remember", "remember"),
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]));
 }
--- a/browser/components/preferences/in-content/tests/browser_privacypane_5.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_5.js
@@ -7,20 +7,21 @@ function test() {
   let rootDir = getRootDirectory(gTestPath);
   let jar = getJar(rootDir);
   if (jar) {
     let tmpdir = extractJarToTmp(jar);
     rootDir = "file://" + tmpdir.path + '/';
   }
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
 
-  run_test_subset([
+  let tests = [
     test_locbar_suggestion_retention("history", true),
     test_locbar_suggestion_retention("bookmark", true),
-    test_locbar_suggestion_retention("searches", true),
     test_locbar_suggestion_retention("openpage", false),
     test_locbar_suggestion_retention("history", true),
     test_locbar_suggestion_retention("history", false),
+  ];
 
-    // reset all preferences to their default values once we're done
-    reset_preferences
-  ]);
+  if (AppConstants.NIGHTLY_BUILD)
+    tests.push(test_locbar_suggestion_retention("searches", true)),
+
+  run_test_subset(tests);
 }
--- a/browser/components/preferences/in-content/tests/browser_privacypane_8.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_8.js
@@ -22,13 +22,10 @@ function test() {
     // history mode should now be custom; set history mode to dontremember
     test_historymode_retention("dontremember", "custom"),
 
     // history mode should remain custom; set history mode to remember
     test_historymode_retention("remember", "custom"),
 
     // history mode should now be remember
     test_historymode_retention("remember", "remember"),
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]);
 }
--- a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
+++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
@@ -310,36 +310,43 @@ function test_locbar_suggestion_retentio
     ok(elem, "Suggest " + suggestion + " checkbox should exist.");
     elem.click();
 
     is(Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled"), autocomplete,
        "browser.urlbar.autocomplete.enabled pref should be " + autocomplete);
   };
 }
 
+const gPrefCache = new Map();
+
+function cache_preferences(win) {
+  let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
+  for (let pref of prefs)
+    gPrefCache.set(pref.name, pref.value);
+}
+
 function reset_preferences(win) {
   let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
-  for (let i = 0; i < prefs.length; ++i)
-    if (prefs[i].hasUserValue)
-      prefs[i].reset();
+  for (let pref of prefs)
+    pref.value = gPrefCache.get(pref.name);
 }
 
 let testRunner;
 function run_test_subset(subset) {
   Services.prefs.setBoolPref("browser.preferences.instantApply", true);
   dump("subset: " + [x.name for (x of subset)].join(",") + "\n");
 
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     // Reset pref to its default
     Services.prefs.clearUserPref("browser.preferences.instantApply");
   });
 
   testRunner = {
-    tests: subset,
+    tests: [cache_preferences, ...subset, reset_preferences],
     counter: 0,
     runNext: function() {
       if (this.counter == this.tests.length) {
         finish();
       } else {
         let self = this;
         setTimeout(function() {
           runTestOnPrivacyPrefPane(self.tests[self.counter++]);
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -1,14 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
 var gPrivacyPane = {
 
   /**
    * Whether the use has selected the auto-start private browsing mode in the UI.
    */
   _autoStartPrivateBrowsing: false,
 
@@ -64,16 +65,18 @@ var gPrivacyPane = {
     this.initializeHistoryMode();
     this.updateHistoryModePane();
     this.updatePrivacyMicroControls();
     this.initAutoStartPrivateBrowsingReverter();
 #ifdef NIGHTLY_BUILD
     this._initTrackingProtection();
 #endif
     this._initAutocomplete();
+
+    document.getElementById("searchesSuggestion").hidden = !AppConstants.NIGHTLY_BUILD;
   },
 
   // HISTORY MODE
 
   /**
    * The list of preferences which affect the initial history mode settings.
    * If the auto start private browsing mode pref is active, the initial
    * history mode would be set to "Don't remember anything".
--- a/browser/components/preferences/privacy.xul
+++ b/browser/components/preferences/privacy.xul
@@ -279,16 +279,17 @@
                   onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
                   accesskey="&locbar.bookmarks.accesskey;"
                   preference="browser.urlbar.suggest.bookmark"/>
         <checkbox id="openpageSuggestion" label="&locbar.openpage.label;"
                   onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
                   accesskey="&locbar.openpage.accesskey;"
                   preference="browser.urlbar.suggest.openpage"/>
         <checkbox id="searchesSuggestion" label="&locbar.searches.label;"
+                  hidden="true"
                   onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
                   accesskey="&locbar.searches.accesskey;"
                   preference="browser.urlbar.suggest.searches"/>
       </vbox>
     </groupbox>
 
   </prefpane>
 
--- a/browser/components/preferences/tests/browser_privacypane_1.js
+++ b/browser/components/preferences/tests/browser_privacypane_1.js
@@ -15,13 +15,10 @@ function test() {
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
 
   run_test_subset([
     test_pane_visibility,
     test_dependent_elements,
     test_dependent_cookie_elements,
     test_dependent_clearonclose_elements,
     test_dependent_prefs,
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]);
 }
--- a/browser/components/preferences/tests/browser_privacypane_3.js
+++ b/browser/components/preferences/tests/browser_privacypane_3.js
@@ -14,13 +14,10 @@ function test() {
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
 
   run_test_subset([
     test_custom_retention("rememberHistory", "remember"),
     test_custom_retention("rememberHistory", "custom"),
     test_custom_retention("rememberForms", "remember"),
     test_custom_retention("rememberForms", "custom"),
     test_historymode_retention("remember", "remember"),
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]);
 }
--- a/browser/components/preferences/tests/browser_privacypane_4.js
+++ b/browser/components/preferences/tests/browser_privacypane_4.js
@@ -23,13 +23,10 @@ function test() {
     test_custom_retention("acceptThirdPartyMenu", "custom", "always")
     ], [
     test_custom_retention("keepCookiesUntil", "remember", 1),
     test_custom_retention("keepCookiesUntil", "custom", 2),
     test_custom_retention("keepCookiesUntil", "custom", 0),
     test_custom_retention("alwaysClear", "remember"),
     test_custom_retention("alwaysClear", "custom"),
     test_historymode_retention("remember", "remember"),
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]));
 }
--- a/browser/components/preferences/tests/browser_privacypane_5.js
+++ b/browser/components/preferences/tests/browser_privacypane_5.js
@@ -7,20 +7,21 @@ function test() {
   let rootDir = getRootDirectory(gTestPath);
   let jar = getJar(rootDir);
   if (jar) {
     let tmpdir = extractJarToTmp(jar);
     rootDir = "file://" + tmpdir.path + '/';
   }
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
 
-  run_test_subset([
+  let tests = [
     test_locbar_suggestion_retention("history", true),
     test_locbar_suggestion_retention("bookmark", true),
-    test_locbar_suggestion_retention("searches", true),
     test_locbar_suggestion_retention("openpage", false),
     test_locbar_suggestion_retention("history", true),
     test_locbar_suggestion_retention("history", false),
+  ];
 
-    // reset all preferences to their default values once we're done
-    reset_preferences
-  ]);
+  if (AppConstants.NIGHTLY_BUILD)
+    tests.push(test_locbar_suggestion_retention("searches", true)),
+
+  run_test_subset(tests);
 }
--- a/browser/components/preferences/tests/browser_privacypane_8.js
+++ b/browser/components/preferences/tests/browser_privacypane_8.js
@@ -23,13 +23,10 @@ function test() {
     // history mode should now be custom; set history mode to dontremember
     test_historymode_retention("dontremember", "custom"),
 
     // history mode should remain custom; set history mode to remember
     test_historymode_retention("remember", "custom"),
 
     // history mode should now be remember
     test_historymode_retention("remember", "remember"),
-
-    // reset all preferences to their default values once we're done
-    reset_preferences
   ]);
 }
--- a/browser/components/preferences/tests/privacypane_tests_perwindow.js
+++ b/browser/components/preferences/tests/privacypane_tests_perwindow.js
@@ -316,32 +316,39 @@ function test_locbar_suggestion_retentio
     ok(elem, "Suggest " + suggestion + " checkbox should exist.");
     elem.click();
 
     is(Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled"), autocomplete,
        "browser.urlbar.autocomplete.enabled pref should be " + autocomplete);
   };
 }
 
+const gPrefCache = new Map();
+
+function cache_preferences(win) {
+  let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
+  for (let pref of prefs)
+    gPrefCache.set(pref.name, pref.value);
+}
+
 function reset_preferences(win) {
-  let prefs = win.document.getElementsByTagName("preference");
-  for (let i = 0; i < prefs.length; ++i)
-    if (prefs[i].hasUserValue)
-      prefs[i].reset();
+  let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
+  for (let pref of prefs)
+    pref.value = gPrefCache.get(pref.name);
 }
 
 let testRunner;
 function run_test_subset(subset) {
   let instantApplyOrig = Services.prefs.getBoolPref("browser.preferences.instantApply");
   Services.prefs.setBoolPref("browser.preferences.instantApply", true);
 
   waitForExplicitFinish();
 
   testRunner = {
-    tests: subset,
+    tests: [cache_preferences, ...subset, reset_preferences],
     counter: 0,
     runNext: function() {
       if (this.counter == this.tests.length) {
         // cleanup
         Services.prefs.setBoolPref("browser.preferences.instantApply", instantApplyOrig);
         finish();
       } else {
         let self = this;
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -339,16 +339,25 @@ HistoryListener.prototype = {
   OnHistoryGoForward: function(forwardURI) { return true; },
   OnHistoryGotoIndex: function(index, gotoURI) { return true; },
   OnHistoryPurge: function(numEntries) { return true; },
   OnHistoryReplaceEntry: function(index) {},
 
   // This will be called for a pending tab when loadURI(uri) is called where
   // the given |uri| only differs in the fragment.
   OnHistoryNewEntry(newURI) {
+    let currentURI = this.webNavigation.currentURI;
+
+    // Ignore new SHistory entries with the same URI as those do not indicate
+    // a navigation inside a document by changing the #hash part of the URL.
+    // We usually hit this when purging session history for browsers.
+    if (currentURI && (currentURI.spec == newURI.spec)) {
+      return;
+    }
+
     // Reset the tab's URL to what it's actually showing. Without this loadURI()
     // would use the current document and change the displayed URL only.
     this.webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
 
     // Kick off a new load so that we navigate away from about:blank to the
     // new URL that was passed to loadURI(). The new load will cause a
     // STATE_START notification to be sent and the ProgressListener will then
     // notify the parent and do the rest.
--- a/browser/components/sessionstore/SessionSaver.jsm
+++ b/browser/components/sessionstore/SessionSaver.jsm
@@ -87,24 +87,16 @@ this.SessionSaver = Object.freeze({
    * Sets the last save time to the current time. This will cause us to wait for
    * at least the configured interval when runDelayed() is called next.
    */
   updateLastSaveTime: function () {
     SessionSaverInternal.updateLastSaveTime();
   },
 
   /**
-   * Sets the last save time to zero. This will cause us to
-   * immediately save the next time runDelayed() is called.
-   */
-  clearLastSaveTime: function () {
-    SessionSaverInternal.clearLastSaveTime();
-  },
-
-  /**
    * Cancels all pending session saves.
    */
   cancel: function () {
     SessionSaverInternal.cancel();
   }
 });
 
 /**
@@ -157,24 +149,16 @@ let SessionSaverInternal = {
    * Sets the last save time to the current time. This will cause us to wait for
    * at least the configured interval when runDelayed() is called next.
    */
   updateLastSaveTime: function () {
     this._lastSaveTime = Date.now();
   },
 
   /**
-   * Sets the last save time to zero. This will cause us to
-   * immediately save the next time runDelayed() is called.
-   */
-  clearLastSaveTime: function () {
-    this._lastSaveTime = 0;
-  },
-
-  /**
    * Cancels all pending session saves.
    */
   cancel: function () {
     clearTimeout(this._timeoutID);
     this._timeoutID = null;
   },
 
   /**
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -904,20 +904,23 @@ let SessionStoreInternal = {
    *        The initial state to be loaded after startup (optional)
    */
   initializeWindow(aWindow, aInitialState = null) {
     let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
 
     // perform additional initialization when the first window is loading
     if (RunState.isStopped) {
       RunState.setRunning();
-      SessionSaver.updateLastSaveTime();
 
       // restore a crashed session resp. resume the last session if requested
       if (aInitialState) {
+        // Don't write to disk right after startup. Set the last time we wrote
+        // to disk to NOW() to enforce a full interval before the next write.
+        SessionSaver.updateLastSaveTime();
+
         if (isPrivateWindow) {
           // We're starting with a single private window. Save the state we
           // actually wanted to restore so that we can do it later in case
           // the user opens another, non-private window.
           this._deferredInitialState = gSessionStartup.state;
 
           // Nothing to restore now, notify observers things are complete.
           Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
@@ -932,19 +935,16 @@ let SessionStoreInternal = {
           let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
           let options = {firstWindow: true, overwriteTabs: overwrite};
           this.restoreWindows(aWindow, aInitialState, options);
         }
       }
       else {
         // Nothing to restore, notify observers things are complete.
         Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
-
-        // The next delayed save request should execute immediately.
-        SessionSaver.clearLastSaveTime();
       }
     }
     // this window was opened by _openWindowWithState
     else if (!this._isWindowLoaded(aWindow)) {
       let state = this._statesToRestore[aWindow.__SS_restoreID];
       let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
       this.restoreWindow(aWindow, state.windows[0], options);
     }
@@ -1276,25 +1276,21 @@ let SessionStoreInternal = {
   onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
     SessionFile.wipe();
     // If the browser is shutting down, simply return after clearing the
     // session data on disk as this notification fires after the
     // quit-application notification so the browser is about to exit.
     if (RunState.isQuitting)
       return;
     LastSession.clear();
+
     let openWindows = {};
-    this._forEachBrowserWindow(function(aWindow) {
-      Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
-        delete aTab.linkedBrowser.__SS_data;
-        if (aTab.linkedBrowser.__SS_restoreState)
-          this._resetTabRestoringState(aTab);
-      }, this);
-      openWindows[aWindow.__SSi] = true;
-    });
+    // Collect open windows.
+    this._forEachBrowserWindow(({__SSi: id}) => openWindows[id] = true);
+
     // also clear all data about closed tabs and windows
     for (let ix in this._windows) {
       if (ix in openWindows) {
         this._windows[ix]._closedTabs = [];
       } else {
         delete this._windows[ix];
       }
     }
--- a/browser/components/sessionstore/TabStateCache.jsm
+++ b/browser/components/sessionstore/TabStateCache.jsm
@@ -13,70 +13,74 @@ this.EXPORTED_SYMBOLS = ["TabStateCache"
  * to tab data (as objects).
  *
  * Note that we should never cache private data, as:
  * - that data is used very seldom by SessionStore;
  * - caching private data in addition to public data is memory consuming.
  */
 this.TabStateCache = Object.freeze({
   /**
-   * Retrieves cached data for a given |browser|.
+   * Retrieves cached data for a given |tab| or associated |browser|.
    *
-   * @param browser (xul:browser)
-   *        The browser to retrieve cached data for.
+   * @param browserOrTab (xul:tab or xul:browser)
+   *        The tab or browser to retrieve cached data for.
    * @return (object)
-   *         The cached data stored for the given |browser|.
+   *         The cached data stored for the given |tab|
+   *         or associated |browser|.
    */
-  get: function (browser) {
-    return TabStateCacheInternal.get(browser);
+  get: function (browserOrTab) {
+    return TabStateCacheInternal.get(browserOrTab);
   },
 
   /**
-   * Updates cached data for a given |browser|.
+   * Updates cached data for a given |tab| or associated |browser|.
    *
-   * @param browser (xul:browser)
-   *        The browser belonging to the given tab data.
+   * @param browserOrTab (xul:tab or xul:browser)
+   *        The tab or browser belonging to the given tab data.
    * @param newData (object)
-   *        The new data to be stored for the given |browser|.
+   *        The new data to be stored for the given |tab|
+   *        or associated |browser|.
    */
-  update: function (browser, newData) {
-    TabStateCacheInternal.update(browser, newData);
+  update: function (browserOrTab, newData) {
+    TabStateCacheInternal.update(browserOrTab, newData);
   }
 });
 
 let TabStateCacheInternal = {
   _data: new WeakMap(),
 
   /**
-   * Retrieves cached data for a given |browser|.
+   * Retrieves cached data for a given |tab| or associated |browser|.
    *
-   * @param browser (xul:browser)
-   *        The browser to retrieve cached data for.
+   * @param browserOrTab (xul:tab or xul:browser)
+   *        The tab or browser to retrieve cached data for.
    * @return (object)
-   *         The cached data stored for the given |browser|.
+   *         The cached data stored for the given |tab|
+   *         or associated |browser|.
    */
-  get: function (browser) {
-    return this._data.get(browser.permanentKey);
+  get: function (browserOrTab) {
+    return this._data.get(browserOrTab.permanentKey);
   },
 
   /**
-   * Updates cached data for a given |browser|.
+   * Updates cached data for a given |tab| or associated |browser|.
    *
-   * @param browser (xul:browser)
-   *        The browser belonging to the given tab data.
+   * @param browserOrTab (xul:tab or xul:browser)
+   *        The tab or browser belonging to the given tab data.
    * @param newData (object)
-   *        The new data to be stored for the given |browser|.
+   *        The new data to be stored for the given |tab|
+   *        or associated |browser|.
    */
-  update: function (browser, newData) {
-    let data = this._data.get(browser.permanentKey) || {};
+  update: function (browserOrTab, newData) {
+    let data = this._data.get(browserOrTab.permanentKey) || {};
 
     for (let key of Object.keys(newData)) {
       let value = newData[key];
       if (value === null) {
         delete data[key];
       } else {
         data[key] = value;
       }
     }
 
-    this._data.set(browser.permanentKey, data);
+    this._data.set(browserOrTab.permanentKey, data);
   }
 };
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -91,16 +91,18 @@ skip-if = buildapp == 'mulet'
 [browser_global_store.js]
 [browser_history_persist.js]
 [browser_label_and_icon.js]
 [browser_merge_closed_tabs.js]
 [browser_page_title.js]
 [browser_pageStyle.js]
 [browser_pending_tabs.js]
 [browser_privatetabs.js]
+[browser_purge_shistory.js]
+skip-if = e10s
 [browser_replace_load.js]
 [browser_restore_redirect.js]
 [browser_scrollPositions.js]
 [browser_sessionHistory.js]
 [browser_sessionStorage.js]
 [browser_swapDocShells.js]
 skip-if = e10s # See bug 918634
 [browser_switch_remoteness.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_purge_shistory.js
@@ -0,0 +1,58 @@
+"use strict";
+
+/**
+ * This test checks that pending tabs are treated like fully loaded tabs when
+ * purging session history. Just like for fully loaded tabs we want to remove
+ * every but the current shistory entry.
+ */
+
+const TAB_STATE = {
+  entries: [{url: "about:mozilla"}, {url: "about:robots"}],
+  index: 1,
+};
+
+function checkTabContents(browser) {
+  return ContentTask.spawn(browser, null, function* () {
+    let Ci = Components.interfaces;
+    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+    let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
+    return history && history.count == 1 && content.document.documentURI == "about:mozilla";
+  });
+}
+
+add_task(function* () {
+  // Create a new tab.
+  let tab = gBrowser.addTab("about:blank");
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+  yield promiseTabState(tab, TAB_STATE);
+
+  // Create another new tab.
+  let tab2 = gBrowser.addTab("about:blank");
+  let browser2 = tab2.linkedBrowser;
+  yield promiseBrowserLoaded(browser2);
+
+  // The tab shouldn't be restored right away.
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+  // Prepare the tab state.
+  let promise = promiseTabRestoring(tab2);
+  ss.setTabState(tab2, JSON.stringify(TAB_STATE));
+  ok(tab2.hasAttribute("pending"), "tab is pending");
+  yield promise;
+
+  // Purge session history.
+  Services.obs.notifyObservers(null, "browser:purge-session-history", "");
+  ok((yield checkTabContents(browser)), "expected tab contents found");
+  ok(tab2.hasAttribute("pending"), "tab is still pending");
+
+  // Kick off tab restoration.
+  gBrowser.selectedTab = tab2;
+  yield promiseTabRestored(tab2);
+  ok((yield checkTabContents(browser2)), "expected tab contents found");
+  ok(!tab2.hasAttribute("pending"), "tab is not pending anymore");
+
+  // Cleanup.
+  gBrowser.removeTab(tab2);
+  gBrowser.removeTab(tab);
+});
--- a/browser/components/sessionstore/test/browser_sessionHistory.js
+++ b/browser/components/sessionstore/test/browser_sessionHistory.js
@@ -27,46 +27,16 @@ add_task(function test_load_start() {
   // Check that the correct URL was restored.
   is(browser.currentURI.spec, "about:mozilla", "url is correct");
 
   // Cleanup.
   gBrowser.removeTab(tab);
 });
 
 /**
- * Ensure that purging shistory invalidates.
- */
-add_task(function test_purge() {
-  // Create a new tab.
-  let tab = gBrowser.addTab("about:mozilla");
-  let browser = tab.linkedBrowser;
-  yield promiseBrowserLoaded(browser);
-
-  // Create a second shistory entry.
-  browser.loadURI("about:robots");
-  yield promiseBrowserLoaded(browser);
-
-  // Check that we now have two shistory entries.
-  yield TabStateFlusher.flush(browser);
-  let {entries} = JSON.parse(ss.getTabState(tab));
-  is(entries.length, 2, "there are two shistory entries");
-
-  // Purge session history.
-  yield sendMessage(browser, "ss-test:purgeSessionHistory");
-
-  // Check that we are left with a single shistory entry.
-  yield TabStateFlusher.flush(browser);
-  ({entries} = JSON.parse(ss.getTabState(tab)));
-  is(entries.length, 1, "there is one shistory entry");
-
-  // Cleanup.
-  gBrowser.removeTab(tab);
-});
-
-/**
  * Ensure that anchor navigation invalidates shistory.
  */
 add_task(function test_hashchange() {
   const URL = "data:text/html;charset=utf-8,<a id=a href=%23>clickme</a>";
 
   // Create a new tab.
   let tab = gBrowser.addTab(URL);
   let browser = tab.linkedBrowser;
--- a/browser/components/sessionstore/test/content.js
+++ b/browser/components/sessionstore/test/content.js
@@ -79,21 +79,16 @@ addEventListener("hashchange", function 
   sendAsyncMessage("ss-test:hashchange");
 });
 
 addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
   Services.obs.notifyObservers(null, "browser:purge-domain-data", domain);
   content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));
 });
 
-addMessageListener("ss-test:purgeSessionHistory", function () {
-  Services.obs.notifyObservers(null, "browser:purge-session-history", "");
-  content.setTimeout(() => sendAsyncMessage("ss-test:purgeSessionHistory"));
-});
-
 addMessageListener("ss-test:getStyleSheets", function (msg) {
   let sheets = content.document.styleSheets;
   let titles = Array.map(sheets, ss => [ss.title, ss.disabled]);
   sendSyncMessage("ss-test:getStyleSheets", titles);
 });
 
 addMessageListener("ss-test:enableStyleSheetsForSet", function (msg) {
   let sheets = content.document.styleSheets;
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -82,16 +82,17 @@ support-files =
   doc_minified_bogus_map.html
   doc_native-event-handler.html
   doc_no-page-sources.html
   doc_pause-exceptions.html
   doc_pretty-print.html
   doc_pretty-print-2.html
   doc_pretty-print-3.html
   doc_pretty-print-on-paused.html
+  doc_promise-get-allocation-stack.html
   doc_promise.html
   doc_random-javascript.html
   doc_recursion-stack.html
   doc_same-line-functions.html
   doc_scope-variable.html
   doc_scope-variable-2.html
   doc_scope-variable-3.html
   doc_scope-variable-4.html
@@ -341,17 +342,21 @@ skip-if = e10s && debug
 skip-if = e10s && debug
 [browser_dbg_pretty-print-12.js]
 skip-if = e10s && debug
 [browser_dbg_pretty-print-13.js]
 skip-if = e10s && debug
 [browser_dbg_pretty-print-on-paused.js]
 skip-if = e10s && debug
 [browser_dbg_progress-listener-bug.js]
-skip-if = e10a && debug
+skip-if = e10s && debug
+[browser_dbg_promises-allocation-stack.js]
+skip-if = e10s && debug
+[browser_dbg_promises-chrome-allocation-stack.js]
+skip-if = e10s && debug
 [browser_dbg_reload-preferred-script-01.js]
 skip-if = e10s && debug
 [browser_dbg_reload-preferred-script-02.js]
 skip-if = e10s && debug
 [browser_dbg_reload-preferred-script-03.js]
 skip-if = e10s && debug
 [browser_dbg_reload-same-script.js]
 skip-if = e10s && debug
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_promises-allocation-stack.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get a stack to a promise's allocation point.
+ */
+
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "doc_promise-get-allocation-stack.html";
+const { PromisesFront } = devtools.require("devtools/server/actors/promises");
+let events = devtools.require("sdk/event/core");
+
+function test() {
+  Task.spawn(function* () {
+    DebuggerServer.init();
+    DebuggerServer.addBrowserActors();
+
+    const [ tab,, panel ] = yield initDebugger(TAB_URL);
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    yield connect(client);
+
+    let { tabs } = yield listTabs(client);
+    let targetTab = findTab(tabs, TAB_URL);
+    yield attachTab(client, targetTab);
+
+    yield testGetAllocationStack(client, targetTab, tab);
+
+    yield close(client);
+    yield closeDebuggerAndFinish(panel);
+  }).then(null, error => {
+    ok(false, "Got an error: " + error.message + "\n" + error.stack);
+  });
+}
+
+function* testGetAllocationStack(client, form, tab) {
+  let front = PromisesFront(client, form);
+
+  yield front.attach();
+  yield front.listPromises();
+
+  // Get the grip for promise p
+  let onNewPromise = new Promise(resolve => {
+    events.on(front, "new-promises", promises => {
+      for (let p of promises) {
+        if (p.preview.ownProperties.name &&
+            p.preview.ownProperties.name.value === "p") {
+          resolve(p);
+        }
+      }
+    });
+  });
+
+  callInTab(tab, "makePromises");
+
+  let grip = yield onNewPromise;
+  ok(grip, "Found our promise p");
+
+  let objectClient = new ObjectClient(client, grip);
+  ok(objectClient, "Got Object Client");
+
+  yield new Promise(resolve => {
+    objectClient.getPromiseAllocationStack(response => {
+      ok(response.allocationStack.length, "Got promise allocation stack.");
+
+      for (let stack of response.allocationStack) {
+        is(stack.source.url, TAB_URL, "Got correct source URL.");
+        is(stack.functionDisplayName, "makePromises",
+           "Got correct function display name.");
+        is(typeof stack.line, "number", "Expect stack line to be a number.");
+        is(typeof stack.column, "number",
+           "Expect stack column to be a number.");
+      }
+
+      resolve();
+    });
+  });
+
+  yield front.detach();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_promises-chrome-allocation-stack.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get a stack to a promise's allocation point in the chrome
+ * process.
+ */
+
+"use strict";
+
+const SOURCE_URL = "browser_dbg_promises-chrome-allocation-stack.js";
+const { PromisesFront } = devtools.require("devtools/server/actors/promises");
+let events = devtools.require("sdk/event/core");
+
+const STACK_DATA = [
+  { functionDisplayName: "test/</<" },
+  { functionDisplayName: "testGetAllocationStack" },
+];
+
+function test() {
+  Task.spawn(function* () {
+    requestLongerTimeout(10);
+
+    DebuggerServer.init();
+    DebuggerServer.addBrowserActors();
+    DebuggerServer.allowChromeProcess = true;
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    yield connect(client);
+    let chrome = yield client.getProcess();
+    let [, tabClient] = yield attachTab(client, chrome.form);
+    yield tabClient.attachThread();
+
+    yield testGetAllocationStack(client, chrome.form, () => {
+      let p = new Promise(() => {});
+      p.name = "p";
+      let q = p.then();
+      q.name = "q";
+      let r = p.then(null, () => {});
+      r.name = "r";
+    });
+
+    yield close(client);
+    finish();
+  }).then(null, error => {
+    ok(false, "Got an error: " + error.message + "\n" + error.stack);
+  });
+}
+
+function* testGetAllocationStack(client, form, makePromises) {
+  let front = PromisesFront(client, form);
+
+  yield front.attach();
+  yield front.listPromises();
+
+  // Get the grip for promise p
+  let onNewPromise = new Promise(resolve => {
+    events.on(front, "new-promises", promises => {
+      for (let p of promises) {
+        if (p.preview.ownProperties.name &&
+            p.preview.ownProperties.name.value === "p") {
+          resolve(p);
+        }
+      }
+    });
+  });
+
+  makePromises();
+
+  let grip = yield onNewPromise;
+  ok(grip, "Found our promise p");
+
+  let objectClient = new ObjectClient(client, grip);
+  ok(objectClient, "Got Object Client");
+
+  yield new Promise(resolve => {
+    objectClient.getPromiseAllocationStack(response => {
+      ok(response.allocationStack.length, "Got promise allocation stack.");
+
+      for (let i = 0; i < STACK_DATA.length; i++) {
+        let data = STACK_DATA[i];
+        let stack = response.allocationStack[i];
+
+        ok(stack.source.url.startsWith("chrome:"), "Got a chrome source URL");
+        ok(stack.source.url.endsWith(SOURCE_URL), "Got correct source URL.");
+        is(stack.functionDisplayName, data.functionDisplayName,
+           "Got correct function display name.");
+        is(typeof stack.line, "number", "Expect stack line to be a number.");
+        is(typeof stack.column, "number",
+           "Expect stack column to be a number.");
+      }
+
+      resolve();
+    });
+  });
+
+  yield front.detach();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_promise-get-allocation-stack.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Promise test page</title>
+  </head>
+
+  <body>
+    <script type="text/javascript">
+      function makePromises() {
+        var p = new Promise(() => {});
+        p.name = "p";
+        var q = p.then();
+        q.name = "q";
+        var r = p.then(null, () => {});
+        r.name = "r";
+      }
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -15,17 +15,18 @@ Services.prefs.setBoolPref("devtools.deb
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 let { Promise: promise } = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { require } = devtools;
 let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
 let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
-let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+let { DebuggerClient, ObjectClient } =
+  Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
 let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 let EventEmitter = require("devtools/toolkit/event-emitter");
 const { promiseInvoke } = require("devtools/async-utils");
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -29,16 +29,17 @@ support-files =
 [browser_target_support.js]
 [browser_two_tabs.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
 [browser_toolbox_highlight.js]
 [browser_toolbox_hosts.js]
 [browser_toolbox_hosts_size.js]
 [browser_toolbox_minimize.js]
+skip-if = true # Bug 1177463 - Temporarily hide the minimize button
 [browser_toolbox_options.js]
 [browser_toolbox_options_disable_buttons.js]
 [browser_toolbox_options_disable_cache-01.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_cache-02.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_js.js]
 skip-if = e10s # Bug 1030318
--- a/browser/devtools/markupview/test/helper_events_test_runner.js
+++ b/browser/devtools/markupview/test/helper_events_test_runner.js
@@ -10,18 +10,16 @@ function* runEventPopupTests() {
   let {inspector} = yield addTab(TEST_URL).then(openInspector);
 
   yield inspector.markup.expandAll();
 
   for (let {selector, expected} of TEST_DATA) {
     yield checkEventsForNode(selector, expected, inspector);
   }
 
-  gBrowser.removeCurrentTab();
-
   // Wait for promises to avoid leaks when running this as a single test.
   // We need to do this because we have opened a bunch of popups and don't them
   // to affect other test runs when they are GCd.
   yield promiseNextTick();
 }
 
 /**
  * Generator function that takes a selector and expected results and returns
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -30,17 +30,16 @@ const REQUESTS_WATERFALL_SAFE_BOUNDS = 9
 const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
 const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
-const DEFAULT_HTTP_VERSION = "HTTP/1.1";
 const REQUEST_TIME_DECIMALS = 2;
 const HEADERS_SIZE_DECIMALS = 3;
 const CONTENT_SIZE_DECIMALS = 2;
 const CONTENT_MIME_TYPE_ABBREVIATIONS = {
   "ecmascript": "js",
   "javascript": "js",
   "x-javascript": "js"
 };
@@ -2636,17 +2635,17 @@ NetworkDetailsView.prototype = {
     if (aData.status) {
       $("#headers-summary-status-circle").setAttribute("code", aData.fromCache ? "cached" : aData.status);
       $("#headers-summary-status-value").setAttribute("value", aData.status + " " + aData.statusText);
       $("#headers-summary-status").removeAttribute("hidden");
     } else {
       $("#headers-summary-status").setAttribute("hidden", "true");
     }
 
-    if (aData.httpVersion && aData.httpVersion != DEFAULT_HTTP_VERSION) {
+    if (aData.httpVersion) {
       $("#headers-summary-version-value").setAttribute("value", aData.httpVersion);
       $("#headers-summary-version").removeAttribute("hidden");
     } else {
       $("#headers-summary-version").setAttribute("hidden", "true");
     }
   },
 
   /**
--- a/browser/devtools/shared/inplace-editor.js
+++ b/browser/devtools/shared/inplace-editor.js
@@ -229,20 +229,21 @@ function InplaceEditor(aOptions, aEvent)
     this._advanceChars = aCharCode => aCharCode in advanceCharcodes;
   }
 
   // Hide the provided element and add our editor.
   this.originalDisplay = this.elt.style.display;
   this.elt.style.display = "none";
   this.elt.parentNode.insertBefore(this.input, this.elt);
 
+  this.input.focus();
+
   if (typeof(aOptions.selectAll) == "undefined" || aOptions.selectAll) {
     this.input.select();
   }
-  this.input.focus();
 
   if (this.contentType == CONTENT_TYPES.CSS_VALUE && this.input.value == "") {
     this._maybeSuggestCompletion(true);
   }
 
   this.input.addEventListener("blur", this._onBlur, false);
   this.input.addEventListener("keypress", this._onKeyPress, false);
   this.input.addEventListener("input", this._onInput, false);
@@ -954,17 +955,18 @@ InplaceEditor.prototype = {
         if (this.stopOnShiftTab) {
           direction = null;
         } else {
           direction = FOCUS_BACKWARD;
         }
       }
       if ((this.stopOnReturn &&
            aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) ||
-          (this.stopOnTab && aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB)) {
+          (this.stopOnTab && aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
+           !aEvent.shiftKey)) {
         direction = null;
       }
 
       // Now we don't want to suggest anything as we are moving out.
       this._preventSuggestions = true;
       // But we still want to show suggestions for css values. i.e. moving out
       // of css property input box in forward direction
       if (this.contentType == CONTENT_TYPES.CSS_PROPERTY &&
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -2705,31 +2705,29 @@ RuleEditor.prototype = {
         title: CssLogic.l10n("rule.selectorHighlighter.tooltip")
       });
       selectorHighlighter.addEventListener("click", () => {
         this.ruleView.toggleSelectorHighlighter(selectorHighlighter, selector);
       });
     }
 
     this.selectorText = createChild(this.selectorContainer, "span", {
-      class: "ruleview-selector theme-fg-color3"
+      class: "ruleview-selector theme-fg-color3",
+      tabindex: this.isSelectorEditable ? "0" : "-1",
     });
 
     if (this.isSelectorEditable) {
       this.selectorContainer.addEventListener("click", aEvent => {
         // Clicks within the selector shouldn't propagate any further.
         aEvent.stopPropagation();
       }, false);
 
       editableField({
         element: this.selectorText,
         done: this._onSelectorDone,
-        stopOnShiftTab: true,
-        stopOnTab: true,
-        stopOnReturn: true
       });
     }
 
     this.openBrace = createChild(header, "span", {
       class: "ruleview-ruleopen",
       textContent: " {"
     });
 
@@ -2996,36 +2994,38 @@ RuleEditor.prototype = {
     }
   },
 
   /**
    * Called when the selector's inplace editor is closed.
    * Ignores the change if the user pressed escape, otherwise
    * commits it.
    *
-   * @param {string} aValue
+   * @param {string} value
    *        The value contained in the editor.
-   * @param {boolean} aCommit
+   * @param {boolean} commit
    *        True if the change should be applied.
+   * @param {number} direction
+   *        The move focus direction number.
    */
-  _onSelectorDone: function(aValue, aCommit) {
-    if (!aCommit || this.isEditing || aValue === "" ||
-        aValue === this.rule.selectorText) {
+  _onSelectorDone: function(value, commit, direction) {
+    if (!commit || this.isEditing || value === "" ||
+        value === this.rule.selectorText) {
       return;
     }
 
     let ruleView = this.ruleView;
     let elementStyle = ruleView._elementStyle;
     let element = elementStyle.element;
     let supportsUnmatchedRules =
       this.rule.domRule.supportsModifySelectorUnmatched;
 
     this.isEditing = true;
 
-    this.rule.domRule.modifySelector(element, aValue).then(response => {
+    this.rule.domRule.modifySelector(element, value).then(response => {
       this.isEditing = false;
 
       if (!supportsUnmatchedRules) {
         if (response) {
           this.ruleView.refreshPanel();
         }
         return;
       }
@@ -3047,20 +3047,44 @@ RuleEditor.prototype = {
       this.element.parentNode.replaceChild(editor.element, this.element);
 
       // Remove highlight for modified selector
       if (ruleView.highlightedSelector &&
           ruleView.highlightedSelector == this.rule.selectorText) {
         ruleView.toggleSelectorHighlighter(ruleView.lastSelectorIcon,
           ruleView.highlightedSelector);
       }
+
+      this._moveSelectorFocus(newRule, direction);
     }).then(null, err => {
       this.isEditing = false;
       promiseWarn(err);
     });
+  },
+
+  /**
+   * Handle moving the focus change after pressing tab and return from the
+   * selector inplace editor. The focused element after a tab or return keypress
+   * is lost because the rule editor is replaced.
+   *
+   * @param {Rule} rule
+   *        The Rule object.
+   * @param {number} direction
+   *        The move focus direction number.
+   */
+  _moveSelectorFocus: function(rule, direction) {
+    if (!direction || direction == Ci.nsIFocusManager.MOVEFOCUS_BACKWARD) {
+      return;
+    }
+
+    if (rule.textProps.length > 0) {
+      rule.textProps[0].editor.nameSpan.click();
+    } else {
+      this.propertyList.click();
+    }
   }
 };
 
 /**
  * Create a TextPropertyEditor.
  *
  * @param {RuleEditor} aRuleEditor
  *        The rule editor that owns this TextPropertyEditor.
--- a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector-commit.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector-commit.js
@@ -2,70 +2,79 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test selector value is correctly displayed when committing the inplace editor
 // with ENTER, ESC, SHIFT+TAB and TAB
 
-let PAGE_CONTENT = [
-  '<style type="text/css">',
-  '  #testid {',
-  '    text-align: center;',
-  '  }',
-  '</style>',
-  '<div id="testid" class="testclass">Styled Node</div>',
+let TEST_URI = [
+  "<style type='text/css'>",
+  "  #testid1 {",
+  "    text-align: center;",
+  "  }",
+  "  #testid2 {",
+  "    text-align: center;",
+  "  }",
+  "  #testid3 {",
+  "  }",
+  "</style>",
+  "<div id='testid1'>Styled Node</div>",
+  "<div id='testid2'>Styled Node</div>",
+  "<div id='testid3'>Styled Node</div>",
 ].join("\n");
 
 const TEST_DATA = [
   {
-    node: "#testid",
+    node: "#testid1",
     value: ".testclass",
     commitKey: "VK_ESCAPE",
     modifiers: {},
-    expected: "#testid"
+    expected: "#testid1",
+
   },
   {
-    node: "#testid",
-    value: ".testclass",
+    node: "#testid1",
+    value: ".testclass1",
     commitKey: "VK_RETURN",
     modifiers: {},
-    expected: ".testclass"
+    expected: ".testclass1"
   },
   {
-    node: "#testid",
-    value: ".testclass",
+    node: "#testid2",
+    value: ".testclass2",
     commitKey: "VK_TAB",
     modifiers: {},
-    expected: ".testclass"
+    expected: ".testclass2"
   },
   {
-    node: "#testid",
-    value: ".testclass",
+    node: "#testid3",
+    value: ".testclass3",
     commitKey: "VK_TAB",
     modifiers: {shiftKey: true},
-    expected: ".testclass"
+    expected: ".testclass3"
   }
 ];
 
 add_task(function*() {
-  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(PAGE_CONTENT));
-  let {toolbox, inspector, view} = yield openRuleView();
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, view } = yield openRuleView();
 
   info("Iterating over the test data");
   for (let data of TEST_DATA) {
     yield runTestData(inspector, view, data);
   }
 });
 
 function* runTestData(inspector, view, data) {
   let {node, value, commitKey, modifiers, expected} = data;
 
-  info("Updating " + node + " to " + value + " and committing with " + commitKey + ". Expecting: " + expected);
+  info("Updating " + node + " to " + value + " and committing with " +
+       commitKey + ". Expecting: " + expected);
 
   info("Selecting the test element");
   yield selectNode(node, inspector);
 
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
 
   info("Focusing an existing selector name in the rule-view");
   let editor = yield focusEditableField(idRuleEditor.selectorText);
@@ -73,21 +82,37 @@ function* runTestData(inspector, view, d
       "The selector editor got focused");
 
   info("Enter the new selector value: " + value);
   editor.input.value = value;
 
   info("Entering the commit key " + commitKey + " " + modifiers);
   EventUtils.synthesizeKey(commitKey, modifiers);
 
+  let activeElement = view.doc.activeElement;
+
   if (commitKey === "VK_ESCAPE") {
     is(idRuleEditor.rule.selectorText, expected,
         "Value is as expected: " + expected);
-    is(idRuleEditor.isEditing, false, "Selector is not being edited.")
-  } else {
-    yield once(view, "ruleview-changed");
-    ok(getRuleViewRule(view, expected),
-        "Rule with " + name + " selector exists.");
+    is(idRuleEditor.isEditing, false, "Selector is not being edited.");
+    is(idRuleEditor.selectorText, activeElement,
+       "Focus is on selector span.");
+    return;
   }
 
-  info("Resetting page content");
-  content.document.body.innerHTML = PAGE_CONTENT;
+  yield once(view, "ruleview-changed");
+
+  ok(getRuleViewRule(view, expected),
+     "Rule with " + expected + " selector exists.");
+
+  if (modifiers.shiftKey) {
+    idRuleEditor = getRuleViewRuleEditor(view, 0);
+  }
+
+  let rule = idRuleEditor.rule;
+  if (rule.textProps.length > 0) {
+    is(inplaceEditor(rule.textProps[0].editor.nameSpan).input, activeElement,
+       "Focus is on the first property name span.");
+  } else {
+    is(inplaceEditor(idRuleEditor.newPropSpan).input, activeElement,
+       "Focus is on the new property span.");
+  }
 }
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -435,17 +435,17 @@ WebConsole.prototype = {
    *        The URL of the file.
    * @param integer aSourceLine
    *        The line number which should be highlighted.
    */
   viewSource: function WC_viewSource(aSourceURL, aSourceLine) {
     // Attempt to access view source via a browser first, which may display it in
     // a tab, if enabled.
     let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
-    if (browserWin) {
+    if (browserWin && browserWin.BrowserViewSourceOfDocument) {
       return browserWin.BrowserViewSourceOfDocument({
         URL: aSourceURL,
         lineNumber: aSourceLine
       });
     }
     this.gViewSourceUtils.viewSource(aSourceURL, null, this.iframeWindow.document, aSourceLine || 0);
   },
 
--- a/browser/devtools/webconsole/test/browser_bug1045902_console_csp_ignore_reflected_xss_message.js
+++ b/browser/devtools/webconsole/test/browser_bug1045902_console_csp_ignore_reflected_xss_message.js
@@ -5,55 +5,59 @@
 
 /* Description of the test:
  * We are loading a file with the following CSP:
  *     'reflected-xss filter'
  * This directive is not supported, hence we confirm that
  * the according message is displayed in the web console.
  */
 
-const EXPECTED_RESULT = "Not supporting directive 'reflected-xss'. Directive and values will be ignored.";
-const TEST_FILE = "http://example.com/browser/browser/devtools/webconsole/test/" +
-                  "test_bug1045902_console_csp_ignore_reflected_xss_message.html";
+"use strict";
+
+const EXPECTED_RESULT = "Not supporting directive 'reflected-xss'. Directive " +
+                        "and values will be ignored.";
+const TEST_FILE = "http://example.com/browser/browser/devtools/webconsole/" +
+                  "test/test_bug1045902_console_csp_ignore_reflected_xss_" +
+                  "message.html";
 
 let hud = undefined;
 
-let TEST_URI = "data:text/html;charset=utf8,Web Console CSP ignoring reflected XSS (bug 1045902)";
+let TEST_URI = "data:text/html;charset=utf8,Web Console CSP ignoring " +
+               "reflected XSS (bug 1045902)";
 
 let test = asyncTest(function* () {
   let { browser } = yield loadTab(TEST_URI);
 
   hud = yield openConsole();
 
   yield loadDocument(browser);
   yield testViolationMessage();
 
   hud = null;
 });
 
-
 function loadDocument(browser) {
   let deferred = promise.defer();
 
-  hud.jsterm.clearOutput()
+  hud.jsterm.clearOutput();
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     deferred.resolve();
   }, true);
   content.location = TEST_FILE;
 
   return deferred.promise;
 }
 
 function testViolationMessage() {
-  let deferred = promise.defer();
   let aOutputNode = hud.outputNode;
 
   return waitForSuccess({
-      name: "Confirming that CSP logs messages to the console when 'reflected-xss' directive is used!",
+      name: "Confirming that CSP logs messages to the console when " +
+            "'reflected-xss' directive is used!",
       validator: function() {
-        console.log(hud.outputNode.textContent);
+        console.log(aOutputNode.textContent);
         let success = false;
-        success = hud.outputNode.textContent.indexOf(EXPECTED_RESULT) > -1;
+        success = aOutputNode.textContent.indexOf(EXPECTED_RESULT) > -1;
         return success;
       }
     });
 }
--- a/browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js
+++ b/browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js
@@ -5,18 +5,20 @@
 
 // Tests if the JSTerm sandbox is updated when the user navigates from one
 // domain to another, in order to avoid permission denied errors with a sandbox
 // created for a different origin.
 
 "use strict";
 
 let test = asyncTest(function* () {
-  const TEST_URI1 = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
-  const TEST_URI2 = "http://example.org/browser/browser/devtools/webconsole/test/test-console.html";
+  const TEST_URI1 = "http://example.com/browser/browser/devtools/webconsole/" +
+                    "test/test-console.html";
+  const TEST_URI2 = "http://example.org/browser/browser/devtools/webconsole/" +
+                    "test/test-console.html";
 
   yield loadTab(TEST_URI1);
   let hud = yield openConsole();
 
   hud.jsterm.clearOutput();
   hud.jsterm.execute("window.location.href");
 
   info("wait for window.location.href");
--- a/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
+++ b/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
@@ -50,17 +50,18 @@ let test = asyncTest(function* () {
   message.scrollIntoView();
 
   yield waitForContextMenu(menu, message, () => {
     let isHidden = menu.querySelector(CONTEXT_MENU_ID).hidden;
     ok(isHidden, CONTEXT_MENU_ID + " is hidden");
   });
 
   hud.jsterm.clearOutput();
-  content.location.reload(); // Reloading will produce network logging
+  // Reloading will produce network logging
+  content.location.reload();
 
   // Test that the "Copy Link Location" command is enabled and works
   // as expected for any network-related message.
   // This command should copy only the URL.
   [result] = yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "test-console.html",
@@ -75,20 +76,25 @@ let test = asyncTest(function* () {
 
   goUpdateCommand(COMMAND_NAME);
   ok(isEnabled(), COMMAND_NAME + " is enabled");
 
   info("expected clipboard value: " + message.url);
 
   let deferred = promise.defer();
 
-  waitForClipboard((aData) => { return aData.trim() == message.url; },
-    () => { goDoCommand(COMMAND_NAME); },
-    () => { deferred.resolve(null); },
-    () => { deferred.reject(null); });
+  waitForClipboard((aData) => {
+    return aData.trim() == message.url;
+  }, () => {
+    goDoCommand(COMMAND_NAME);
+  }, () => {
+    deferred.resolve(null);
+  }, () => {
+    deferred.reject(null);
+  });
 
   yield deferred.promise;
 
   // Test that the "Copy Link Location" menu item is visible for network-related
   // messages.
   message.scrollIntoView();
 
   yield waitForContextMenu(menu, message, () => {
--- a/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
+++ b/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
@@ -3,17 +3,18 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Test that makes sure messages are not considered repeated when console.log()
 // is invoked with different objects, see bug 865288.
 
 "use strict";
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-repeated-messages.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                 "test/test-repeated-messages.html";
 
 let test = asyncTest(function* () {
   yield loadTab(TEST_URI);
   let hud = yield openConsole();
 
   info("waiting for 3 console.log objects");
 
   hud.jsterm.clearOutput(true);
@@ -41,18 +42,17 @@ let test = asyncTest(function* () {
     let msg = msgs[i];
     let clickable = msg.querySelector(".message-body a");
     ok(clickable, "clickable object #" + i);
 
     msg.scrollIntoView(false);
     yield clickObject(clickable, i);
   }
 
-  function* clickObject(obj, i)
-  {
+  function* clickObject(obj, i) {
     executeSoon(() => {
       EventUtils.synthesizeMouse(obj, 2, 2, {}, hud.iframeWindow);
     });
 
     let varView = yield hud.jsterm.once("variablesview-fetched");
     ok(varView, "variables view fetched #" + i);
 
     yield findVariableViewProperties(varView, [
--- a/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
+++ b/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
@@ -3,20 +3,20 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Check that the variables view sidebar can be closed by pressing Escape in the
 // web console.
 
 "use strict";
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                 "test/test-eval-in-stackframe.html";
 
-function test()
-{
+function test() {
   let hud;
 
   Task.spawn(runner).then(finishTest);
 
   function* runner() {
     let {tab} = yield loadTab(TEST_URI);
     hud = yield openConsole(tab);
     let jsterm = hud.jsterm;
--- a/browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js
+++ b/browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js
@@ -3,22 +3,24 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Check that users can inspect objects logged from cross-domain iframes -
 // bug 869003.
 
 "use strict";
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-869003-top-window.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                 "test/test-bug-869003-top-window.html";
 
 let test = asyncTest(function* () {
   // This test is slightly more involved: it opens the web console, then the
   // variables view for a given object, it updates a property in the view and
-  // checks the result. We can get a timeout with debug builds on slower machines.
+  // checks the result. We can get a timeout with debug builds on slower
+  // machines.
   requestLongerTimeout(2);
 
   yield loadTab("data:text/html;charset=utf8,<p>hello");
   let hud = yield openConsole();
 
   content.location = TEST_URI;
 
   let [result] = yield waitForMessages({
@@ -38,17 +40,17 @@ let test = asyncTest(function* () {
   let body = msg.querySelector(".message-body");
   ok(body, "message body");
 
   let clickable = result.clickableElements[0];
   ok(clickable, "clickable object found");
   ok(body.textContent.includes('{ hello: "world!",'), "message text check");
 
   executeSoon(() => {
-    EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow)
+    EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
   });
 
   let aVar = yield hud.jsterm.once("variablesview-fetched");
   ok(aVar, "variables view fetched");
   ok(aVar._variablesView, "variables view object");
 
   [result] = yield findVariableViewProperties(aVar, [
     { name: "hello", value: "world!" },
--- a/browser/devtools/webconsole/test/browser_bug_871156_ctrlw_close_tab.js
+++ b/browser/devtools/webconsole/test/browser_bug_871156_ctrlw_close_tab.js
@@ -49,17 +49,16 @@ let test = asyncTest(function* () {
     toolboxDestroyed.resolve(null);
   });
 
   // Get out of the web console initialization.
   executeSoon(() => {
     EventUtils.synthesizeKey("w", { accelKey: true });
   });
 
-
   yield promise.all([tabClosed.promise, toolboxDestroyed.promise,
                      tabSelected.promise]);
   info("promise.all resolved. start testing the Browser Console");
 
   hud = yield HUDService.toggleBrowserConsole();
   ok(hud, "Browser Console opened");
 
   let deferred = promise.defer();
--- a/browser/devtools/webconsole/test/browser_cached_messages.js
+++ b/browser/devtools/webconsole/test/browser_cached_messages.js
@@ -1,28 +1,30 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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 see if the cached messages are displayed when the console UI is opened.
+// Test to see if the cached messages are displayed when the console UI is
+// opened.
+
+"use strict";
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-webconsole-error-observer.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                 "test/test-webconsole-error-observer.html";
 
-function test()
-{
+function test() {
   waitForExplicitFinish();
 
   expectUncaughtException();
 
   loadTab(TEST_URI).then(testOpenUI);
 }
 
-function testOpenUI(aTestReopen)
-{
+function testOpenUI(aTestReopen) {
   openConsole().then((hud) => {
     waitForMessages({
       webconsole: hud,
       messages: [
         {
           text: "log Bazzle",
           category: CATEGORY_WEBDEV,
           severity: SEVERITY_LOG,
--- a/browser/devtools/webconsole/test/browser_console.js
+++ b/browser/devtools/webconsole/test/browser_console.js
@@ -1,16 +1,19 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Test the basic features of the Browser Console, bug 587757.
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html?" + Date.now();
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                 "test/test-console.html?" + Date.now();
 
 const TEST_XHR_ERROR_URI = `http://example.com/404.html?${Date.now()}`;
 
 "use strict";
 
 let test = asyncTest(function*() {
   yield loadTab(TEST_URI);
 
@@ -22,18 +25,17 @@ let test = asyncTest(function*() {
   EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, window);
 
   hud = yield opened;
   ok(hud, "browser console opened");
 
   yield consoleOpened(hud);
 });
 
-function consoleOpened(hud)
-{
+function consoleOpened(hud) {
   hud.jsterm.clearOutput(true);
 
   expectUncaughtException();
   executeSoon(() => {
     foobarExceptionBug587757();
   });
 
   // Add a message from a chrome window.
@@ -48,17 +50,19 @@ function consoleOpened(hud)
   // Check for network requests.
   let xhr = new XMLHttpRequest();
   xhr.onload = () => console.log("xhr loaded, status is: " + xhr.status);
   xhr.open("get", TEST_URI, true);
   xhr.send();
 
   // Check for xhr error.
   let xhrErr = new XMLHttpRequest();
-  xhrErr.onload = () => console.log("xhr error loaded, status is: " + xhrErr.status);
+  xhrErr.onload = () => {
+    console.log("xhr error loaded, status is: " + xhrErr.status);
+  };
   xhrErr.open("get", TEST_XHR_ERROR_URI, true);
   xhrErr.send();
 
   return waitForMessages({
     webconsole: hud,
     messages: [
       {
         name: "chrome window console.log() is displayed",
--- a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
+++ b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
@@ -6,18 +6,17 @@
 // Check that exceptions from scripts loaded with the addon-sdk loader are
 // opened correctly in View Source from the Browser Console.
 // See bug 866950.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf8,<p>hello world from bug 866950";
 
-function test()
-{
+function test() {
   requestLongerTimeout(2);
 
   let webconsole, browserconsole;
 
   Task.spawn(runner).then(finishTest);
 
   function* runner() {
     let {tab} = yield loadTab(TEST_URI);
@@ -25,17 +24,18 @@ function test()
     ok(webconsole, "web console opened");
 
     browserconsole = yield HUDService.toggleBrowserConsole();
     ok(browserconsole, "browser console opened");
 
     // Cause an exception in a script loaded with the addon-sdk loader.
     let toolbox = gDevTools.getToolbox(webconsole.target);
     let oldPanels = toolbox._toolPanels;
-    toolbox._toolPanels = {}; // non-iterable
+    // non-iterable
+    toolbox._toolPanels = {};
 
     function fixToolbox() {
       toolbox._toolPanels = oldPanels;
     }
 
     info("generate exception and wait for message");
 
     executeSoon(() => {
--- a/browser/devtools/webconsole/test/browser_console_clear_on_reload.js
+++ b/browser/devtools/webconsole/test/browser_console_clear_on_reload.js
@@ -5,17 +5,18 @@
 
 // Check that clear output on page reload works - bug 705921.
 // Check that clear output and page reload remove the sidebar - bug 971967.
 
 "use strict";
 
 let test = asyncTest(function*() {
   const PREF = "devtools.webconsole.persistlog";
-  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                   "test/test-console.html";
 
   Services.prefs.setBoolPref(PREF, false);
   registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
 
   yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
   ok(hud, "Web Console opened");
--- a/browser/devtools/webconsole/test/browser_console_click_focus.js
+++ b/browser/devtools/webconsole/test/browser_console_click_focus.js
@@ -2,17 +2,18 @@
 /* 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/. */
 
 // Tests that the input field is focused when the console is opened.
 
 "use strict";
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                 "test/test-console.html";
 
 let test = asyncTest(function*() {
   yield loadTab(TEST_URI);
   let hud = yield openConsole();
 
   let [result] = yield waitForMessages({
     webconsole: hud,
     messages: [{
@@ -27,27 +28,27 @@ let test = asyncTest(function*() {
   ok(outputItem, "found a logged message");
 
   let inputNode = hud.jsterm.inputNode;
   ok(inputNode.getAttribute("focused"), "input node is focused, first");
 
   let lostFocus = () => {
     inputNode.removeEventListener("blur", lostFocus);
     info("input node lost focus");
-  }
+  };
 
   inputNode.addEventListener("blur", lostFocus);
 
   document.getElementById("urlbar").click();
 
   ok(!inputNode.getAttribute("focused"), "input node is not focused");
 
   EventUtils.sendMouseEvent({type: "click"}, hud.outputNode);
 
-  ok(inputNode.getAttribute("focused"), "input node is focused, second time")
+  ok(inputNode.getAttribute("focused"), "input node is focused, second time");
 
   // test click-drags are not focusing the input element.
   EventUtils.sendMouseEvent({type: "mousedown", clientX: 3, clientY: 4},
     outputItem);
   EventUtils.sendMouseEvent({type: "click", clientX: 15, clientY: 5},
     outputItem);
 
   todo(!inputNode.getAttribute("focused"), "input node is not focused after drag");
--- a/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
+++ b/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
@@ -9,20 +9,21 @@
 
 function onNewMessage(aEvent, aNewMessages) {
   for (let msg of aNewMessages) {
     // Messages that shouldn't be output contain the substring FAIL_TEST
     if (msg.node.textContent.includes("FAIL_TEST")) {
       ok(false, "Message shouldn't have been output: " + msg.node.textContent);
     }
   }
-};
+}
 
 add_task(function*() {
-  let storage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(Ci.nsIConsoleAPIStorage);
+  let consoleStorage = Cc["@mozilla.org/consoleAPI-storage;1"];
+  let storage = consoleStorage.getService(Ci.nsIConsoleAPIStorage);
   storage.clearEvents();
 
   let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
   console.log("bug861338-log-cached");
 
   let hud = yield HUDService.toggleBrowserConsole();
 
   yield waitForMessages({
@@ -142,18 +143,19 @@ add_task(function*() {
   yield findVariableViewProperties(varView, [{
     name: "bug851231prop",
     value: "bug851231value",
   }], { webconsole: hud });
 
   yield HUDService.toggleBrowserConsole();
 });
 
-add_task(function* test_prefix() {
-  let storage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(Ci.nsIConsoleAPIStorage);
+add_task(function* testPrefix() {
+  let consoleStorage = Cc["@mozilla.org/consoleAPI-storage;1"];
+  let storage = consoleStorage.getService(Ci.nsIConsoleAPIStorage);
   storage.clearEvents();
 
   let {ConsoleAPI} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
   let consoleOptions = {
     maxLogLevel: "error",
     prefix: "Log Prefix",
   };
   let console2 = new ConsoleAPI(consoleOptions);
@@ -172,18 +174,19 @@ add_task(function* test_prefix() {
     }],
   });
 
   hud.jsterm.clearOutput(true);
   hud.ui.off("new-messages", onNewMessage);
   yield HUDService.toggleBrowserConsole();
 });
 
-add_task(function* test_maxLogLevelPref_missing() {
-  let storage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(Ci.nsIConsoleAPIStorage);
+add_task(function* testMaxLogLevelPrefMissing() {
+  let consoleStorage = Cc["@mozilla.org/consoleAPI-storage;1"];
+  let storage = consoleStorage.getService(Ci.nsIConsoleAPIStorage);
   storage.clearEvents();
 
   let {ConsoleAPI} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
   let consoleOptions = {
     maxLogLevel: "error",
     maxLogLevelPref: "testing.maxLogLevel",
   };
   let console = new ConsoleAPI(consoleOptions);
@@ -210,18 +213,19 @@ add_task(function* test_maxLogLevelPref_
     }],
   });
 
   hud.jsterm.clearOutput(true);
   hud.ui.off("new-messages", onNewMessage);
   yield HUDService.toggleBrowserConsole();
 });
 
-add_task(function* test_maxLogLevelPref() {
-  let storage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(Ci.nsIConsoleAPIStorage);
+add_task(function* testMaxLogLevelPref() {
+  let consoleStorage = Cc["@mozilla.org/consoleAPI-storage;1"];
+  let storage = consoleStorage.getService(Ci.nsIConsoleAPIStorage);
   storage.clearEvents();
 
   let {ConsoleAPI} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
   let consoleOptions = {
     maxLogLevel: "error",
     maxLogLevelPref: "testing.maxLogLevel",
   };
 
--- a/browser/devtools/webconsole/test/browser_console_copy_command.js
+++ b/browser/devtools/webconsole/test/browser_console_copy_command.js
@@ -1,52 +1,58 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Tests that the `copy` console helper works as intended.
 
+"use strict";
+
 let gWebConsole, gJSTerm;
 
-let TEXT =  "Lorem ipsum dolor sit amet, consectetur adipisicing " +
+let TEXT = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
     "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
     "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
     "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
     "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
     "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
     "proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +
     new Date();
 
 let ID = "select-me";
 
 add_task(function* init() {
   yield loadTab("data:text/html;charset=utf-8," +
                 "<body>" +
                 "  <div>" +
                 "    <h1>Testing copy command</h1>" +
                 "    <p>This is some example text</p>" +
-                "    <p id='select-me'>"+TEXT+"</p>" +
+                "    <p id='select-me'>" + TEXT + "</p>" +
                 "  </div>" +
                 "  <div><p></p></div>" +
                 "</body>");
 
   gWebConsole = yield openConsole();
   gJSTerm = gWebConsole.jsterm;
 });
 
-add_task(function* test_copy() {
+add_task(function* testCopy() {
   let RANDOM = Math.random();
   let string = "Text: " + RANDOM;
   let obj = {a: 1, b: "foo", c: RANDOM};
 
-  let samples = [[RANDOM, RANDOM],
-                 [JSON.stringify(string), string],
-                 [obj.toSource(),  JSON.stringify(obj, null, "  ")],
-                 ["$('#" + ID + "')", content.document.getElementById(ID).outerHTML]
+  let samples = [
+                  [RANDOM, RANDOM],
+                  [JSON.stringify(string), string],
+                  [obj.toSource(), JSON.stringify(obj, null, "  ")],
+                  [
+                    "$('#" + ID + "')",
+                    content.document.getElementById(ID).outerHTML
+                  ]
                 ];
   for (let [source, reference] of samples) {
     let deferredResult = promise.defer();
 
     SimpleTest.waitForClipboard(
       "" + reference,
       () => {
         let command = "copy(" + source + ")";
--- a/browser/devtools/webconsole/test/browser_console_copy_entire_message_context_menu.js
+++ b/browser/devtools/webconsole/test/browser_console_copy_entire_message_context_menu.js
@@ -6,18 +6,18 @@
 // Test copying of the entire console message when right-clicked
 // with no other text selected. See Bug 1100562.
 
 function test() {
   let hud;
   let outputNode;
   let contextMenu;
 
-  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
-  const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
+  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                   "test/test-console.html";
 
   Task.spawn(runner).then(finishTest);
 
   function* runner() {
     const {tab} = yield loadTab(TEST_URI);
     hud = yield openConsole(tab);
     outputNode = hud.outputNode;
     contextMenu = hud.iframeWindow.document.getElementById("output-contextmenu");
@@ -37,28 +37,32 @@ function test() {
         severity: SEVERITY_LOG,
       }]
     });
 
     outputNode.focus();
     let message = [...results.matched][0];
     message.scrollIntoView();
 
-    yield waitForContextMenu(contextMenu, message, copyFromPopup, testContextMenuCopy);
+    yield waitForContextMenu(contextMenu, message, copyFromPopup,
+                             testContextMenuCopy);
 
     function copyFromPopup() {
       let copyItem = contextMenu.querySelector("#cMenu_copy");
       copyItem.doCommand();
 
-      let controller = top.document.commandDispatcher.getControllerForCommand("cmd_copy");
+      let controller = top.document.commandDispatcher
+                                   .getControllerForCommand("cmd_copy");
       is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
     }
 
     function testContextMenuCopy() {
-      waitForClipboard((str) => { return message.textContent.trim() == str.trim(); },
-        () => { goDoCommand("cmd_copy") },
-        () => {}, () => {}
+      waitForClipboard((str) => {
+        return message.textContent.trim() == str.trim();
+      }, () => {
+        goDoCommand("cmd_copy");
+      }, () => {}, () => {}
       );
     }
 
     yield closeConsole(tab);
   }
-}
\ No newline at end of file
+}
--- a/browser/devtools/webconsole/test/browser_console_dead_objects.js
+++ b/browser/devtools/webconsole/test/browser_console_dead_objects.js
@@ -11,43 +11,43 @@
 // - closes the tab,
 // - tries to use the object that was pointing to the now-defunct content
 // document. This is the dead object.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf8,<p>dead objects!";
 
-function test()
-{
+function test() {
   let hud = null;
 
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref("devtools.chrome.enabled");
   });
 
   Task.spawn(runner).then(finishTest);
 
   function* runner() {
     Services.prefs.setBoolPref("devtools.chrome.enabled", true);
-    let {tab} = yield loadTab(TEST_URI);
+    yield loadTab(TEST_URI);
 
     info("open the browser console");
 
     hud = yield HUDService.toggleBrowserConsole();
     ok(hud, "browser console opened");
 
     let jsterm = hud.jsterm;
 
     jsterm.clearOutput();
 
     // Add the reference to the content document.
     yield jsterm.execute("Cu = Components.utils;" +
                   "Cu.import('resource://gre/modules/Services.jsm');" +
-                  "chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');" +
+                  "chromeWindow = Services.wm.getMostRecentWindow('" +
+                  "navigator:browser');" +
                   "foobarzTezt = chromeWindow.content.document;" +
                   "delete chromeWindow");
 
     gBrowser.removeCurrentTab();
 
     let msg = yield jsterm.execute("foobarzTezt");
 
     isnot(hud.outputNode.textContent.indexOf("[object DeadObject]"), -1,
--- a/browser/devtools/webconsole/test/browser_console_error_source_click.js
+++ b/browser/devtools/webconsole/test/browser_console_error_source_click.js
@@ -1,29 +1,29 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Check that JS errors and CSS warnings open view source when their source link
 // is clicked in the Browser Console. See bug 877778.
 
+"use strict";
+
 const TEST_URI = "data:text/html;charset=utf8,<p>hello world from bug 877778 " +
                  "<button onclick='foobar.explode()' " +
                  "style='test-color: green-please'>click!</button>";
-function test()
-{
+function test() {
   let hud;
 
   loadTab(TEST_URI).then(() => {
     HUDService.toggleBrowserConsole().then(browserConsoleOpened);
   });
 
-  function browserConsoleOpened(aHud)
-  {
+  function browserConsoleOpened(aHud) {
     hud = aHud;
     ok(hud, "browser console opened");
 
     let button = content.document.querySelector("button");
     ok(button, "button element found");
 
     info("generate exception and wait for the message");
     executeSoon(() => {
@@ -43,18 +43,17 @@ function test()
           text: "Unknown property 'test-color'",
           category: CATEGORY_CSS,
           severity: SEVERITY_WARNING,
         },
       ],
     }).then(onMessageFound);
   }
 
-  function onMessageFound(results)
-  {
+  function onMessageFound(results) {
     let viewSource = hud.viewSource;
     let viewSourceCalled = false;
     hud.viewSourceInDebugger = () => viewSourceCalled = true;
 
     for (let result of results) {
       viewSourceCalled = false;
 
       let msg = [...results[0].matched][0];
--- a/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js
+++ b/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js
@@ -1,105 +1,110 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 /*
- * Bug 922161 - hide Browser Console JS input field if devtools.chrome.enabled is false
+ * Bug 922161 - Hide Browser Console JS input field if devtools.chrome.enabled
+ * is false.
  * when devtools.chrome.enabled then
  *   -browser console jsterm should be enabled
  *   -browser console object inspector properties should be set.
  *   -webconsole jsterm should be enabled
  *   -webconsole object inspector properties should be set.
  *
  * when devtools.chrome.enabled == false then
  *   -browser console jsterm should be disabled
  *   -browser console object inspector properties should not be set.
  *   -webconsole jsterm should be enabled
  *   -webconsole object inspector properties should be set.
  */
 
+"use strict";
+
 function testObjectInspectorPropertiesAreNotSet(variablesView) {
   is(variablesView.eval, null, "vview.eval is null");
   is(variablesView.switch, null, "vview.switch is null");
   is(variablesView.delete, null, "vview.delete is null");
 }
 
 function* getVariablesView(hud) {
   function openVariablesView(event, vview) {
     deferred.resolve(vview._variablesView);
   }
 
   let deferred = promise.defer();
   hud.jsterm.clearOutput();
-  hud.jsterm.execute('new Object()');
+  hud.jsterm.execute("new Object()");
 
   let [message] = yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "Object",
       category: CATEGORY_OUTPUT,
     }],
-  })
+  });
 
   hud.jsterm.once("variablesview-fetched", openVariablesView);
 
   let anchor = [...message.matched][0].querySelector("a");
 
   executeSoon(() =>
     EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow)
   );
 
   return deferred.promise;
 }
 
 function testJSTermIsVisible(hud) {
-  let inputContainer = hud.ui.window.document.querySelector(".jsterm-input-container");
+  let inputContainer = hud.ui.window.document
+                                    .querySelector(".jsterm-input-container");
   isnot(inputContainer.style.display, "none", "input is visible");
 }
 
 function testObjectInspectorPropertiesAreSet(variablesView) {
   isnot(variablesView.eval, null, "vview.eval is set");
   isnot(variablesView.switch, null, "vview.switch is set");
   isnot(variablesView.delete, null, "vview.delete is set");
 }
 
 function testJSTermIsNotVisible(hud) {
-  let inputContainer = hud.ui.window.document.querySelector(".jsterm-input-container");
+  let inputContainer = hud.ui.window.document
+                                    .querySelector(".jsterm-input-container");
   is(inputContainer.style.display, "none", "input is not visible");
 }
 
 function* testRunner() {
   let browserConsole, webConsole, variablesView;
 
   Services.prefs.setBoolPref("devtools.chrome.enabled", true);
 
   browserConsole = yield HUDService.toggleBrowserConsole();
   variablesView = yield getVariablesView(browserConsole);
   testJSTermIsVisible(browserConsole);
   testObjectInspectorPropertiesAreSet(variablesView);
 
   let {tab: browserTab} = yield loadTab("data:text/html;charset=utf8,hello world");
   webConsole = yield openConsole(browserTab);
   variablesView = yield getVariablesView(webConsole);
-  testJSTermIsVisible(webConsole)
-  testObjectInspectorPropertiesAreSet(variablesView)
+  testJSTermIsVisible(webConsole);
+  testObjectInspectorPropertiesAreSet(variablesView);
   yield closeConsole(browserTab);
 
   yield HUDService.toggleBrowserConsole();
   Services.prefs.setBoolPref("devtools.chrome.enabled", false);
 
   browserConsole = yield HUDService.toggleBrowserConsole();
   variablesView = yield getVariablesView(browserConsole);
   testJSTermIsNotVisible(browserConsole);
   testObjectInspectorPropertiesAreNotSet(variablesView);
 
   webConsole = yield openConsole(browserTab);
   variablesView = yield getVariablesView(webConsole);
-  testJSTermIsVisible(webConsole)
-  testObjectInspectorPropertiesAreSet(variablesView)
+  testJSTermIsVisible(webConsole);
+  testObjectInspectorPropertiesAreSet(variablesView);
   yield closeConsole(browserTab);
 }
 
 function test() {
   Task.spawn(testRunner).then(finishTest);
 }
--- a/browser/devtools/webconsole/test/browser_console_history_persist.js
+++ b/browser/devtools/webconsole/test/browser_console_history_persist.js
@@ -3,90 +3,102 @@
  * 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 that console command input is persisted across toolbox loads.
 // See Bug 943306.
 
 "use strict";
 
-const TEST_URI = "data:text/html;charset=utf-8,Web Console test for persisting history - bug 943306";
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
+                 "persisting history - bug 943306";
 const INPUT_HISTORY_COUNT = 10;
 
 let test = asyncTest(function* () {
-  info ("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
-  Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount", INPUT_HISTORY_COUNT);
+  info("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
+  Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount",
+                            INPUT_HISTORY_COUNT);
 
   // First tab: run a bunch of commands and then make sure that you can
   // navigate through their history.
   yield loadTab(TEST_URI);
   let hud1 = yield openConsole();
-  is (JSON.stringify(hud1.jsterm.history), "[]", "No history on first tab initially");
+  is(JSON.stringify(hud1.jsterm.history), "[]",
+     "No history on first tab initially");
   yield populateInputHistory(hud1);
-  is (JSON.stringify(hud1.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
-    "First tab has populated history");
+  is(JSON.stringify(hud1.jsterm.history),
+     '["0","1","2","3","4","5","6","7","8","9"]',
+     "First tab has populated history");
 
   // Second tab: Just make sure that you can navigate through the history
   // generated by the first tab.
   yield loadTab(TEST_URI);
   let hud2 = yield openConsole();
-  is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
-    "Second tab has populated history");
+  is(JSON.stringify(hud2.jsterm.history),
+     '["0","1","2","3","4","5","6","7","8","9"]',
+     "Second tab has populated history");
   yield testNaviatingHistoryInUI(hud2);
-  is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9",""]',
-    "An empty entry has been added in the second tab due to history perusal");
+  is(JSON.stringify(hud2.jsterm.history),
+     '["0","1","2","3","4","5","6","7","8","9",""]',
+     "An empty entry has been added in the second tab due to history perusal");
 
   // Third tab: Should have the same history as first tab, but if we run a
   // command, then the history of the first and second shouldn't be affected
   yield loadTab(TEST_URI);
   let hud3 = yield openConsole();
-  is (JSON.stringify(hud3.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
-    "Third tab has populated history");
+  is(JSON.stringify(hud3.jsterm.history),
+     '["0","1","2","3","4","5","6","7","8","9"]',
+     "Third tab has populated history");
 
   // Set input value separately from execute so UP arrow accurately navigates history.
   hud3.jsterm.setInputValue('"hello from third tab"');
   hud3.jsterm.execute();
 
-  is (JSON.stringify(hud1.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
-    "First tab history hasn't changed due to command in third tab");
-  is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9",""]',
-    "Second tab history hasn't changed due to command in third tab");
-  is (JSON.stringify(hud3.jsterm.history), '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
-    "Third tab has updated history (and purged the first result) after running a command");
+  is(JSON.stringify(hud1.jsterm.history),
+     '["0","1","2","3","4","5","6","7","8","9"]',
+     "First tab history hasn't changed due to command in third tab");
+  is(JSON.stringify(hud2.jsterm.history),
+     '["0","1","2","3","4","5","6","7","8","9",""]',
+     "Second tab history hasn't changed due to command in third tab");
+  is(JSON.stringify(hud3.jsterm.history),
+     '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
+     "Third tab has updated history (and purged the first result) after " +
+     "running a command");
 
   // Fourth tab: Should have the latest command from the third tab, followed
   // by the rest of the history from the first tab.
   yield loadTab(TEST_URI);
   let hud4 = yield openConsole();
-  is (JSON.stringify(hud4.jsterm.history), '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
-    "Fourth tab has most recent history");
+  is(JSON.stringify(hud4.jsterm.history),
+     '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
+     "Fourth tab has most recent history");
 
   yield hud4.jsterm.clearHistory();
-  is (JSON.stringify(hud4.jsterm.history), '[]',
-    "Clearing history for a tab works");
+  is(JSON.stringify(hud4.jsterm.history), '[]',
+     "Clearing history for a tab works");
 
   yield loadTab(TEST_URI);
   let hud5 = yield openConsole();
-  is (JSON.stringify(hud5.jsterm.history), '[]',
-    "Clearing history carries over to a new tab");
+  is(JSON.stringify(hud5.jsterm.history), '[]',
+     "Clearing history carries over to a new tab");
 
-  info ("Clearing custom input history pref");
+  info("Clearing custom input history pref");
   Services.prefs.clearUserPref("devtools.webconsole.inputHistoryCount");
 });
 
 /**
  * Populate the history by running the following commands:
  *  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  */
 function* populateInputHistory(hud) {
   let jsterm = hud.jsterm;
-  let {inputNode} = jsterm;
 
   for (let i = 0; i < INPUT_HISTORY_COUNT; i++) {
-    // Set input value separately from execute so UP arrow accurately navigates history.
+    // Set input value separately from execute so UP arrow accurately navigates
+    // history.
     jsterm.setInputValue(i);
     jsterm.execute();
   }
 }
 
 /**
  * Check pressing up results in history traversal like:
  *  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -670,16 +670,18 @@ you can use these alternative items. Oth
 <!ENTITY spellAddDictionaries.accesskey "A">
 
 <!ENTITY editBookmark.done.label                     "Done">
 <!ENTITY editBookmark.cancel.label                   "Cancel">
 <!ENTITY editBookmark.removeBookmark.accessKey       "R">
 
 <!ENTITY identity.connectionSecure "Secure Connection">
 <!ENTITY identity.connectionNotSecure "Connection is Not Secure">
+<!ENTITY identity.connectionVerified "&brandShortName; verified that you are securely connected to this site, run by:">
+<!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
 
 <!ENTITY identity.moreInfoLinkText2 "More Information">
 
 <!ENTITY identity.permissions "Permissions">
 
 <!-- Name for the tabs toolbar as spoken by screen readers.
      The word "toolbar" is appended automatically and should not be contained below! -->
 <!ENTITY tabsToolbar.label "Browser tabs">
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -36,16 +36,20 @@
 <!ENTITY engine.passwords.accesskey "P">
 <!ENTITY engine.prefs.label         "Preferences">
 <!ENTITY engine.prefs.accesskey     "S">
 <!ENTITY engine.addons.label        "Add-ons">
 <!ENTITY engine.addons.accesskey    "A">
 
 <!-- Device Settings -->
 <!ENTITY syncDeviceName.label       "Device Name:">
+<!ENTITY fxaSyncDeviceName.label       "Device Name">
+<!ENTITY changeSyncDeviceName.label "Change Device Name...">
+<!ENTITY cancelChangeSyncDeviceName.label "Cancel">
+<!ENTITY saveChangeSyncDeviceName.label "Save">
 <!ENTITY syncDeviceName.accesskey   "c">
 <!ENTITY unlinkDevice.label           "Unlink This Device">
 
 <!-- Footer stuff -->
 <!ENTITY prefs.tosLink.label        "Terms of Service">
 <!ENTITY prefs.ppLink.label         "Privacy Policy">
 
 <!-- Firefox Accounts stuff -->
--- a/browser/themes/linux/controlcenter/panel.css
+++ b/browser/themes/linux/controlcenter/panel.css
@@ -1,22 +1,5 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../shared/controlcenter/panel.inc.css
-
-/* Identity popup icons */
-#identity-popup-icon {
-  height: 64px;
-  width: 64px;
-  padding: 0;
-  list-style-image: url("chrome://browser/skin/identity.png");
-  -moz-image-region: rect(0px, 64px, 64px, 0px);
-}
-
-#identity-popup.verifiedDomain > #identity-popup-container > #identity-popup-icon {
-  -moz-image-region: rect(64px, 64px, 128px, 0px);
-}
-
-#identity-popup.verifiedIdentity > #identity-popup-container > #identity-popup-icon {
-  -moz-image-region: rect(128px, 64px, 192px, 0px);
-}
deleted file mode 100644
index f3f790e9efd49151ff45365e8c08713fcef0f232..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -41,17 +41,16 @@ browser.jar:
   skin/classic/browser/fullscreen-darknoise.png
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/Push-16.png
   skin/classic/browser/Push-64.png
   skin/classic/browser/heartbeat-icon.svg                   (../shared/heartbeat-icon.svg)
   skin/classic/browser/heartbeat-star-lit.svg               (../shared/heartbeat-star-lit.svg)
   skin/classic/browser/heartbeat-star-off.svg               (../shared/heartbeat-star-off.svg)
-  skin/classic/browser/identity.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-generic@2x.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https@2x.png
   skin/classic/browser/identity-icons-https-ev.png
   skin/classic/browser/identity-icons-https-ev@2x.png
   skin/classic/browser/identity-icons-https-mixed-active.png
   skin/classic/browser/identity-icons-https-mixed-active@2x.png
@@ -141,16 +140,21 @@ browser.jar:
   skin/classic/browser/webRTC-screen-white-16.png     (../shared/webrtc/screen-white-16.png)
   skin/classic/browser/loop/menuPanel.png             (loop/menuPanel.png)
   skin/classic/browser/loop/menuPanel@2x.png          (loop/menuPanel@2x.png)
   skin/classic/browser/loop/toolbar.png               (loop/toolbar.png)
   skin/classic/browser/loop/toolbar@2x.png            (loop/toolbar@2x.png)
   skin/classic/browser/loop/toolbar-inverted.png      (loop/toolbar-inverted.png)
   skin/classic/browser/loop/toolbar-inverted@2x.png   (loop/toolbar-inverted@2x.png)
 * skin/classic/browser/controlcenter/panel.css        (controlcenter/panel.css)
+  skin/classic/browser/controlcenter/arrow-subview.svg  (../shared/controlcenter/arrow-subview.svg)
+  skin/classic/browser/controlcenter/conn-not-secure.svg  (../shared/controlcenter/conn-not-secure.svg)
+  skin/classic/browser/controlcenter/conn-secure-dv.svg  (../shared/controlcenter/conn-secure-dv.svg)
+  skin/classic/browser/controlcenter/conn-secure-ev.svg  (../shared/controlcenter/conn-secure-ev.svg)
+  skin/classic/browser/controlcenter/permissions.svg  (../shared/controlcenter/permissions.svg)
   skin/classic/browser/customizableui/background-noise-toolbar.png  (customizableui/background-noise-toolbar.png)
   skin/classic/browser/customizableui/customize-illustration.png  (../shared/customizableui/customize-illustration.png)
   skin/classic/browser/customizableui/customize-illustration-rtl.png  (../shared/customizableui/customize-illustration-rtl.png)
   skin/classic/browser/customizableui/customizeMode-gridTexture.png  (customizableui/customizeMode-gridTexture.png)
   skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
   skin/classic/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
   skin/classic/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
   skin/classic/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
--- a/browser/themes/osx/controlcenter/panel.css
+++ b/browser/themes/osx/controlcenter/panel.css
@@ -2,40 +2,8 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../shared/controlcenter/panel.inc.css
 
 #identity-popup {
   margin-top: 1px;
 }
-
-/* Popup Icons */
-#identity-popup-icon {
-  height: 64px;
-  width: 64px;
-  padding: 0;
-  list-style-image: url("chrome://browser/skin/identity.png");
-  -moz-image-region: rect(0px, 64px, 64px, 0px);
-}
-
-#identity-popup.verifiedDomain > #identity-popup-container > #identity-popup-icon {
-  -moz-image-region: rect(64px, 64px, 128px, 0px);
-}
-
-#identity-popup.verifiedIdentity > #identity-popup-container > #identity-popup-icon {
-  -moz-image-region: rect(128px, 64px, 192px, 0px);
-}
-
-@media (min-resolution: 2dppx) {
-  #identity-popup-icon {
-    list-style-image: url("chrome://browser/skin/identity@2x.png");
-    -moz-image-region: rect(0px, 128px, 128px, 0px);
-  }
-
-  #identity-popup.verifiedDomain > #identity-popup-container > #identity-popup-icon {
-    -moz-image-region: rect(128px, 128px, 256px, 0px);
-  }
-
-  #identity-popup.verifiedIdentity > #identity-popup-container > #identity-popup-icon {
-    -moz-image-region: rect(256px, 128px, 384px, 0px);
-  }
-}
deleted file mode 100644
index 99db29c752183a9c74aa6d757299633ed6c471d7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f95b18ae98695daacec9fc5c3b545a858456098e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -45,18 +45,16 @@ browser.jar:
   skin/classic/browser/Geolocation-64@2x.png
   skin/classic/browser/Push-16.png
   skin/classic/browser/Push-16@2x.png
   skin/classic/browser/Push-64.png
   skin/classic/browser/Push-64@2x.png
   skin/classic/browser/heartbeat-icon.svg                   (../shared/heartbeat-icon.svg)
   skin/classic/browser/heartbeat-star-lit.svg               (../shared/heartbeat-star-lit.svg)
   skin/classic/browser/heartbeat-star-off.svg               (../shared/heartbeat-star-off.svg)
-  skin/classic/browser/identity.png
-  skin/classic/browser/identity@2x.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-generic@2x.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https@2x.png
   skin/classic/browser/identity-icons-https-ev.png
   skin/classic/browser/identity-icons-https-ev@2x.png
   skin/classic/browser/identity-icons-https-mixed-active.png
   skin/classic/browser/identity-icons-https-mixed-active@2x.png
@@ -186,16 +184,21 @@ browser.jar:
   skin/classic/browser/loop/toolbar@2x.png            (loop/toolbar@2x.png)
   skin/classic/browser/loop/toolbar-inverted.png      (loop/toolbar-inverted.png)
   skin/classic/browser/loop/toolbar-inverted@2x.png   (loop/toolbar-inverted@2x.png)
   skin/classic/browser/yosemite/loop/menuPanel.png          (loop/menuPanel-yosemite.png)
   skin/classic/browser/yosemite/loop/menuPanel@2x.png       (loop/menuPanel-yosemite@2x.png)
   skin/classic/browser/yosemite/loop/toolbar.png            (loop/toolbar-yosemite.png)
   skin/classic/browser/yosemite/loop/toolbar@2x.png         (loop/toolbar-yosemite@2x.png)
 * skin/classic/browser/controlcenter/panel.css        (controlcenter/panel.css)
+  skin/classic/browser/controlcenter/arrow-subview.svg  (../shared/controlcenter/arrow-subview.svg)
+  skin/classic/browser/controlcenter/conn-not-secure.svg  (../shared/controlcenter/conn-not-secure.svg)
+  skin/classic/browser/controlcenter/conn-secure-dv.svg  (../shared/controlcenter/conn-secure-dv.svg)
+  skin/classic/browser/controlcenter/conn-secure-ev.svg  (../shared/controlcenter/conn-secure-ev.svg)
+  skin/classic/browser/controlcenter/permissions.svg  (../shared/controlcenter/permissions.svg)
   skin/classic/browser/customizableui/background-noise-toolbar.png  (customizableui/background-noise-toolbar.png)
   skin/classic/browser/customizableui/customize-titleBar-toggle.png  (customizableui/customize-titleBar-toggle.png)
   skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png  (customizableui/customize-titleBar-toggle@2x.png)
   skin/classic/browser/customizableui/customize-illustration.png  (../shared/customizableui/customize-illustration.png)
   skin/classic/browser/customizableui/customize-illustration@2x.png  (../shared/customizableui/customize-illustration@2x.png)
   skin/classic/browser/customizableui/customize-illustration-rtl.png  (../shared/customizableui/customize-illustration-rtl.png)
   skin/classic/browser/customizableui/customize-illustration-rtl@2x.png  (../shared/customizableui/customize-illustration-rtl@2x.png)
   skin/classic/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/arrow-subview.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <polygon fill="#808080" points="5,3.5 6.5,2 12.375,8 6.5,14 5,12.5 9.375,8" />
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/conn-not-secure.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+  <circle fill="#a6a6a6" cx="12" cy="12" r="11" />
+  <path transform="translate(1 1)" fill="#fff" d="M8.41648275,2.92371996 C8.38948313,2.92220329 8.35948356,2.92371996 8.33398393,2.93433671 C8.32798401,2.93737006 8.32048413,2.94343677 8.31448421,2.95102016 C8.32348407,2.95102016 8.33398393,2.95102016 8.34148382,2.94950348 C8.36848344,2.94343677 8.38948313,2.92523664 8.41648275,2.92371996 L8.41648275,2.92371996 L8.41648275,2.92371996 Z M8.45098225,4.22654587 C8.48548176,4.18256223 8.40298294,4.14616197 8.35648361,4.14919533 C8.36848344,4.09611162 8.43598246,4.06881142 8.41798272,4.00207762 C8.40148297,3.93231045 8.31748416,3.94596055 8.27248482,3.98387749 C8.23198539,4.01876107 8.21098569,4.07942816 8.17648618,4.12037845 C8.15698646,4.14312862 8.12098698,4.150712 8.10748718,4.1780122 C8.09548735,4.20379571 8.11048713,4.24777936 8.10898715,4.27659623 C8.16448635,4.28417962 8.22298552,4.26749616 8.26348494,4.22806255 L8.29048456,4.21592913 C8.28448464,4.22047916 8.28148468,4.23109591 8.27848473,4.23716262 C8.30548433,4.27052952 8.42548262,4.26142946 8.45098225,4.22654587 L8.45098225,4.22654587 L8.45098225,4.22654587 Z M8.50798144,2.36558267 C8.50498148,2.44748325 8.58448033,2.45658332 8.64297949,2.48843354 C8.62497975,2.53545054 8.56048068,2.53393387 8.53048111,2.5718508 C8.49448162,2.6188678 8.56048068,2.65981809 8.59348021,2.68105158 C8.65797929,2.72048519 8.6219798,2.76598551 8.60997998,2.82210258 C8.59198024,2.9009698 8.7614778,2.87973632 8.79897727,2.87821964 C8.86347635,2.87518629 8.96547488,2.88580303 9.02697399,2.85546948 C9.09297304,2.81906922 9.12747255,2.73868532 9.19647157,2.6992517 C9.25347076,2.6658848 9.33446958,2.64768467 9.39746869,2.67043483 C9.46346774,2.693185 9.45596785,2.77811893 9.50996707,2.81451919 C9.57296618,2.85850283 9.64196518,2.87215293 9.6959644,2.8069358 C9.73046391,2.76598551 9.80696281,2.71441848 9.80996278,2.67043483 C9.81596269,2.59308428 9.83846236,2.53241719 9.92396114,2.51573374 C9.99296016,2.50208364 9.97796036,2.56881745 10.0259597,2.58095087 C10.1324581,2.60825106 10.1834574,2.28368209 10.2989558,2.37923277 C10.3259554,2.40198293 10.3334553,2.48995022 10.3799546,2.48236683 C10.4279539,2.47478345 10.4294539,2.40501628 10.4804532,2.40349961 C10.4969529,2.45051661 10.3919544,2.50815035 10.3784546,2.55971738 C10.4429537,2.50663367 10.4744532,2.51421706 10.5479522,2.50663367 C10.5674519,2.55668403 10.423454,2.63858461 10.3859545,2.64616799 C10.3334553,2.65981809 10.3019557,2.62948454 10.2614563,2.65830141 C10.2299568,2.6795349 10.1864574,2.67801822 10.1489579,2.68105158 C10.0964587,2.68711829 9.99746008,2.75688545 9.99896007,2.81451919 C9.99896007,2.83726935 10.0169598,2.88883638 9.99746008,2.90703651 C9.97946034,2.92675332 9.93596097,2.90855319 9.93146103,2.88883638 C9.88646168,2.95557019 9.82796252,2.83878603 9.79046305,2.93282003 C9.85046218,2.9479868 9.9059614,3.00562055 9.97346042,3.022304 C10.0394595,3.03898745 10.1054585,3.0556709 10.1699576,3.07387103 C10.279456,3.10723793 10.4429537,2.975287 10.5269525,2.91158654 C10.6064514,2.85243612 10.7069499,2.71896851 10.7279496,2.62341783 C10.7519493,2.51876709 10.8659476,2.39894957 10.841948,2.29581551 C10.8209483,2.19874815 10.8059485,2.15324783 10.9154469,2.11836425 C10.9619463,2.10319748 11.0744446,2.07893064 11.0909444,2.02584693 C11.1149441,1.9469797 10.8674476,1.96821318 10.8299482,1.95001306 C10.7054499,1.89389599 10.6514507,1.83171222 10.5074528,1.88782928 C10.4324539,1.91664615 10.3589549,1.94091299 10.280956,1.96214647 C10.2404566,1.97276322 10.1999572,1.97579657 10.1774575,2.01068015 C10.1684576,2.02433025 10.1564578,2.03494699 10.141458,2.04253038 C10.0769589,2.06831389 10.1564578,1.94546302 10.1639577,1.93787964 C10.1849574,1.9136128 10.2194569,1.8392956 10.1519579,1.85597906 C10.0529593,1.87872922 9.98096031,2.03039696 9.87446184,2.03798035 C9.79346301,2.04404706 9.81896264,1.97276322 9.83996235,1.93181293 C9.88046177,1.85749573 9.76496342,1.84839567 9.71396414,1.84839567 C9.64196518,1.84839567 9.58796595,1.88934596 9.51896694,1.89692935 C9.45446788,1.90299606 9.37946895,1.91512947 9.31496987,1.9136128 C9.18597171,1.90906277 9.10047295,1.98489664 8.97297477,1.94242967 C8.8379767,1.89844602 8.69247879,2.01068015 8.56198067,2.02584693 C8.51848128,2.03191364 8.45548219,2.02281357 8.43748245,2.0743806 C8.42248266,2.11684757 8.43748245,2.1820647 8.47048197,2.21391493 L8.48098182,2.20481486 C8.45248223,2.23363173 8.44948228,2.27458203 8.40748288,2.2897488 C8.36698346,2.3033989 8.32648404,2.35648261 8.30548433,2.39288286 C8.28898458,2.42018306 8.24248525,2.53393387 8.32348407,2.47478345 C8.38198324,2.4307998 8.41498277,2.34889922 8.50798144,2.36558267 L8.50798144,2.36558267 L8.50798144,2.36558267 Z M3.65405101,9.54705029 C3.516053,9.44694958 3.20255749,9.42268274 3.24155692,9.18911442 C3.26555658,9.04654674 3.41255447,8.93582929 3.53105277,8.86606213 C3.68705055,8.77506148 3.86104805,8.77202812 4.03654553,8.7871949 C4.07854493,8.79174493 4.15804379,8.78416154 4.18054347,8.81752845 C4.19254329,8.8342119 4.21654295,8.84482864 4.23604266,8.85089535 C4.282542,8.86454545 4.33054131,8.86606213 4.37854064,8.87516219 C4.4505396,8.88881229 4.50153886,8.95099606 4.57353783,8.89942903 C4.65603664,8.84179529 4.66953646,8.82966187 4.77003502,8.84179529 C4.86003373,8.85241203 4.91403295,8.78416154 4.98903188,8.79022825 C5.01303154,8.79174493 5.03403123,8.79629496 5.05203097,8.80387835 C5.05953087,8.77809483 5.07003071,8.75534467 5.08653048,8.75079464 C5.12402995,8.7401779 5.20652875,8.83117854 5.2455282,8.83876193 C5.34452678,8.85999542 5.33852687,8.79022825 5.34602676,8.71894441 C5.39552604,8.70984435 5.4195257,8.77809483 5.46452506,8.73562787 C5.46302509,8.74927796 5.47202495,8.77051145 5.47202495,8.78416154 C5.48102483,8.79022825 5.49152468,8.79022825 5.50052454,8.78264487 C5.50502448,8.77506148 5.50652446,8.76747809 5.50352451,8.75837803 C5.52752416,8.76596142 5.53952399,8.74927796 5.54252394,8.72046109 C5.56052368,8.72197777 5.58902327,8.71136103 5.60702302,8.71439438 C5.62052282,8.66282735 5.6505224,8.59457687 5.61302293,8.54755987 C5.62202281,8.54604319 5.63252265,8.54300983 5.6430225,8.54149316 C5.6430225,8.48992612 5.67752201,8.46717596 5.67902198,8.42622567 C5.62502276,8.41864228 5.56502362,8.42167564 5.50952442,8.42319232 C5.54252394,8.39285877 5.62202281,8.32612496 5.6295227,8.28517467 C5.64452249,8.21237415 5.55002384,8.16839051 5.55752373,8.0819399 C5.56652359,8.12744022 5.61752287,8.23209096 5.66552218,8.24725773 C5.77352063,8.28365799 5.74202108,8.1759739 5.74952097,8.11985683 C5.77652059,7.94240557 5.93551831,8.08952328 5.93851826,8.18204061 C5.9850176,8.07587319 6.10051595,8.19417402 6.0480167,8.28517467 C6.02251706,8.32915832 5.98801756,8.31247486 6.01951711,8.37010861 C6.04201679,8.4110589 6.07501631,8.41257557 6.12301562,8.40195883 C6.13501544,8.37769199 6.1455153,8.3503918 6.1455153,8.32157493 C6.22801412,8.29579141 6.28201334,8.39437545 6.23401403,8.45352586 C6.29551316,8.42015896 6.35851224,8.38830874 6.4245113,8.37162528 C6.38551186,8.23512432 6.34501244,8.1016567 6.37051207,7.95605567 C6.37651198,7.92420544 6.38101192,7.88780518 6.40501158,7.86353835 C6.43501115,7.83168812 6.39751169,7.84382154 6.39451173,7.82258806 C6.38551186,7.7588876 6.45301089,7.69367047 6.47851052,7.63603673 C6.40801154,7.62086996 6.47251061,7.48133563 6.51900994,7.4570688 C6.57000922,7.43128528 6.72000706,7.47375225 6.73050691,7.43280196 C6.76050648,7.44948541 6.78900608,7.47375225 6.82500556,7.47375225 C6.90750438,7.47526892 6.96300358,7.4767856 7.02000276,7.54351941 C7.04850236,7.57840299 7.09050176,7.65575354 7.14150103,7.66182025 C7.14000104,7.71945399 7.20600009,7.76192096 7.13700109,7.81045464 C7.08300187,7.84837157 7.00800294,7.83927151 6.98550326,7.91207202 C6.97050348,7.95757235 6.92700409,7.97880583 6.99300314,8.01065606 C7.02150274,8.02582283 7.05750222,8.02885619 7.08900178,8.02885619 C7.09650167,8.07283983 7.11900135,8.13199025 7.17600052,8.12440686 C7.28699894,8.11075677 7.31099859,7.97122244 7.39949732,7.92420544 C7.54349525,7.84837157 7.52099559,8.1759739 7.6454938,8.09407331 C7.67549337,8.07435651 7.67549337,7.99093925 7.69049315,7.95908902 C7.72049272,7.89235522 7.75499223,7.82410473 7.79849161,7.76495431 C7.85399081,7.68912044 7.92298983,7.61025322 7.89899017,7.51166918 C7.88549035,7.4570688 7.78049187,7.43431863 7.73249254,7.3964017 C7.67549337,7.3493847 7.61999416,7.29933434 7.58549466,7.23411721 C7.56449496,7.1946836 7.55249513,7.1810335 7.58549466,7.1613167 C7.60499438,7.15069995 7.59899447,7.12946647 7.59149458,7.1142997 C7.5554951,7.03846583 7.45799649,6.90348153 7.59449453,6.85646453 C7.6244941,6.84584779 7.6874932,6.74878044 7.69199314,6.7108635 C7.69949303,6.64716305 7.60349441,6.58952931 7.64699378,6.52582885 C7.67849332,6.47881185 7.75799219,6.44847831 7.79849161,6.40146131 C7.81799133,6.37871114 7.84049101,6.35899434 7.87049058,6.35292763 C7.87199055,6.32714411 7.87799047,6.29832724 7.90049015,6.28164379 C7.93648963,6.25434359 7.99348882,6.26799369 8.03548821,6.25434359 C8.10448721,6.23311011 8.13448678,6.16182627 8.18848602,6.12239266 C8.23348537,6.08902576 8.28448464,6.10115917 8.33248395,6.07689234 C8.35798358,6.06475892 8.36848344,6.03745872 8.39398307,6.0253253 C8.45698217,5.99499175 8.52898113,6.04352543 8.55748073,6.09660914 C8.62497975,6.22401005 8.70897855,6.42117811 8.9009758,6.36961108 C8.97897468,6.3483776 9.03747384,6.28467714 9.0614735,6.21187663 C9.08397318,6.14210947 9.05697356,6.08447572 9.0614735,6.01622524 C9.06747341,5.89489104 9.18597171,5.8175405 9.19947152,5.69772298 C9.10947281,5.69923966 9.15597215,5.64312259 9.12597258,5.59155556 C9.09297304,5.53392182 9.01797413,5.57638878 8.96847483,5.56728872 C9.0194741,5.44443785 9.0194741,5.40197088 8.89947583,5.34282046 C8.84697658,5.31703694 8.75997782,5.19570275 8.7194784,5.20025278 C8.75247793,5.15475246 8.83047681,5.23058633 8.85447647,5.25181982 C8.90697572,5.30187017 8.95347505,5.32462033 9.02697399,5.33068704 C9.0059743,5.29883682 8.99547445,5.24120307 9.00897425,5.20480282 C9.02247405,5.17143591 8.98947453,5.13655233 8.99247448,5.09408536 C9.07647329,5.20176946 9.06297347,5.32462033 9.11097278,5.44292117 C9.13047252,5.49297153 9.1799718,5.52633843 9.20097151,5.57790546 C9.22797111,5.64312259 9.20997137,5.64160591 9.26697056,5.68103953 C9.30147007,5.70530637 9.31346989,5.74777333 9.31946981,5.78569027 C9.32996965,5.85242408 9.35396932,5.82360721 9.3899688,5.86152414 C9.41096849,5.8842743 9.46496771,5.88730766 9.45446788,5.93432466 C9.44696797,5.96769156 9.42446829,5.99499175 9.41996837,6.02987534 C9.40646855,6.12997605 9.60296575,5.99347508 9.62246546,5.97982498 C9.66446486,5.94797475 9.73346387,5.94190804 9.76646339,5.90399111 C9.8009629,5.8645575 9.79346301,5.80692375 9.82946249,5.7705235 C9.87446184,5.7235065 9.91796123,5.75535672 9.97196045,5.74625666 C10.0349596,5.73715659 10.0889588,5.68710624 10.1354581,5.6491893 C10.2344567,5.56577204 10.2974558,5.4747714 10.3799546,5.37922072 C10.3439551,5.38832078 10.2209569,5.47780475 10.213457,5.39438749 C10.1669577,5.39438749 10.0589592,5.38680411 10.0424594,5.33523707 C10.0289596,5.29732014 10.0334596,5.25485317 10.0334596,5.21693623 C10.0319596,5.17598594 9.9824603,5.18963604 9.94946077,5.16840256 C9.88346172,5.12593559 9.85046218,5.04706836 9.77996321,5.0106681 C9.66746482,4.95151768 9.59996578,4.85596701 9.53246675,4.75434962 C9.49346731,4.6951992 9.35546929,4.57538168 9.36596914,4.50409784 C9.37196906,4.45708084 9.41096849,4.40703049 9.40796854,4.36001349 C9.40646855,4.31754652 9.37346903,4.29479636 9.37796897,4.24777936 C9.38246889,4.19317897 9.25047079,4.09762829 9.36596914,4.08701155 C9.40196863,4.0839782 9.40796854,4.03999455 9.44846795,4.01572771 C9.49346731,3.98842752 9.48296745,3.96416068 9.53246675,3.97781078 C9.61196561,4.00207762 9.66746482,3.91411032 9.72146405,3.86709332 C9.8144627,3.78519274 9.66596483,3.78367607 9.65846494,3.72149229 C9.65096506,3.65930852 9.61496557,3.61380819 9.60446572,3.54100768 C9.59846581,3.48792397 9.54896651,3.50915745 9.52046693,3.52280755 C9.48146748,3.54100768 9.44246803,3.51370748 9.40496858,3.5061241 C9.37046907,3.49854071 9.34196947,3.44090697 9.29997008,3.4591071 C9.26847053,3.47427387 9.26997051,3.51219081 9.22497116,3.50764077 C9.19197163,3.50460742 9.17097194,3.47275719 9.1379724,3.46669048 C9.08697313,3.46062377 9.13197249,3.50915745 9.0749733,3.51370748 C9.0344739,3.51674084 8.90397575,3.46214045 8.9009758,3.51370748 C8.86047638,3.44394032 8.84397661,3.56224116 8.80047724,3.57437458 C8.75247793,3.58802468 8.70147866,3.57589126 8.65347935,3.59409139 C8.54848085,3.63655836 8.58298036,3.7412091 8.68347892,3.75789255 C8.76447776,3.77002597 8.65947926,3.82614303 8.68647888,3.88074342 C8.71047854,3.9292771 8.71797842,3.962644 8.77047767,3.98236081 C8.85747643,4.01421103 8.95047509,4.03999455 8.92047552,4.15222868 C8.88147609,4.291763 8.78397748,4.42371394 8.64747943,4.48741439 C8.51698131,4.54808149 8.48098182,4.38276365 8.38198324,4.34029668 C8.32048413,4.31451317 8.25148511,4.32361323 8.18698603,4.33119662 C8.17648618,4.34788007 8.27698474,4.37973029 8.29348451,4.40854717 C8.32348407,4.46769759 8.24098526,4.4601142 8.23498534,4.50561452 C8.22898543,4.54353146 8.17948614,4.57083165 8.20648576,4.60874859 C8.17798617,4.57386501 8.12248696,4.62088201 8.10298724,4.64211549 C8.07448765,4.67244904 8.08048756,4.69216584 8.09248739,4.73008278 C8.11648704,4.80743333 7.99948873,4.88781723 7.93048971,4.87871717 C7.87199055,4.8696171 7.81649135,4.87416714 7.76099214,4.84686694 C7.69499309,4.81501672 7.71749277,4.83473352 7.70399297,4.76041633 C7.69049315,4.69064917 7.59449453,4.66031562 7.65149372,4.57386501 C7.69199314,4.51016455 7.67249341,4.51623126 7.66499352,4.45556417 C7.65599366,4.39186371 7.68449323,4.38276365 7.73549251,4.37366358 C7.79099171,4.36304684 7.81499136,4.26446281 7.8479909,4.21744581 C7.85549078,4.20682907 7.88999029,4.11886178 7.83599107,4.14161194 C7.8059915,4.15526204 7.82849118,4.19317897 7.78199184,4.19924568 C7.74899231,4.20531239 7.71599279,4.18256223 7.68149329,4.18256223 C7.64249384,4.18256223 7.60199442,4.20227904 7.56599493,4.1780122 C7.58399467,4.15677871 7.71299283,4.05061129 7.61099429,4.02937781 C7.57049487,4.02027774 7.60349441,4.08701155 7.54949518,4.07791149 C7.53899533,4.1309952 7.47449625,4.12644516 7.44299671,4.15981207 C7.45649651,4.103695 7.57499481,4.06122803 7.53599536,4.01572771 C7.62149415,3.94141052 7.63949389,3.92776042 7.52399554,3.88681013 C7.34249815,3.82310968 7.35599795,3.63655836 7.48049617,3.52432423 C7.59449453,3.42119016 7.78199184,3.2907559 7.89299026,3.46214045 C8.01148856,3.64565842 8.08648747,3.51219081 8.18398608,3.39388997 C8.15098655,3.38023987 8.18098612,3.37113981 8.17048627,3.33018951 C8.06248782,3.37417316 7.9664892,3.23463884 8.03998814,3.15122158 C8.0849875,3.10117122 8.15398651,3.11482132 8.21398565,3.09965455 C8.2664849,3.08600445 8.31448421,3.03443742 8.33698389,2.98590374 C8.29198453,2.99803716 8.29798444,2.97073696 8.31448421,2.95102016 C8.28748459,2.9479868 8.25748502,2.93585338 8.23498534,2.92827 C8.17048627,2.90551983 8.1749862,2.85546948 8.1059872,2.84636942 C7.94248954,2.82210258 8.27698474,2.63403458 8.11348709,2.63403458 C8.06098784,2.6325179 8.01448851,2.55365067 7.97548908,2.56881745 C7.94848946,2.57943419 7.94098957,2.60066767 7.90799003,2.58701758 C7.88549035,2.57791751 7.85849075,2.5582007 7.83149113,2.57336748 C7.77299197,2.60976774 7.76099214,2.56578409 7.70399297,2.58246754 C7.65749363,2.59763432 7.63199399,2.64313464 7.57799476,2.63100122 C7.63199399,2.5582007 7.69799305,2.49753361 7.74599236,2.42169974 C7.77749191,2.3701327 7.81649135,2.3231157 7.8689906,2.29126548 C7.89749018,2.27458203 7.97848902,2.25789857 7.98298896,2.21846496 C7.99048885,2.15324783 7.94998943,2.15931454 7.90499008,2.18509806 C7.78799176,2.25486522 7.66649349,2.32918241 7.55249513,2.40349961 C7.48349612,2.44748325 7.42949689,2.48540019 7.34399812,2.47326677 C7.27799907,2.46265003 7.25099945,2.53545054 7.1985002,2.52938383 C7.17300057,2.42776645 6.61050864,2.79631906 6.54150962,2.82816929 C6.4320112,2.87670296 6.30901296,2.96012022 6.19201463,2.98893709 C6.14401532,3.00107051 6.04351676,3.11330464 6.04951668,2.98893709 C5.98951754,2.98135371 5.94451818,3.0420208 5.90401876,3.07235435 C5.84701958,3.116338 5.77952054,3.14363819 5.71802143,3.18155513 C5.58602333,3.26648906 5.46152511,3.36810645 5.33852687,3.46365713 C5.22152854,3.55465777 5.10453022,3.66082519 4.98153198,3.74272577 C4.93953258,3.77154265 4.7850348,3.85040987 4.78953474,3.90956029 C4.89903316,3.93079378 5.27252782,3.46365713 5.3775263,3.58954135 C5.40452592,3.62139158 5.2185286,3.71542558 5.18852901,3.73362571 C5.16302938,3.74727581 5.13302981,3.74575913 5.10753018,3.75940923 C5.07453065,3.77912603 5.05353096,3.81249294 5.0220314,3.83372642 C4.93803261,3.88681013 4.86753362,3.95506061 4.8060345,4.03089449 C4.76253512,4.08701155 4.73103557,4.15981207 4.68303626,4.2113791 C4.69053615,4.15677871 4.6800363,4.1173451 4.68153629,4.06426139 C4.62003716,4.103695 4.59453753,4.17042881 4.50903876,4.15222868 C4.43103987,4.13402855 4.36954076,4.21289578 4.31404156,4.25536275 C4.1850434,4.35394678 4.09804465,4.46314755 3.9900462,4.57841504 C3.93004706,4.64363217 3.86554798,4.68913249 3.82804851,4.76951639 C3.78754911,4.85596701 3.73054992,4.93331756 3.67805067,5.01370146 C3.57755211,5.16081917 3.46205377,5.29580346 3.36305519,5.44292117 C3.16055809,5.74473998 3.02706,6.09054243 2.86056239,6.41207805 C2.77506361,6.57891256 2.6925648,6.74271373 2.65806529,6.92926505 C2.62806573,7.09003286 2.62656574,7.25383402 2.6295657,7.41763518 C2.72106438,7.34635134 2.71656446,7.49498573 2.6925648,7.53896938 C2.65806529,7.60721986 2.64456548,7.68457041 2.63406564,7.76040428 C2.61906585,7.85898831 2.60106611,7.95757235 2.60106611,8.05767306 C2.60106611,8.14260699 2.5740665,8.22147422 2.57256651,8.3033748 C2.54106697,8.27910796 2.60856599,8.1759739 2.54856686,8.19114067 C2.51856729,8.19872406 2.51706731,8.2427077 2.50956742,8.26545786 C2.48556777,8.34432509 2.3790693,8.3367417 2.36256953,8.42622567 C2.35356965,8.48082606 2.34756974,8.51267628 2.31157026,8.55665993 C2.28307066,8.59002683 2.31007028,8.60519361 2.31607019,8.63856051 C2.32957,8.71742774 2.22757146,8.82662851 2.25457108,8.89032896 C2.28007071,8.95251274 2.26207097,9.0222799 2.29057056,9.08143032 C2.30557034,9.11176387 2.34006985,9.15119748 2.32657005,9.18911442 C2.26207097,9.20124784 2.34456979,9.34988222 2.3505697,9.39538255 C2.35956957,9.46969974 2.42556862,9.70326807 2.4975676,9.73663497 C2.5875663,9.87465261 2.71056454,10.0672706 2.86956225,10.1370378 C2.98206064,10.1855715 3.02256006,10.0399705 3.08555916,9.97930336 C3.16505802,9.90043613 3.26855654,9.85038578 3.37355504,9.81398552 C3.46205377,9.78213529 3.83254845,9.67445119 3.65405101,9.54705029 L3.65405101,9.54705029 L3.65405101,9.54705029 Z M3.81004877,14.5293356 C3.82804851,14.4990021 3.81454871,14.4216515 3.78154919,14.395868 C3.70205032,14.3291342 3.67055078,14.4868687 3.72155004,14.5338857 C3.74104977,14.583936 3.78754911,14.5672526 3.81004877,14.5293356 L3.81004877,14.5293356 L3.81004877,14.5293356 Z M4.1355441,9.9429031 C4.11304444,9.92318629 4.09654467,9.93531971 4.0950447,9.89436942 C4.09654467,9.85796916 4.09954462,9.788202 4.05154531,9.83218565 C4.03804551,9.83673568 4.05604525,9.84431907 4.03654553,9.85190245 C4.02304572,9.85645249 4.01254588,9.84583574 4.003546,9.84128571 C3.97804637,9.83066897 3.96304658,9.82915229 3.94354686,9.85645249 C3.93004706,9.87465261 3.93004706,9.8958861 3.90754738,9.91105287 L3.86854793,9.92470297 C3.85504813,9.929253 3.81604868,9.9565532 3.81454871,9.97171997 C3.8085488,9.99447013 3.8430483,10.0111536 3.86704796,10.0172203 C3.88654768,10.0308704 3.91204732,10.0430038 3.93154704,10.0566539 C3.95104675,10.070304 3.98254631,10.0945708 4.00654597,10.1006376 C4.06054519,10.1309711 4.14454398,10.164338 4.18654338,10.1006376 C4.19854321,10.0763707 4.20754309,10.0581706 4.19104332,10.0369371 C4.17604353,10.0141869 4.15204387,10.0081202 4.14454398,9.9899201 C4.13854407,9.97020329 4.15504384,9.95806987 4.1355441,9.9429031 L4.1355441,9.9429031 L4.1355441,9.9429031 Z M11.3489407,11.0879946 C11.3204411,11.0895112 11.2694418,11.1077114 11.2469422,11.1274282 C11.2019428,11.1668618 11.2979414,11.1896119 11.3354409,11.2002287 C11.3774403,11.2244955 11.4374394,11.2366289 11.4779389,11.2608958 C11.5124384,11.2866793 11.536438,11.3215629 11.5754375,11.3382463 C11.6234368,11.3609965 11.6894358,11.3716132 11.7419351,11.3852633 C11.7644348,11.3928467 11.7989343,11.39133 11.8289338,11.3973968 C11.8619334,11.4171136 11.8769331,11.4474471 11.9039328,11.4686806 C11.9504321,11.5111476 12.0164312,11.5217643 12.0779303,11.5187309 C12.1364294,11.5247977 12.1799288,11.5354144 12.232428,11.5217643 C12.2924272,11.5065975 12.3344266,11.5369311 12.3899258,11.5369311 C12.4124255,11.5369311 12.4349251,11.5187309 12.4559248,11.5202476 C12.4844244,11.5202476 12.4874244,11.532381 12.5009242,11.5566479 C12.5249239,11.5915315 12.586423,11.6446152 12.6299223,11.6461318 C12.656922,11.6461318 12.6779217,11.6415818 12.7004213,11.6491652 C12.725921,11.664332 12.7364208,11.664332 12.7544206,11.6794988 C12.7859201,11.6931489 12.8129197,11.7022489 12.8264195,11.7265158 C12.8504192,11.7689827 12.8489192,11.8159997 12.8864187,11.8493666 C12.9119183,11.8690834 12.9389179,11.8903169 12.9659175,11.9100337 C12.9839173,11.9267172 12.9689175,11.9236838 12.9959171,11.9236838 C13.0109169,11.9267172 13.0394165,11.9267172 13.0589162,11.9206505 C13.1339151,11.9161004 13.0934157,11.809933 13.0694161,11.7750494 C13.0544163,11.7447159 13.0409165,11.720449 13.0469164,11.6916322 C13.0514163,11.6567486 13.0679161,11.6324818 13.0454164,11.6036649 C13.0334166,11.5854648 13.0169168,11.5763647 13.000417,11.5672646 C12.9914172,11.5551312 12.9869172,11.5429978 12.9764174,11.5247977 C12.9539177,11.4959808 12.9104183,11.4868807 12.8819187,11.4595805 C12.8339194,11.4110468 12.8084198,11.3412797 12.7469207,11.2957794 C12.7139211,11.2760626 12.6839216,11.2912293 12.6434222,11.2745459 C12.6269224,11.2639291 12.6179225,11.2533124 12.5939229,11.245729 C12.5714232,11.2381456 12.5519235,11.2426957 12.5309238,11.241179 C12.4874244,11.2381456 12.4499249,11.2002287 12.4079255,11.203262 C12.3614262,11.2093288 12.3524263,11.2593791 12.3254267,11.2866793 C12.3014271,11.3063961 12.2744274,11.3063961 12.2654276,11.2745459 C12.2624276,11.2335956 12.2774274,11.2093288 12.2984271,11.1835452 C12.3314266,11.1486617 12.2984271,11.1289448 12.2549277,11.1259115 C12.1994285,11.1259115 12.1889287,11.1698951 12.166429,11.2169121 C12.1304295,11.2669625 12.1079298,11.2320789 12.0569306,11.2244955 C12.0224311,11.2260122 11.9984314,11.2396623 11.9654319,11.2260122 C11.9429322,11.2184288 11.9384323,11.2002287 11.9219325,11.1896119 C11.8964329,11.1759618 11.8769331,11.1805119 11.8589334,11.1926453 C11.8334338,11.198712 11.8334338,11.198712 11.8079341,11.1850619 C11.7854345,11.1759618 11.7794345,11.1577617 11.750935,11.151695 C11.7059356,11.1425949 11.6594363,11.1805119 11.6219368,11.1698951 C11.605437,11.1607951 11.5919372,11.1365282 11.5694376,11.1289448 C11.5439379,11.1137781 11.5484378,11.1274282 11.5319381,11.1456283 C11.5034385,11.1729285 11.4644391,11.1820286 11.4374394,11.1607951 C11.4029399,11.1365282 11.39994,11.0955779 11.3489407,11.0879946 L11.3489407,11.0879946 L11.3489407,11.0879946 Z M13.0619162,12.8807073 C13.0859158,12.8776739 13.0979156,12.8534071 13.1189153,12.8564404 C13.142915,12.8518904 13.1309152,12.8761572 13.1474149,12.891324 C13.1624147,12.9049741 13.1774145,12.9049741 13.1924143,12.9049741 C13.2194139,12.9095241 13.2719132,12.9125575 13.282413,12.8882907 C13.2944128,12.8503737 13.2314137,12.8427903 13.214914,12.8124568 C13.1999142,12.7684731 13.2344137,12.7260062 13.2464135,12.6880892 C13.2644132,12.6365222 13.1999142,12.613772 13.2059141,12.5728218 C13.2044141,12.5288381 13.2344137,12.5136713 13.2254138,12.4712044 C13.2179139,12.4393541 13.1909143,12.4044706 13.1714146,12.3817204 C13.1534148,12.3574536 13.1189153,12.3347034 13.1219153,12.2998198 C13.1249153,12.2634196 13.1909143,12.2649362 13.1594148,12.2209526 C13.141415,12.1830357 13.0934157,12.190619 13.0499163,12.1845523 C13.0349165,12.1845523 13.0199168,12.186069 13.004917,12.1709022 C12.9914172,12.1496688 12.9974171,12.139052 12.9974171,12.1238852 C12.9884172,12.0829349 12.9569177,12.0677682 12.9209182,12.0510847 C12.9089183,12.045018 12.8909186,12.0374346 12.8834187,12.0192345 C12.8774188,12.0010344 12.8954185,11.9949677 12.8894186,11.9782842 C12.8699189,11.9388506 12.7979199,11.993451 12.7679204,11.9798009 C12.7469207,11.9767675 12.7499206,11.9570507 12.7364208,11.9358172 L12.7004213,11.9191338 C12.6509221,11.8963836 12.6314223,11.9373339 12.6404222,11.9782842 C12.6629219,12.0708015 12.7304209,12.1314686 12.722921,12.2239859 C12.727421,12.2619029 12.7334209,12.280103 12.7469207,12.3149866 C12.7589205,12.365037 12.7724203,12.3893038 12.7499206,12.4378375 C12.7139211,12.4651377 12.7469207,12.4985046 12.7589205,12.5333881 C12.7664204,12.5804051 12.7784202,12.6168054 12.7769202,12.6668558 C12.7679204,12.7578564 12.7394208,12.8473404 12.7469207,12.9398577 C12.7514206,12.9777746 12.7484206,13.0126582 12.7619205,13.0490585 C12.7679204,13.0960755 12.8054198,13.1127589 12.8444193,13.1385424 C12.8819187,13.1703927 13.0559162,13.2750434 13.004917,13.1476425 C12.9899172,13.1188256 12.9659175,13.0778753 12.9584176,13.0445084 C12.9464178,13.0096249 12.9884172,12.985358 12.9899172,12.9489578 C12.9959171,12.9080075 12.9644176,12.8943574 13.0229167,12.8837406 C13.0349165,12.8746406 13.0529163,12.8837406 13.0619162,12.8807073 L13.0619162,12.8807073 L13.0619162,12.8807073 Z M11.2529421,1.95152973 C11.2994414,1.94091299 11.3459408,1.95456309 11.3894401,1.93787964 C11.4119398,1.92877957 11.4854388,1.90299606 11.4809388,1.87266251 C11.4734389,1.81654544 11.2349423,1.8529457 11.1959429,1.86962915 C11.1839431,1.90451273 11.2199426,1.93181293 11.2514421,1.94091299 C11.2514421,1.94394635 11.2529421,1.94849638 11.2529421,1.95152973 L11.2529421,1.95152973 L11.2529421,1.95152973 Z M12.3989257,12.0950684 C12.3899258,12.0753516 12.3989257,12.0571514 12.3989257,12.0374346 C12.3944257,12.0055844 12.3854259,11.9964843 12.3899258,11.9631174 C12.4004256,11.9449173 12.4004256,11.9161004 12.3959257,11.8933503 C12.3869258,11.8751501 12.3719261,11.8599834 12.3584262,11.8463333 C12.3584262,11.8372332 12.3539263,11.8220664 12.3434265,11.8129664 C12.3254267,11.7947662 12.305927,11.8220664 12.2864273,11.8311665 C12.2714275,11.8448166 12.2429279,11.8539167 12.238428,11.8690834 C12.2234282,11.8918336 12.232428,11.9100337 12.232428,11.9297505 L12.236928,11.9525007 C12.2039285,11.9858676 12.235428,12.0738349 12.2309281,12.1072018 C12.2309281,12.1451187 12.1769288,12.2512861 12.2489278,12.2194359 C12.2684275,12.2103359 12.2834273,12.1966858 12.3014271,12.1875857 C12.3254267,12.1739356 12.3554263,12.1739356 12.3839259,12.1648355 C12.3929257,12.1648355 12.445425,12.1602855 12.445425,12.1511854 C12.446925,12.1314686 12.4034256,12.1178185 12.3989257,12.0950684 L12.3989257,12.0950684 L12.3989257,12.0950684 Z M11.0444451,14.0121486 C11.1044442,14.0409655 11.2094427,13.9833318 11.2664419,13.9651316 C11.3384409,13.9423815 11.4524392,13.8665476 11.5259382,13.9090146 C11.5559377,13.925698 11.5709375,13.9605816 11.6024371,13.9742317 C11.6414365,13.9909152 11.6879359,13.9757484 11.7269353,13.9666483 C11.7674347,13.9575482 11.813934,13.9514815 11.8514335,13.9332814 C11.884433,13.916598 11.9054327,13.8892978 11.9339323,13.8680643 C12.0074313,13.8119472 12.0704304,13.8650309 12.1514292,13.8528975 C12.1979285,13.8468308 12.2414279,13.8240806 12.2864273,13.8119472 C12.3194268,13.8043638 12.377926,13.8043638 12.4034256,13.780097 C12.4319252,13.7527968 12.4184254,13.6936464 12.4184254,13.6587628 C12.4169254,13.6117458 12.4199254,13.5632121 12.4034256,13.5192285 C12.3704261,13.4327779 12.2504278,13.3311605 12.374926,13.26291 C12.3989257,13.1112422 12.2219282,13.1370258 12.1709289,13.027825 C12.1379294,12.9565411 12.1274295,12.9019408 12.0329309,12.8943574 C11.953432,12.886774 11.9054327,12.929241 11.8364337,12.9565411 C11.7584348,12.985358 11.6984357,12.9595745 11.6309367,12.9216576 C11.5904373,12.8989074 11.5049385,12.8458237 11.4884387,12.9155909 C11.4734389,12.976258 11.5319381,13.0354084 11.4839388,13.0900088 C11.4419394,13.1385424 11.3684404,13.1597759 11.3084413,13.173426 C11.1779432,13.2007262 11.0744446,13.3008269 10.979946,13.3872775 L10.9904458,13.3963776 C10.9529464,13.3948609 10.8974472,13.4949616 10.8959472,13.5252952 C10.910947,13.5298452 10.9244468,13.5343952 10.9409466,13.5389453 C10.9394466,13.5905123 10.9979457,13.5556287 11.0024457,13.5192285 C11.0144455,13.5222618 11.0264453,13.5298452 11.0384452,13.5313619 C11.048945,13.5343952 11.0714447,13.5328786 11.0804446,13.5374286 C11.1059442,13.5480453 11.1104441,13.5723122 11.1419437,13.5753455 C11.1239439,13.6542128 11.1404437,13.73763 11.1029442,13.8119472 C11.0789446,13.8574475 10.9574463,13.9696817 11.0444451,14.0121486 L11.0444451,14.0121486 L11.0444451,14.0121486 Z M11.6639362,2.34586586 C11.7089356,2.39439954 11.7644348,2.40804964 11.7539349,2.48388351 C11.8109341,2.4914669 11.8469336,2.51270038 11.8799331,2.4641667 C11.9009328,2.43383315 11.9324323,2.40956632 11.9669319,2.3974329 C12.0089312,2.38074944 12.1814288,2.38226612 12.1739289,2.45354996 C12.1694289,2.48843354 12.1484293,2.51876709 12.1424293,2.55365067 C12.1349295,2.60218435 12.1874287,2.56730077 12.2099284,2.57943419 C12.1844287,2.59763432 12.1529292,2.60825106 12.1214296,2.61583445 C12.1349295,2.62493451 12.1439293,2.63706793 12.1454293,2.6522347 C12.1064299,2.66133477 12.0869301,2.77053554 12.0179311,2.79176903 C11.9759317,2.80541912 11.9144326,2.77660225 11.8724332,2.77205222 C11.8229339,2.76598551 11.7854345,2.75081874 11.7359352,2.74778538 C11.6879359,2.74475203 11.7269353,2.68105158 11.6669361,2.693185 C11.6564363,2.73565196 11.677436,2.84333606 11.6849359,2.88580303 C11.6924358,2.93888674 11.7374351,2.96922029 11.7899344,2.97832035 C11.8634333,2.99045377 11.8979328,3.01472061 11.9609319,3.05112087 C12.0104312,3.07842106 12.0659304,3.06173761 12.1199297,3.06628764 C12.1559291,3.069321 12.1859287,3.08297109 12.2129283,3.10572126 C12.2069284,3.12240471 12.1934286,3.1497049 12.2024285,3.16790503 C12.2129283,3.19217187 12.2894272,3.16487168 12.307427,3.163355 C12.3614262,3.15728829 12.4124255,3.09813787 12.4634247,3.10572126 C12.4829245,3.10875461 12.5729232,3.1360548 12.5684232,3.15880497 C12.5204239,3.13908816 12.4889244,3.19823858 12.4514249,3.16638835 C12.4184254,3.13757148 12.3299267,3.16183832 12.3869258,3.20278861 C12.3914258,3.20733864 12.4064255,3.32108945 12.4064255,3.33322287 C12.4019256,3.37568984 12.3254267,3.42119016 12.3314266,3.44849035 C12.3419265,3.45000703 12.4109255,3.45455706 12.4259253,3.46820716 C12.4304252,3.45000703 12.4169254,3.44242364 12.4589248,3.43029022 C12.4904244,3.42119016 12.5279238,3.41815681 12.5594234,3.43332358 C12.5714232,3.48640729 12.5324238,3.54100768 12.6059227,3.52735758 C12.6749217,3.51370748 12.7049213,3.55920781 12.7769202,3.51219081 C12.8219196,3.48489061 12.8699189,3.48944064 12.9089183,3.52887426 C12.9614176,3.58044129 12.8549191,3.65172513 12.9164182,3.70177548 C12.9404179,3.72149229 12.9599176,3.78215939 12.9839173,3.79277613 C13.000417,3.80035952 13.0904157,3.76850929 13.1069155,3.7609259 C13.1354151,3.81249294 13.1609147,3.73362571 13.1834144,3.72907568 C13.1924143,3.69570877 13.2344137,3.65779184 13.2749131,3.65324181 C13.3349122,3.6471751 13.3364122,3.65779184 13.3769116,3.68660871 C13.4954099,3.76850929 13.4789102,3.56830787 13.5404093,3.51674084 C13.6514077,3.42574019 13.7069069,3.33928958 13.7834058,3.22250542 C13.8434049,3.12847142 13.9274038,3.10572126 14.0339022,3.0890378 C14.117901,3.07538771 14.2468992,3.0556709 14.2813987,2.96467025 C14.3218981,2.85850283 14.2243995,2.80086909 14.1389007,2.76901887 C14.0429021,2.73565196 13.9349036,2.6992517 13.976903,2.57791751 C14.0264023,2.43686651 13.982903,2.35496593 13.8329051,2.30946561 C13.5164096,2.21088157 13.2314137,2.04556373 12.9089183,1.95456309 C12.6239224,1.87417918 12.3359266,1.84536231 12.0434308,1.83019554 C11.9129326,1.78469522 11.6369366,1.78014518 11.5574377,1.89086264 C11.5064384,1.96214647 11.5709375,2.02433025 11.5649376,2.09864744 C11.5574377,2.18813141 11.6009371,2.27913206 11.6639362,2.34586586 L11.6639362,2.34586586 L11.6639362,2.34586586 Z M16.6423649,16.4069823 L16.6408648,16.4054657 C16.6453648,16.4130491 16.6423649,16.4266991 16.6438648,16.4373159 C16.702364,16.4373159 16.7278637,16.4903996 16.7908628,16.4706828 C16.8553617,16.4524827 16.8928612,16.3902989 16.841862,16.3387319 C16.7968627,16.2947482 16.7578632,16.2568313 16.6918641,16.2689647 C16.6138653,16.2841315 16.6303649,16.3463152 16.6423649,16.4069823 L16.6423649,16.4069823 L16.6423649,16.4069823 Z M18.6808357,14.3791846 C18.6778357,14.3655345 18.6748358,14.3534011 18.6718357,14.339751 C18.6103366,14.3215508 18.5713371,14.3852513 18.514338,14.3382343 C18.4048396,14.4125515 18.5218379,14.5596692 18.3418405,14.5505691 C18.37334,14.5884861 18.3703401,14.630953 18.3553403,14.6749367 C18.3328406,14.7431872 18.3148408,14.7371204 18.2683415,14.7462205 C18.1708429,14.7613873 18.1243436,14.7007202 18.094344,14.6188196 C17.9983455,14.621853 17.8663472,14.7704874 17.7913484,14.8175044 C17.7718486,14.8281211 17.7373491,14.8599713 17.7163495,14.8736214 C17.6998497,14.8827215 17.6593503,14.9024383 17.6383505,14.9145717 C17.5873513,14.9403552 17.4778528,14.9752388 17.4718529,15.0328725 C17.4463533,15.0283225 17.4073539,15.0434893 17.3818543,15.0404559 C17.3728543,15.0525894 17.3728543,15.0662394 17.3818543,15.0798895 C17.4988526,15.0996064 17.5603516,15.0601727 17.6593503,15.0177058 C17.7628488,14.9706888 17.8738472,14.9813055 17.9713458,14.9449053 C18.0178451,14.9282218 18.0193451,14.8766548 18.095844,14.9069883 C18.1288435,14.9221551 18.167843,14.9706888 18.1753429,15.0040557 C18.1903427,15.0798895 18.1108438,15.1921237 18.0313449,15.1966737 C18.0118452,15.1496567 18.0403448,15.101123 18.0493446,15.0632061 C17.9443462,15.0283225 17.7718486,15.1769569 17.743349,15.2649242 C17.8513475,15.2876744 17.8963469,15.4454088 17.8378476,15.5318595 C17.818348,15.5530929 17.7958484,15.5803931 17.7583489,15.5925266 C17.6968497,15.6107267 17.6683501,15.5546096 17.608351,15.5985933 C17.5303522,15.6577437 17.6158509,15.8200282 17.5708515,15.9110288 C17.5363521,15.980796 17.4778528,16.0065795 17.4283536,16.0551132 C17.395354,16.0899968 17.3758544,16.1279137 17.330855,16.1582472 C17.2723558,16.1976809 17.1298578,16.2826148 17.1418577,16.3645154 C17.2708559,16.408499 17.537852,16.1840308 17.6488504,16.1097136 C17.7193494,16.0626966 17.7628488,15.989896 17.8348477,15.942879 C17.9158466,15.8928287 18.0223451,15.8670452 18.0748442,15.7775612 C18.1048439,15.7259942 18.0808441,15.6804939 18.098844,15.6289268 C18.1153438,15.5834265 18.1468433,15.5682597 18.1768428,15.5333761 C18.2323421,15.4666423 18.2833413,15.4454088 18.3463403,15.3908084 C18.4243393,15.3210413 18.4063395,15.2118405 18.442339,15.1193232 C18.4738386,15.0389393 18.5353378,14.9767555 18.5788371,14.9009216 C18.6463362,14.7811041 18.8233336,14.4959687 18.7483346,14.3609844 C18.7303349,14.3761512 18.6973353,14.3716012 18.6808357,14.3791846 L18.6808357,14.3791846 L18.6808357,14.3791846 Z M20.2123137,10.498007 C20.1733142,10.4252065 20.2318134,10.215905 20.2318134,10.1294544 C20.2303134,9.97020329 20.182314,9.85038578 20.1598145,9.70175139 C20.1463146,9.56221706 20.1268149,9.19214777 20.1778142,9.06626355 C20.2498131,8.88881229 19.8988181,8.58851016 19.8793184,8.39589212 C19.8613187,8.22905761 19.7668201,8.07132315 19.630322,7.97577248 C19.5748228,7.93482218 19.4548246,7.38426828 19.3843255,7.41005179 C19.349826,7.42673525 19.421825,7.54806944 19.4173251,7.58598638 C19.3978254,7.71945399 19.3303263,7.58598638 19.2598273,7.61631992 C19.1293292,7.66940363 18.9913311,7.79528786 18.9748315,7.92420544 C18.9133323,8.40195883 18.5743371,7.90752199 18.6013368,7.88780518 C18.6763357,7.83017144 18.7033352,7.84837157 18.7918339,7.83623815 C18.8893327,7.80135457 18.7378349,7.73158741 18.8908326,7.71642064 C18.8548332,7.6178366 18.935832,7.58598638 18.8968325,7.50863583 C18.8383334,7.39488502 18.7963339,7.40853512 18.8548332,7.28568425 C18.8803327,7.21895044 18.727335,7.00813228 18.7153352,6.92774837 C18.7033352,6.84888115 18.7018353,6.74574708 18.6913354,6.65777979 C18.6853355,6.60166273 18.7768342,6.54857902 18.7603344,6.50459537 C18.7573345,6.3483776 18.7918339,6.1800264 18.7363348,6.02835866 C18.6973353,5.92522459 18.6523361,5.78872362 18.586337,5.70075633 C18.5638373,5.67042279 18.4933383,5.51420501 18.4858384,5.47022136 C18.4678387,5.37315401 18.4243393,5.40955427 18.3703401,5.37163733 C18.3403404,5.3337204 18.2053424,5.20783617 18.1663429,5.18963604 C18.1303435,5.17295259 17.8693472,4.93483423 17.8633473,4.91511743 C17.8408476,4.84686694 17.6938498,4.79378323 17.7103496,4.71946604 C17.7343492,4.60723191 17.3428548,4.31602984 17.2333564,4.29631304 C17.1628574,4.28417962 17.4493532,4.62694872 17.4478533,4.61936533 C17.4523532,4.63908213 17.6323506,4.86810043 17.6323506,4.86810043 C17.6728501,4.88175052 17.7718486,5.16233585 17.7688487,5.19873611 C17.7583489,5.30490353 17.4838527,5.0243182 17.4628531,4.98640127 C17.327855,4.82108343 17.0833585,4.68761581 16.9288607,4.57689836 C16.8193624,4.47528097 16.8733616,4.41764723 16.6858642,4.33119662 C16.6168652,4.29934639 16.4293679,4.14616197 16.3708688,4.14161194 C16.3003698,4.13857858 16.3813685,4.28721297 16.3828686,4.30389642 C16.3933683,4.41006384 16.5088668,4.41764723 16.5778657,4.48741439 C16.6333649,4.54504813 16.6828643,4.61633197 16.6348649,4.68003243 C16.6333649,4.68003243 16.5478663,4.83776688 16.5433663,4.82411678 C16.5673659,4.89085059 16.766863,5.0379683 16.8163624,5.09256869 C16.8073624,5.07891859 17.0773586,5.43230443 17.0998583,5.24423643 C17.1088581,5.17598594 17.0323592,5.09408536 17.0428591,5.03493494 C17.0503591,4.99853468 17.3518546,5.37922072 17.3683544,5.41258762 C17.4418534,5.61430572 17.4358535,5.38225407 17.5123523,5.40652091 C17.5753514,5.42623772 17.7448491,5.63705588 17.603851,5.64615595 C17.3893541,5.65980604 17.6428505,5.84635737 17.7028496,5.87517424 C17.8273479,5.93584134 17.9128467,6.07689234 18.0418448,6.12997605 C18.2443419,6.21187663 18.2023425,6.35747766 18.304341,6.50611205 C18.3373405,6.55312905 17.7223494,6.50611205 17.67435,6.53341224 C17.5933512,6.59711269 17.9833456,7.03239912 17.9848456,7.09761624 C17.9878456,7.23411721 18.0658444,7.33421792 18.0868442,7.47071889 C18.098844,7.59660312 18.0838442,7.76192096 18.1663429,7.86353835 C18.233842,7.92572212 18.2968411,7.76040428 18.4033395,7.85898831 C18.442339,7.87718844 18.4978383,7.92117209 18.5098381,7.95453899 C18.5458375,8.04857299 18.7498347,8.62339374 18.5053381,8.58092677 C18.3973396,8.56120996 18.4588388,9.03289664 18.4663386,9.10114713 C18.4993382,9.24978151 18.5593373,9.23764809 18.5203379,9.4257161 C18.5248379,9.58193387 18.3988397,9.6608011 18.3088409,9.77303523 C18.2668416,9.82460226 18.2398419,9.88526936 18.2203422,9.95048649 C18.1708429,9.90195281 18.1468433,9.82460226 18.0778442,9.79881874 C18.0028453,9.77000187 17.8378476,9.86100252 17.7718486,9.89285274 C17.612851,9.97323665 17.7298493,10.1188377 17.6668501,10.2447219 C17.6218508,10.3372392 17.4868527,10.3827396 17.3983539,10.4282399 C17.2918555,10.4828403 17.1493575,10.540474 17.044359,10.4525067 C16.9543604,10.3797062 16.9933599,10.2325885 16.8988611,10.168888 C16.7923627,10.0976042 16.7818629,10.2568553 16.7578632,10.3205558 C16.7113639,10.4403733 16.5718659,10.4813236 16.6078654,10.6314747 C16.6228651,10.6936584 16.6543647,10.7497755 16.6663645,10.8119593 C16.6813642,10.8893098 16.6423649,10.959077 16.6378649,11.0364275 C16.628865,11.1683785 16.766863,11.198712 16.8028626,11.3063961 C16.8343621,11.4034635 16.8013625,11.5536145 16.6933642,11.5900148 C16.5778657,11.6309651 16.4503675,11.5263143 16.3363691,11.5141809 C16.2223709,11.5020475 16.076873,11.5338977 16.0558732,11.664332 C16.0363735,11.7795995 16.1533718,11.8736335 16.0963726,11.9919343 C16.072373,12.0419847 16.0303736,12.0814183 16.0003741,12.1269186 C15.9478747,12.2027525 15.9178752,12.2907198 15.868376,12.3680703 C15.9268751,12.369587 15.9208752,12.3331867 15.9733744,12.3438035 C16.0288737,12.3559369 16.0783729,12.2998198 16.1248722,12.2816197 C16.1338722,12.31802 16.1293722,12.3559369 16.1338722,12.3923371 C16.1728715,12.4044706 16.211871,12.3893038 16.2463705,12.3756537 C16.2508705,12.4075039 16.2358706,12.4439042 16.2493705,12.4757544 C16.2598704,12.5030546 16.2913699,12.5121547 16.3093696,12.5333881 C16.3558689,12.5895052 16.2988697,12.6820225 16.2658703,12.7305562 C16.1713716,12.8700905 16.0093739,12.9474411 15.9043754,13.0778753 C15.8053768,13.1992095 15.7963769,13.3463272 15.7228781,13.4782782 C15.6973784,13.5237785 15.6718788,13.5859623 15.7393777,13.6071958 C15.7543776,13.5829289 15.7768773,13.5632121 15.8083769,13.5632121 C15.8548761,13.5616954 15.8368765,13.596579 15.860876,13.6238792 C15.9583746,13.7482468 16.0618731,13.5434953 16.1038725,13.4843449 C16.145872,13.4206444 16.3198695,13.3250938 16.3678688,13.4312612 C16.4053683,13.5116451 16.3618688,13.6253959 16.3243694,13.6981964 C16.3873684,13.7270133 16.3693687,13.7725136 16.3843685,13.827114 C16.4038682,13.9029479 16.4728672,13.9514815 16.4728672,14.0348988 C16.4728672,14.1349995 16.2538705,14.3336843 16.3393692,14.4095181 C16.4458677,14.5035521 16.5718659,14.2426836 16.6138653,14.1865665 C16.6933642,14.0788824 16.8748615,14.0637157 16.9198609,13.9317647 C16.9693601,13.7816137 16.9528603,13.6921297 17.1523575,13.6875797 C17.2363564,13.686063 17.2978554,13.6329793 17.3773543,13.6162958 C17.4643531,13.5996124 17.5033525,13.5905123 17.5588518,13.5237785 C17.6383505,13.4282278 17.7103496,13.5419786 17.7133495,13.6178125 C17.7163495,13.6951631 17.68185,13.7922304 17.7298493,13.8619976 C17.7883484,13.9469315 17.8528475,13.8346974 17.9128467,13.780097 C17.9068468,13.8377307 17.9833456,13.8710976 18.028345,13.8892978 C18.0973439,13.8422808 18.1408433,13.7649302 18.2143423,13.7224632 C18.2488418,13.7027464 18.2878413,13.6951631 18.3268407,13.6890963 C18.3373405,13.7512801 18.3478404,13.8180139 18.4033395,13.8483475 C18.4828384,13.8938478 18.3853398,13.9378314 18.4903384,13.9863651 C18.6208365,14.0364155 18.6643359,14.1683664 18.727335,14.2745338 C18.7573345,14.3230675 19.0303306,14.0273154 19.1353291,14.0166987 C19.4833242,13.9772651 19.6648215,13.5237785 19.7938196,13.2553266 C19.9798171,12.8716072 20.0638158,12.4454209 20.1118151,12.049568 C20.2303134,11.805383 20.2783127,11.3579631 20.2303134,11.0743445 C20.2018138,10.9014432 20.3008124,10.6618082 20.2123137,10.498007 L20.2123137,10.498007 L20.2123137,10.498007 Z M17.0623589,16.2568313 C17.0713587,16.2143643 17.1613573,16.0566299 17.0773586,16.0338797 C17.047359,16.0262963 17.0218595,16.0717966 16.9948598,16.07938 C16.9588602,16.0915134 16.9198609,16.0687633 16.8868613,16.0854467 C16.8568618,16.1021302 16.8283622,16.1506639 16.8103624,16.177964 C16.7878628,16.211331 16.7968627,16.224981 16.832862,16.2431812 C16.8688616,16.262898 16.9153609,16.271998 16.9363607,16.3114317 C16.9543604,16.3463152 16.9453605,16.3933322 16.9408605,16.4297325 C16.9408605,16.4282158 16.9468604,16.4221491 16.9483605,16.4160824 C16.9543604,16.4145657 16.9648603,16.4130491 16.9708602,16.4145657 L16.9633602,16.4297325 C17.0593589,16.4448993 17.051859,16.319015 17.0623589,16.2568313 L17.0623589,16.2568313 L17.0623589,16.2568313 Z M18.1858427,14.3594678 C18.1888426,14.4049681 18.238342,14.4004181 18.2713415,14.3882846 C18.3013411,14.3791846 18.3208408,14.3534011 18.3403404,14.3306509 C18.3673401,14.2957673 18.3838399,14.2608837 18.3598402,14.2199334 C18.3358406,14.1774665 18.3208408,14.1471329 18.3088409,14.0970826 C18.2893412,14.1076993 18.2653416,14.1258994 18.2443419,14.1319662 C18.2233421,14.1395495 18.2203422,14.1334828 18.1963426,14.1319662 C18.1408433,14.1304495 18.1528432,14.1729164 18.1288435,14.2078 C18.1093437,14.2381336 18.0658444,14.250267 18.0823442,14.2897006 C18.094344,14.3200342 18.1438434,14.3458177 18.172343,14.3594678 L18.1798428,14.3503677 C18.1783429,14.3534011 18.1768428,14.3549177 18.1753429,14.3579511 C18.1783429,14.3594678 18.1828427,14.3594678 18.1858427,14.3594678 L18.1858427,14.3594678 L18.1858427,14.3594678 Z M12.7709203,17.6157743 C12.8264195,17.5171902 12.9254181,17.4550064 13.0139168,17.386756 C13.1204153,17.3048554 13.214914,17.2093047 13.2989128,17.1061706 C13.2494135,17.0940372 13.2464135,17.0470202 13.214914,17.0166867 C13.1669147,16.9696697 13.0934157,17.0000032 13.0829159,16.9287194 C13.072416,16.8650189 13.0199168,16.8513688 12.9689175,16.8255853 C12.8519192,16.7664349 12.8039199,16.6511674 12.7094212,16.5707835 C12.6059227,16.4797829 12.4649247,16.5101164 12.3389265,16.4858496 C12.2279281,16.4646161 12.1154297,16.2871648 11.9969314,16.3629987 C11.9219325,16.4100157 11.887433,16.5298332 11.9384323,16.6041504 C11.9789317,16.6617841 12.0479307,16.690601 12.0749303,16.7588515 C12.0344309,16.7967684 12.025431,16.8210353 12.0749303,16.8559189 C12.1334295,16.8968691 12.2459278,16.9393361 12.2174283,17.0288201 C12.2024285,17.0773538 12.1619291,17.1243708 12.1094298,17.1334708 C12.0719303,17.1410542 11.9804317,17.1198207 11.9999314,17.1880712 C11.957932,17.0758371 11.8394337,17.2517717 11.7749346,17.1592543 C11.7224354,17.0849372 11.6894358,17.0182033 11.606937,16.968153 C11.5004385,16.9029359 11.6504364,16.8392354 11.6324367,16.7406514 C11.605437,16.596567 11.4299395,16.6360006 11.3669405,16.5298332 C11.329441,16.4691661 11.3804403,16.4206324 11.4044399,16.3675487 C11.4284396,16.3129483 11.4929386,16.356932 11.5274382,16.3720988 C11.6399365,16.4251825 11.8049342,16.4054657 11.8994328,16.3281151 C11.9429322,16.2917149 12.0389308,16.1233637 11.9174326,16.1233637 C11.8334338,16.1248803 11.7689347,16.1976809 11.6894358,16.2022309 C11.677436,16.1066802 11.6324367,15.9443957 11.7344352,15.8806953 C11.8274339,15.8230615 12.0224311,15.7563277 11.9399322,15.6076933 C11.9099327,15.5561263 11.8574334,15.6410602 11.813934,15.6031433 C11.7959343,15.5879765 11.8094341,15.5530929 11.815434,15.5364095 C11.7869344,15.510626 11.7599348,15.4787758 11.744935,15.4423755 C11.678936,15.2816076 11.8739332,15.1557234 11.7914344,14.9858555 C11.7569349,14.9145717 11.6984357,14.8721047 11.6339366,14.8281211 C11.5694376,14.7826208 11.5664376,14.724987 11.5424379,14.6567365 C11.5304381,14.6188196 11.467439,14.5293356 11.4134398,14.5520858 C11.3669405,14.5702859 11.3534406,14.6506698 11.3174412,14.6840367 C11.2364423,14.762904 11.0654448,14.7932375 10.9574463,14.767454 C10.8704476,14.7477372 10.8749475,14.7143703 10.8329481,14.6567365 C10.8194483,14.6355031 10.7894487,14.6339864 10.766949,14.6248863 C10.7279496,14.6097195 10.7234497,14.574836 10.7144498,14.5399524 C10.6784503,14.4080014 10.4174541,14.5718026 10.3769546,14.386768 C10.3679548,14.344301 10.3829546,14.2730172 10.3214554,14.2639171 C10.2524564,14.2517837 10.2494565,14.1850499 10.2494565,14.1289328 C10.2494565,14.0834325 10.2524564,14.019732 10.2044571,13.9939485 C10.142958,13.9605816 10.1279582,13.9757484 10.1084585,13.9059812 C10.0859588,13.8164972 10.0244597,13.9090146 9.97346042,13.8908144 C9.86546198,13.8483475 9.88496169,13.9014312 9.79796295,13.9469315 C9.64946507,14.0257987 9.63296531,13.6602795 9.58196604,13.5889956 C9.48296745,13.4524947 9.5084671,13.7573468 9.44096806,13.7952638 C9.37946895,13.8301473 9.31496987,13.7497634 9.29247019,13.7027464 C9.27897039,13.6754462 9.2714705,13.6451127 9.25497073,13.6178125 C9.22947109,13.5783789 9.18447174,13.5616954 9.15897211,13.5222618 C9.1379724,13.4873782 9.10647286,13.446428 9.09147307,13.408511 C9.07797327,13.3751441 9.08097322,13.3326771 9.05547359,13.305377 C9.02397404,13.2704934 9.06297347,13.2128596 9.08547316,13.168876 C9.1244726,13.1537092 9.18297177,13.1840428 9.21147134,13.2098263 C9.28197034,13.2689767 9.38846881,13.5283285 9.51146705,13.4797949 C9.48596742,13.446428 9.50246719,13.4054777 9.48446744,13.3690774 C9.46496771,13.3311605 9.42746826,13.3084103 9.39896866,13.2780768 C9.33596956,13.2052762 9.26697056,13.1324757 9.22497116,13.0445084 C9.18897168,12.9686746 9.17097194,12.8882907 9.09747298,12.835207 C9.03747384,12.7912233 8.91897554,12.7487563 8.94447517,12.6547223 C8.94447517,12.6532057 8.94597516,12.651689 8.94597516,12.651689 C8.99547445,12.6623057 9.02997396,12.698706 9.06597344,12.7305562 C9.11847269,12.7760565 9.18597171,12.7988067 9.24747084,12.8291402 C9.35996923,12.8837406 9.49046736,12.9216576 9.58646598,13.0050748 C9.64646512,13.0551252 9.61496557,13.1673593 9.68996449,13.2295431 C9.7454637,13.2750434 9.82796252,13.4282278 9.92996105,13.3675607 C9.96746051,13.3448106 9.98396028,13.3008269 10.0214597,13.2750434 C10.0619592,13.2462265 10.1294582,13.220443 10.1759575,13.1992095 C10.2044571,13.1855594 10.2524564,13.1901095 10.2749561,13.1673593 C10.3109556,13.1324757 10.2254568,13.032375 10.2059571,13.0065915 C10.1294582,12.9080075 10.0589592,12.8003234 9.95696066,12.7260062 C9.90446142,12.6880892 9.85346215,12.647139 9.79196304,12.6213554 C9.7589635,12.6077053 9.70046434,12.609222 9.69446443,12.5637217 C9.71096419,12.5743384 9.71996406,12.5713051 9.72296402,12.5546216 C9.72146405,12.5242881 9.67796466,12.5227714 9.65696497,12.5167047 C9.60596569,12.5030546 9.57146619,12.5030546 9.55196647,12.4605876 C9.52646684,12.4090206 9.43646812,12.4105373 9.38846881,12.3984039 C9.31646984,12.3802037 9.25947067,12.3256033 9.19047165,12.2983031 C9.10947281,12.2679696 9.0479737,12.2998198 8.96847483,12.31802 C8.95497503,12.3210533 8.92797542,12.3680703 8.90247578,12.4075039 C8.8454766,12.3938538 8.7824775,12.3999205 8.73447819,12.4378375 C8.66247923,12.4939545 8.62047983,12.5788885 8.56348064,12.6486556 C8.53948099,12.6774725 8.50498148,12.7062894 8.47048197,12.6956726 C8.46448206,12.6926393 8.46748202,12.6850559 8.46298208,12.6820225 C8.50648145,12.3711037 8.53048111,12.0571514 8.50198153,12.1193352 C8.44498234,12.2406694 8.40298294,12.3256033 8.35948356,12.4135706 C8.29648447,12.3862704 8.22298552,12.3847538 8.19598591,12.4514876 C8.16748632,12.5227714 8.20498577,12.6152887 8.15998643,12.6774725 C8.14948658,12.694156 8.13298681,12.6926393 8.11798703,12.7002227 C8.11498707,12.6926393 8.09998729,12.6729225 8.10148726,12.6714058 C8.08798746,12.694156 8.08648747,12.7017393 8.07448765,12.7214561 C8.0294883,12.7229728 7.97398909,12.7047727 7.91848989,12.6865726 C7.91848989,12.6865726 7.91848989,12.6820225 7.91698991,12.6820225 C7.91548992,12.6835392 7.91548992,12.6835392 7.91398995,12.6850559 C7.8479909,12.6623057 7.77899188,12.6410722 7.71899274,12.6623057 C7.59899447,12.7047727 7.59449453,12.8761572 7.51799562,12.980808 C7.40399726,13.1400591 7.11000147,13.0718086 7.05900221,12.882224 C7.10100161,12.8306569 7.14150103,12.7790899 7.18350043,12.7275229 C7.11900135,12.5637217 6.94650382,12.4499709 6.77250631,12.4545209 C6.72300702,12.4560376 6.67050778,12.4651377 6.62550841,12.4439042 C6.57900908,12.421154 6.55350945,12.3726203 6.51301003,12.3422868 C6.38701184,12.2482528 6.21901425,12.3726203 6.11401575,12.4894045 C5.93401832,12.519738 5.76752071,12.6213554 5.65502233,12.7669565 C5.58302336,12.7608898 5.5110244,12.754823 5.4405254,12.7487563 C5.4825248,12.8640238 5.34302681,12.9580578 5.28002771,13.0642252 C5.20202883,13.1931428 5.24252825,13.3432939 5.31902714,13.4813115 C5.31002728,13.502545 5.30402736,13.5252952 5.28452765,13.5343952 C5.19302895,13.5829289 5.21702861,13.6117458 5.24402822,13.7103298 C5.26802788,13.7952638 5.259028,13.9545149 5.23952829,14.0394488 C5.22452851,14.1061826 5.15852944,14.2669504 5.07753061,14.2290335 C5.03553122,14.2078 4.99053186,14.1911166 4.95153241,14.2305502 C4.93353267,14.2472336 4.92153284,14.2684671 4.91553293,14.2912173 C4.88853332,14.292734 4.8615337,14.2957673 4.83603407,14.301834 C4.7850348,14.3124508 4.73103557,14.3245842 4.68153629,14.3033507 C4.63203699,14.2821172 4.56603794,14.2411669 4.51053874,14.2593671 C4.4640394,14.2745338 4.37254072,14.3154841 4.35304099,14.3640178 C4.34404113,14.3852513 4.37254072,14.4413683 4.37254072,14.4701852 C4.37104073,14.5217523 4.41154015,14.5991028 4.39504039,14.6461198 C4.35754093,14.6279197 4.30204173,14.621853 4.27654209,14.583936 C4.25254243,14.5520858 4.21654295,14.5596692 4.18954334,14.5263023 C4.18354343,14.583936 4.16254372,14.6643199 4.09354471,14.6794867 C4.02754566,14.6946535 3.96304658,14.6415698 3.89554755,14.6597699 C3.71855009,14.7052702 4.00204603,14.9267051 4.03954548,14.9691721 C4.10254459,15.0404559 4.12354428,15.1344899 4.17004361,15.2148738 C4.22104289,15.3028411 4.32604139,15.331658 4.3875405,15.4090085 C4.43853977,15.4742257 4.44903961,15.562193 4.52253857,15.6107267 C4.60353741,15.6668438 4.67703635,15.7168941 4.71003588,15.8124448 C4.74453538,15.7790779 4.81653436,15.9368123 4.8900333,15.8109281 C4.92903274,15.7426776 4.99803174,15.6835272 5.04153113,15.7942447 C5.07903059,15.8897953 5.04153113,15.9504624 5.12402995,16.0338797 C5.18702904,16.0990968 5.18402909,16.1764474 5.07603062,16.168864 C5.09403037,16.2173977 5.12252996,16.2659313 5.07603062,16.309915 C5.05503093,16.3311485 4.9950318,16.3751321 5.03853117,16.4054657 C5.08803045,16.3827155 5.1405297,16.3690654 5.190029,16.3463152 C5.24402822,16.3235651 5.29652747,16.2689647 5.35952656,16.2704814 C5.36252653,16.2901982 5.27552777,16.3478319 5.32952699,16.352382 C5.37452635,16.356932 5.43902543,16.309915 5.47502491,16.3493486 C5.51552433,16.3918156 5.46452506,16.4570327 5.48852472,16.5040497 C5.51252437,16.5525834 5.59352321,16.5161831 5.63252265,16.5237665 C5.61602289,16.5662335 5.55302379,16.5586501 5.51852428,16.5753335 C5.59802315,16.6724009 5.49452463,16.8149686 5.37902629,16.8180019 C5.32352708,16.8180019 5.1270299,16.592017 5.11803004,16.737618 C5.11653005,16.780085 5.13002986,16.8331687 5.14352967,16.874119 C5.16152941,16.9272027 5.29052756,16.9044525 5.3370269,16.9241693 C5.40452592,16.9514695 5.4825248,17.0166867 5.50952442,17.0849372 C5.53502405,17.1547043 5.59352321,17.2002046 5.61602289,17.2669384 C5.65952227,17.391306 5.77802057,17.4079894 5.90101881,17.4443897 C6.06451645,17.4929234 5.9850176,17.7462085 5.97601774,17.8675427 C5.96851783,17.9873602 6.13651543,18.0161771 6.21301434,18.0844276 C6.29851311,18.1587448 6.31201291,18.3028291 6.17101494,18.3149625 C6.09901596,18.3210293 5.97901769,18.2876624 5.95201807,18.3801797 C5.91301863,18.5106139 6.11101579,18.4969638 6.19801455,18.5257807 C6.23851397,18.5394308 6.40201163,18.5561143 6.41701141,18.5940312 C6.43951109,18.6531816 6.42151135,18.7335655 6.44251104,18.7957493 C6.49351031,18.9550004 6.64050821,19.0611678 6.78300616,19.1400351 C7.08900178,19.3114196 7.45199657,19.4191037 7.78799176,19.5146544 C7.97548908,19.5692548 8.16598634,19.6117217 8.35798358,19.6375053 C8.54398093,19.6617721 8.70747857,19.6466053 8.86647629,19.7497394 C8.97597473,19.8210232 9.04497373,19.7679395 9.15747212,19.7876563 C9.20547143,19.7967564 9.22647114,19.84074 9.26247062,19.8665236 C9.30297004,19.8983738 9.34946938,19.8574235 9.39296875,19.8741069 C9.40046864,19.8452901 9.39746869,19.8179899 9.38396888,19.7906897 C9.46946765,19.8225399 9.57446615,19.9135406 9.66446486,19.84074 C9.70946422,19.8043398 9.73946379,19.7542894 9.78596312,19.7194058 C9.84146232,19.7239559 9.89546155,19.7269892 9.95096074,19.7269892 C10.1834574,19.7269892 10.3664548,19.6208218 10.5524521,19.4964543 C10.7519493,19.3629866 10.9919458,19.3599533 11.2229425,19.3387198 C11.467439,19.314453 11.7269353,19.2826027 11.9504321,19.1764353 C12.1454293,19.0824013 12.1949286,18.9110168 12.2504278,18.7214321 C12.3104269,18.515164 12.4904244,18.4317467 12.6089227,18.2649122 C12.7484206,18.0707775 12.656922,17.8174923 12.7709203,17.6157743 L12.7709203,17.6157743 L12.7709203,17.6157743 Z M3.82354859,14.8796881 C3.81604868,14.8311544 3.79804894,14.8008209 3.76054949,14.772004 C3.75904951,14.7765541 3.75754954,14.7795874 3.75754954,14.7856541 C3.70655026,14.7598706 3.70055035,14.6628033 3.63155133,14.6703866 C3.57455216,14.579386 3.43055421,14.6203363 3.38555487,14.7037535 C3.3555553,14.7598706 3.39155478,14.7856541 3.4245543,14.8266044 C3.4665537,14.8781714 3.45905381,14.9176051 3.4740536,14.9767555 C3.51155306,15.1299399 3.63905124,15.0252892 3.72904994,15.0844396 C3.76354945,15.1071897 3.77704925,15.1724069 3.82804851,15.1617901 C3.8850477,15.14814 3.88204775,15.0632061 3.86704796,15.0252892 C3.84604827,14.9706888 3.83104848,14.9388385 3.82354859,14.8796881 L3.82354859,14.8796881 L3.82354859,14.8796881 Z M5.0220314,16.686051 C5.03103128,16.6602675 4.99053186,16.6314506 4.96353224,16.6329673 C4.93953258,16.634484 4.91703292,16.6678509 4.90803304,16.686051 C4.87803347,16.7391347 4.911033,16.8104185 4.980032,16.8104185 C4.99653177,16.7816017 4.99053186,16.7254846 5.03403123,16.7209346 C5.03103128,16.7072845 5.0220314,16.7027344 5.01003157,16.6981844 L5.0220314,16.686051 L5.0220314,16.686051 Z M3.74704968,14.7613873 C3.75154962,14.7644206 3.75604955,14.767454 3.76054949,14.772004 C3.76504943,14.7644206 3.76954936,14.7583539 3.77254931,14.7492539 L3.74704968,14.7613873 L3.74704968,14.7613873 Z M17.8018483,16.8892858 C17.7763486,16.9135526 17.7838484,16.9408528 17.7718486,16.9696697 C17.7583489,17.0045532 17.7058496,17.0182033 17.67735,17.0364035 C17.6323506,17.0652203 17.6173508,17.1334708 17.5678516,17.1501543 C17.5468518,17.1183041 17.5153523,17.0288201 17.4718529,17.1061706 C17.4463533,17.156221 17.4628531,17.2062713 17.4223537,17.2517717 C17.3818543,17.2942386 17.3848542,17.3458057 17.3563546,17.3928227 C17.3128551,17.4671399 17.2693558,17.5111235 17.195857,17.5551072 C17.1373577,17.5899907 17.1253579,17.6552079 17.0833585,17.7037415 C17.0383591,17.7568253 16.9693601,17.7765421 16.9078611,17.8038423 C16.8643616,17.8220424 16.7923627,17.8736094 16.7428634,17.8447925 C16.6768644,17.8038423 16.766863,17.7310417 16.7983626,17.7052582 C16.8283622,17.6809914 16.9708602,17.5945408 16.9378606,17.5460071 C16.9153609,17.5141569 16.8343621,17.5187069 16.8013625,17.5217402 C16.7338636,17.5293236 16.6828643,17.5975741 16.6258651,17.6294243 C16.561366,17.6658246 16.5058668,17.6961582 16.4353678,17.720425 C16.354369,17.7492419 16.3483691,17.8250757 16.28387,17.8705761 C16.2328707,17.9100097 16.1668716,17.9403432 16.1008726,17.9403432 C16.0138739,17.9403432 16.0183738,17.8675427 15.9928741,17.8068756 C15.9598747,17.8114256 15.9313751,17.8538926 15.8998755,17.8675427 C15.8488762,17.8887762 15.8158767,17.913043 15.8383763,17.9691601 C15.859376,18.0267938 15.6268794,18.0798775 15.58638,18.1086944 C15.5773802,18.0798775 15.6223794,18.049544 15.6403792,18.0328605 C15.5683802,18.0267938 15.4873814,18.0889776 15.4138824,18.0995943 C15.3433835,18.1086944 15.2563847,18.1572281 15.2458848,18.2300286 C15.238385,18.2876624 15.1618861,18.2861457 15.1138867,18.3058625 C15.0343879,18.3392294 15.0673874,18.3862464 15.0523876,18.4499468 C15.0223881,18.5697644 14.7718916,18.4787637 14.8993898,18.3195126 C14.9443892,18.2633955 15.0118882,18.2269953 15.0523876,18.1693615 C15.098887,18.1026277 15.1063869,18.0192104 15.1423863,17.9479266 C15.0628875,17.9706768 15.0028883,18.0161771 14.9353893,18.0601607 C14.8588904,18.1102111 14.7943913,18.0980777 14.7088926,18.0813942 C14.609894,18.0601607 14.540895,18.1102111 14.4493963,18.1329613 C14.3908971,18.1466113 14.260399,18.1420613 14.2528991,18.2269953 C14.2483992,18.2815956 14.3428978,18.2922124 14.3743973,18.3240626 C14.4208967,18.3725963 14.471896,18.4438801 14.5108954,18.4984805 C14.5423949,18.5409475 14.6593933,18.6000979 14.6518934,18.6546983 C14.6398935,18.7608657 14.5063954,18.7426656 14.4343965,18.7608657 C14.3623975,18.7790658 14.3518977,18.844283 14.2993984,18.8837166 C14.2393993,18.9277002 14.1584004,18.8761332 14.0924014,18.9034334 C14.0249024,18.9292169 13.976903,18.989884 13.9184039,19.0293176 C13.8194053,19.0975681 13.7549062,19.0232509 13.6529077,19.0187009 C13.5704089,19.0156675 13.4954099,19.0535845 13.4174111,19.0702679 C13.3469121,19.0854347 13.2614133,19.0975681 13.2074141,19.1476185 C13.0889158,19.2537859 13.4699103,19.224969 13.5059098,19.2219356 C13.48941,19.2826027 13.4729103,19.3538866 13.4174111,19.3933202 C13.3484121,19.4433706 13.2509134,19.4312371 13.1714146,19.4494373 C13.1099155,19.464604 13.0244167,19.5328545 13.1144154,19.5813882 C13.1954142,19.6223385 13.2989128,19.601105 13.3799116,19.5722881 C13.4729103,19.5404379 13.559909,19.4903876 13.6574076,19.4691541 C13.7594062,19.4464039 13.8659046,19.4570206 13.9679032,19.4388205 C14.0774016,19.417587 14.1734002,19.3584366 14.2753988,19.3174863 C14.3728974,19.2780527 14.4748959,19.2628859 14.5783944,19.2598526 C14.5573947,19.2977695 14.4553962,19.2947362 14.4163967,19.3038362 C14.3398978,19.319003 14.2873986,19.3857368 14.2078997,19.3811868 C14.117901,19.3781534 14.1284009,19.4418539 14.0639018,19.4570206 C14.0219024,19.4676374 13.9229038,19.5647047 13.8914043,19.5040376 C13.8674046,19.4570206 13.8209053,19.4661207 13.8059055,19.5237545 C13.7939057,19.5662214 13.8179053,19.5813882 13.7594062,19.5829049 C13.7144068,19.5829049 13.6934071,19.5647047 13.6529077,19.554088 C13.5719088,19.5328545 13.5359094,19.6162718 13.4789102,19.6375053 C13.3949114,19.6693555 13.3049127,19.6587387 13.2239138,19.7118225 C13.1774145,19.742156 13.1264152,19.7512561 13.070916,19.7679395 C12.9704175,19.7997897 12.8759188,19.8377067 12.7754203,19.8710736 C12.6974214,19.8983738 12.6194225,19.9317407 12.5354237,19.9332574 C12.5009242,19.9332574 12.3629262,19.9089905 12.3404265,19.9499408 C12.2984271,20.0257747 12.4274253,19.9969578 12.4544249,19.981791 C12.5309238,19.9408407 12.6239224,19.9651076 12.7094212,19.9651076 C12.8144197,19.9651076 12.9014185,19.9438741 12.9959171,19.8953404 C13.0214167,19.8816903 13.1909143,19.8452901 13.1999142,19.8650069 C13.214914,19.8741069 13.3274123,19.8331566 13.3469121,19.8286066 C13.4384108,19.8073731 13.5299094,19.7876563 13.6199082,19.7649062 C13.8974042,19.6936223 14.1704003,19.5859382 14.4403964,19.4934209 C14.9788887,19.3114196 15.4738816,19.0035341 15.9418748,18.6910985 C16.1533718,18.5500475 16.3258694,18.3650129 16.5553661,18.2497454 C16.7863628,18.1344779 16.9933599,17.9812935 17.2048568,17.8372092 C17.4118538,17.6961582 17.5693515,17.5020234 17.7418491,17.3245722 C17.9158466,17.1440876 18.0628444,16.9742197 18.1393434,16.7345847 C18.095844,16.7239679 18.0508447,16.8134519 18.0148451,16.8301353 C17.954846,16.8589522 17.8498476,16.8437854 17.8018483,16.8892858 L17.8018483,16.8892858 L17.8018483,16.8892858 Z M16.5088668,16.6451007 C16.561366,16.5738169 16.5163667,16.4767495 16.423368,16.5328666 C16.3888685,16.5525834 16.3933683,16.592017 16.3663688,16.6162838 C16.3378692,16.6420673 16.3348692,16.6117338 16.3063697,16.6041504 C16.2658703,16.5950503 16.1998712,16.6496507 16.1863714,16.686051 C16.1308722,16.6845343 16.0813728,16.7467181 16.1068725,16.7952518 C16.1803715,16.7679516 16.2253709,16.6997011 16.3048696,16.7209346 C16.3678688,16.737618 16.4683674,16.6981844 16.5088668,16.6451007 L16.5088668,16.6451007 L16.5088668,16.6451007 Z" />
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/conn-secure-dv.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
+  <style>
+    .icon-default {
+      fill: #4d9a26;
+    }
+  </style>
+
+  <defs>
+    <rect id="shape-lock-clasp-outer" x="5" y="1" width="14" height="20" rx="7" ry="7" />
+    <rect id="shape-lock-clasp-inner" x="8" y="4" width="8" height="14" rx="4" ry="4" />
+    <rect id="shape-lock-base" x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+
+    <mask id="mask-clasp-cutout">
+      <rect width="24" height="24" fill="#000" />
+      <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+      <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+    </mask>
+  </defs>
+
+  <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" class="icon-default" />
+  <use xlink:href="#shape-lock-base" class="icon-default" />
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/conn-secure-ev.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
+  <style>
+    .icon-default {
+      fill: #4d9a26;
+    }
+  </style>
+
+  <defs>
+    <rect id="shape-lock-clasp-outer" x="5" y="1" width="14" height="20" rx="7" ry="7" />
+    <rect id="shape-lock-clasp-inner" x="8" y="4" width="8" height="14" rx="4" ry="4" />
+    <rect id="shape-lock-base" x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+
+    <mask id="mask-clasp-cutout">
+      <rect width="16" height="16" fill="#000" />
+      <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+      <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+    </mask>
+
+    <mask id="mask-verified-cutout">
+      <rect width="24" height="24" fill="#fff" />
+      <circle cx="18" cy="18" r="7" fill="#000" />
+    </mask>
+  </defs>
+
+  <g mask="url(#mask-verified-cutout)">
+    <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" class="icon-default" />
+    <use xlink:href="#shape-lock-base" class="icon-default" />
+  </g>
+  <circle cx="18" cy="18" r="5.25" fill="#66cc33" />
+
+  <path fill="#fff" d="M17.4,21l-3.1-3c-0.1-0.1-0.1-0.2,0-0.3l0.7-0.9c0.1-0.1,0.2-0.1,0.3,0l2.1,2l3-4c0.1-0.1,0.2-0.1,0.3,0 l0.9,0.9c0.1,0.1,0.1,0.2,0,0.3L17.7,21C17.6,21,