Merge m-c to oak
authorRobert Strong <robert.bugzilla@gmail.com>
Thu, 06 Jul 2017 21:24:31 -0700
changeset 1476290 67952768c03fe5108096e5d3d360cac6de4c9af6
parent 1476289 3d0623c4220ff32ec943c35fc6a8556aad226768 (current diff)
parent 1178160 20f32734df750bddada9d1edca665c2ea53946f0 (diff)
child 1476291 41e3fe206cb661142bc64918d2371b1c24c91976
push id263306
push usercatlee@mozilla.com
push dateFri, 06 Apr 2018 15:43:50 +0000
treeherdertry@a0bfb6549eeb [default view] [failures only]
milestone56.0a1
Merge m-c to oak
CLOBBER
browser/app/profile/firefox.js
browser/base/content/browser.css
browser/base/content/browser.js
browser/components/customizableui/content/panelUI.inc.xul
browser/components/customizableui/content/panelUI.js
browser/components/customizableui/test/browser.ini
browser/components/nsBrowserGlue.js
browser/config/mozconfigs/win32/debug
browser/config/mozconfigs/win64/debug
browser/locales/en-US/chrome/browser/browser.dtd
browser/modules/ExtensionsUI.jsm
browser/themes/shared/customizableui/panelUI.inc.css
dom/plugins/ipc/PluginAsyncSurrogate.cpp
dom/plugins/ipc/PluginAsyncSurrogate.h
dom/plugins/ipc/PluginDataResolver.h
gfx/thebes/gfxMatrix.cpp
gfx/thebes/gfxRect.cpp
js/src/wasm/WasmSignalHandlers.cpp
mobile/android/app/src/main/res/color/url_bar_title.xml
mobile/android/app/src/main/res/color/url_bar_title_hint.xml
mobile/android/app/src/main/res/drawable/url_bar_bg.xml
mobile/android/app/src/main/res/layout-large-v11/browser_toolbar.xml
mobile/android/app/src/main/res/layout/browser_toolbar.xml
mobile/android/app/src/main/res/layout/toolbar_display_layout.xml
mobile/android/app/src/main/res/layout/toolbar_edit_layout.xml
old-configure.in
other-licenses/7zstub/firefox/7zSD.sfx
parser/htmlparser/nsToken.h
security/sandbox/linux/SandboxHooks.cpp
services/common/blocklist-clients.js
services/common/tests/unit/test_blocklist_clients.js
taskcluster/ci/build/macosx.yml
taskcluster/ci/nightly-l10n/kind.yml
taskcluster/ci/test/test-platforms.yml
taskcluster/ci/test/tests.yml
taskcluster/taskgraph/transforms/tests.py
testing/firefox-ui/resources/addons/extensions/restartless_addon_signed.xpi
testing/firefox-ui/resources/addons/extensions/restartless_addon_unsigned.xpi
testing/mochitest/mochitest_options.py
testing/mochitest/runtests.py
testing/talos/talos/config.py
testing/talos/talos/startup_test/tpaint.html
testing/web-platform/meta/XMLHttpRequest/open-during-abort-processing.htm.ini
testing/web-platform/meta/cssom/cssimportrule.html.ini
testing/web-platform/meta/web-animations/animation-model/animation-types/discrete-animation.html.ini
testing/web-platform/meta/webdriver/contexts.py.ini
testing/web-platform/meta/webdriver/navigation.py.ini
testing/web-platform/tests/dom/nodes/Document-contentType/contentType/contenttype_datauri_01.html
testing/web-platform/tests/webdriver/actions/__init__.py
testing/web-platform/tests/webdriver/actions/conftest.py
testing/web-platform/tests/webdriver/actions/key.py
testing/web-platform/tests/webdriver/actions/mouse.py
testing/web-platform/tests/webdriver/actions/sequence.py
testing/web-platform/tests/webdriver/actions/special_keys.py
testing/web-platform/tests/webdriver/actions/support/__init__.py
testing/web-platform/tests/webdriver/actions/support/keys.py
testing/web-platform/tests/webdriver/actions/support/refine.py
testing/web-platform/tests/webdriver/actions/support/test_actions_wdspec.html
testing/web-platform/tests/webdriver/conftest.py
testing/web-platform/tests/webdriver/contexts.py
testing/web-platform/tests/webdriver/cookies.py
testing/web-platform/tests/webdriver/navigation.py
testing/web-platform/tests/webdriver/support/__init__.py
testing/web-platform/tests/webdriver/support/asserts.py
testing/web-platform/tests/webdriver/support/fixtures.py
testing/web-platform/tests/webdriver/support/http_request.py
testing/web-platform/tests/webdriver/support/inline.py
testing/web-platform/tests/webdriver/support/merge_dictionaries.py
testing/web-platform/tests/webdriver/window_maximizing.py
toolkit/components/telemetry/Histograms.json
toolkit/components/url-classifier/content/moz/alarm.js
toolkit/components/url-classifier/content/moz/cryptohasher.js
toolkit/components/url-classifier/content/moz/debug.js
toolkit/components/url-classifier/content/moz/observer.js
toolkit/components/url-classifier/content/moz/preferences.js
toolkit/components/url-classifier/content/moz/protocol4.js
toolkit/components/url-classifier/content/multi-querier.js
toolkit/components/url-classifier/content/trtable.js
toolkit/components/url-classifier/content/wireformat.js
toolkit/components/url-classifier/content/xml-fetcher.js
toolkit/components/url-classifier/nsIUrlClassifierTable.idl
toolkit/crashreporter/CrashSubmit.jsm
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/modules/moz.build
toolkit/modules/sessionstore/XPathGenerator.jsm
toolkit/xre/nsAppRunner.cpp
xpcom/io/nsDirectoryService.cpp
xpcom/threads/HangMonitor.cpp
--- a/.eslintignore
+++ b/.eslintignore
@@ -133,19 +133,16 @@ devtools/server/tests/browser/storage-*.
 devtools/server/tests/browser/stylesheets-nested-iframes.html
 devtools/server/tests/unit/xpcshell_debugging_script.js
 devtools/shared/platform/content/test/test_clipboard.html
 devtools/shared/qrcode/tests/mochitest/test_decode.html
 devtools/shared/tests/mochitest/*.html
 devtools/shared/webconsole/test/test_*.html
 
 # Ignore devtools pre-processed files
-devtools/client/framework/toolbox-process-window.js
-devtools/client/performance/system.js
-devtools/client/webide/webide-prefs.js
 devtools/client/preferences/**
 
 # Ignore devtools third-party libs
 devtools/shared/jsbeautify/*
 devtools/shared/acorn/*
 devtools/shared/gcli/source/*
 devtools/shared/node-properties/*
 devtools/shared/pretty-fast/*
@@ -312,16 +309,17 @@ testing/marionette/harness/**
 testing/mochitest/**
 # third party modules
 testing/modules/ajv-4.1.1.js
 testing/modules/sinon-2.3.2.js
 # octothorpe used for pref file comment causes parsing error
 testing/mozbase/mozprofile/tests/files/prefs_with_comments.js
 testing/talos/talos/scripts/jszip.min.js
 testing/talos/talos/startup_test/sessionrestore/profile/sessionstore.js
+testing/talos/talos/startup_test/sessionrestore/profile-manywindows/sessionstore.js
 testing/talos/talos/tests/canvasmark/**
 testing/talos/talos/tests/dromaeo/**
 testing/talos/talos/tests/v8_7/**
 testing/talos/talos/tests/kraken/**
 
 testing/web-platform/**
 testing/xpcshell/moz-http2/**
 testing/xpcshell/node-http2/**
--- a/accessible/.eslintrc.js
+++ b/accessible/.eslintrc.js
@@ -5,24 +5,20 @@ module.exports = {
     // Warn about cyclomatic complexity in functions.
     "complexity": ["error", 42],
 
     // XXX These are rules that are enabled in the recommended configuration, but
     // disabled here due to failures when initially implemented. They should be
     // removed (and hence enabled) at some stage.
     "brace-style": "off",
     "consistent-return": "off",
-    "func-call-spacing": "off",
     "quotes": "off",
     "object-shorthand": "off",
     "space-before-function-paren": "off",
     "space-infix-ops": "off",
-    "key-spacing": "off",
-    "keyword-spacing": "off",
     "no-else-return": "off",
     "no-multi-spaces": "off",
-    "no-trailing-spaces": "off",
     "no-unexpected-multiline": "off",
     "no-unsafe-finally": "off",
     "no-useless-call": "off",
     "spaced-comment": "off",
   }
 };
--- a/accessible/atk/nsStateMap.h
+++ b/accessible/atk/nsStateMap.h
@@ -105,10 +105,11 @@ static const AtkStateMap gAtkStateMap[] 
   { ATK_STATE_SINGLE_LINE,                    kMapDirectly },   // states::SINGLE_LINE             = 1 << 40
   { ATK_STATE_TRANSIENT,                      kMapDirectly },   // states::TRANSIENT               = 1 << 41
   { ATK_STATE_VERTICAL,                       kMapDirectly },   // states::VERTICAL                = 1 << 42
   { ATK_STATE_STALE,                          kMapDirectly },   // states::STALE                   = 1 << 43
   { ATK_STATE_ENABLED,                        kMapDirectly },   // states::ENABLED                 = 1 << 44
   { ATK_STATE_SENSITIVE,                      kMapDirectly },   // states::SENSITIVE               = 1 << 45
   { ATK_STATE_EXPANDABLE,                     kMapDirectly },   // states::EXPANDABLE              = 1 << 46
   { kNone,                                    kMapDirectly },   // states::PINNED                  = 1 << 47
-  { kNone,                                    kNoSuchState },   //                                 = 1 << 48
+  { ATK_STATE_ACTIVE,                         kMapDirectly },   // states::CURRENT                 = 1 << 48
+  { kNone,                                    kNoSuchState },   //                                 = 1 << 49
 };
--- a/accessible/base/ARIAMap.cpp
+++ b/accessible/base/ARIAMap.cpp
@@ -592,16 +592,26 @@ static const nsRoleMapEntry sWAIRoleMaps
     roles::GROUPING,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
     kGenericAccType,
     kNoReqStates
   },
+  { // figure
+    &nsGkAtoms::figure,
+    roles::FIGURE,
+    kUseMapRole,
+    eNoValue,
+    eNoAction,
+    eNoLiveAttr,
+    kGenericAccType,
+    kNoReqStates
+  },
   { // form
     &nsGkAtoms::form,
     roles::FORM,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
     eLandmark,
@@ -897,22 +907,22 @@ static const nsRoleMapEntry sWAIRoleMaps
     eNoLiveAttr,
     kGenericAccType,
     kNoReqStates,
     eARIAOrientation,
     eARIAReadonly
   },
   { // region
     &nsGkAtoms::region,
-    roles::PANE,
+    roles::REGION,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
-    kGenericAccType,
+    eLandmark,
     kNoReqStates
   },
   { // row
     &nsGkAtoms::row,
     roles::ROW,
     kUseMapRole,
     eNoValue,
     eNoAction,
@@ -1197,16 +1207,17 @@ nsRoleMapEntry aria::gEmptyRoleMap = {
 
 /**
  * Universal (Global) states:
  * The following state rules are applied to any accessible element,
  * whether there is an ARIA role or not:
  */
 static const EStateRule sWAIUnivStateMap[] = {
   eARIABusy,
+  eARIACurrent,
   eARIADisabled,
   eARIAExpanded,  // Currently under spec review but precedent exists
   eARIAHasPopup,  // Note this is technically a "property"
   eARIAInvalid,
   eARIAModal,
   eARIARequired,  // XXX not global, Bug 553117
   eARIANone
 };
--- a/accessible/base/ARIAStateMap.cpp
+++ b/accessible/base/ARIAStateMap.cpp
@@ -140,16 +140,26 @@ aria::MapToState(EStateRule aRule, dom::
       static const TokenTypeData data(
         nsGkAtoms::aria_checked, eMixedType,
         states::CHECKABLE, states::CHECKED);
 
       MapTokenType(aElement, aState, data);
       return true;
     }
 
+    case eARIACurrent:
+    {
+      static const TokenTypeData data(
+        nsGkAtoms::aria_current, eBoolType,
+        0, states::CURRENT);
+
+      MapTokenType(aElement, aState, data);
+      return true;
+    }
+
     case eARIADisabled:
     {
       static const TokenTypeData data(
         nsGkAtoms::aria_disabled, eBoolType,
         0, states::UNAVAILABLE);
 
       MapTokenType(aElement, aState, data);
       return true;
@@ -349,20 +359,22 @@ MapEnumType(dom::Element* aElement, uint
   }
 }
 
 static void
 MapTokenType(dom::Element* aElement, uint64_t* aState,
              const TokenTypeData& aData)
 {
   if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) {
-    if ((aData.mType & eMixedType) &&
-        aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
+    if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
                               nsGkAtoms::mixed, eCaseMatters)) {
-      *aState |= aData.mPermanentState | states::MIXED;
+      if (aData.mType & eMixedType)
+        *aState |= aData.mPermanentState | states::MIXED;
+      else // unsupported use of 'mixed' is an authoring error
+        *aState |= aData.mPermanentState | aData.mFalseState;
       return;
     }
 
     if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
                               nsGkAtoms::_false, eCaseMatters)) {
       *aState |= aData.mPermanentState | aData.mFalseState;
       return;
     }
--- a/accessible/base/ARIAStateMap.h
+++ b/accessible/base/ARIAStateMap.h
@@ -24,16 +24,17 @@ namespace aria {
 enum EStateRule
 {
   eARIANone,
   eARIAAutoComplete,
   eARIABusy,
   eARIACheckableBool,
   eARIACheckableMixed,
   eARIACheckedMixed,
+  eARIACurrent,
   eARIADisabled,
   eARIAExpanded,
   eARIAHasPopup,
   eARIAInvalid,
   eARIAModal,
   eARIAMultiline,
   eARIAMultiSelectable,
   eARIAOrientation,
--- a/accessible/base/Role.h
+++ b/accessible/base/Role.h
@@ -1002,17 +1002,25 @@ enum Role {
 
   /**
    * A complete or self-contained composition in a document, page, application,
    * or site and that is, in principle, independently distributable or reusable,
    * e.g. in syndication.
    */
   ARTICLE = 172,
 
-  LAST_ROLE = ARTICLE
+  /**
+   * A perceivable section containing content that is relevant to a specific,
+   * author-specified purpose and sufficiently important that users will likely
+   * want to be able to navigate to the section easily and to have it listed in
+   * a summary of the page.
+   */
+  REGION = 173,
+
+  LAST_ROLE = REGION
 };
 
 } // namespace role
 
 typedef enum mozilla::a11y::roles::Role role;
 
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/base/RoleMap.h
+++ b/accessible/base/RoleMap.h
@@ -1394,8 +1394,16 @@ ROLE(FOOTNOTE,
 
 ROLE(ARTICLE,
      "article",
      ATK_ROLE_ARTICLE,
      NSAccessibilityGroupRole,
      ROLE_SYSTEM_DOCUMENT,
      ROLE_SYSTEM_DOCUMENT,
      eNoNameRule)
+
+ROLE(REGION,
+     "region",
+     ATK_ROLE_LANDMARK,
+     NSAccessibilityGroupRole,
+     USE_ROLE_STRING,
+     IA2_ROLE_LANDMARK,
+     eNoNameRule)
--- a/accessible/base/States.h
+++ b/accessible/base/States.h
@@ -272,14 +272,20 @@ namespace states {
    * @see EXPANDED and COLLAPSED states.
    */
   const uint64_t EXPANDABLE = ((uint64_t) 0x1) << 46;
 
   /**
    * The object is pinned, usually indicating it is fixed in place and has permanence.
    */
   const uint64_t PINNED = ((uint64_t) 0x1) << 47;
+
+  /**
+   * The object is the current item within a container or set of related elements.
+   */
+  const uint64_t CURRENT = ((uint64_t) 0x1) << 48;
+
 } // namespace states
 } // namespace a11y
 } // namespace mozilla
 
 #endif
 	
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -1409,16 +1409,32 @@ Accessible::SetCurValue(double aValue)
 
   return NS_SUCCEEDED(
     mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
 }
 
 role
 Accessible::ARIATransformRole(role aRole)
 {
+  // Beginning with ARIA 1.1, user agents are expected to use the native host
+  // language role of the element when the region role is used without a name.
+  // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-region
+  //
+  // XXX: While the name computation algorithm can be non-trivial in the general
+  // case, it should not be especially bad here: If the author hasn't used the
+  // region role, this calculation won't occur. And the region role's name
+  // calculation rule excludes name from content. That said, this use case is
+  // another example of why we should consider caching the accessible name. See:
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
+  if (aRole == roles::REGION) {
+    nsAutoString name;
+    Name(name);
+    return name.IsEmpty() ? NativeRole() : aRole;
+  }
+
   // XXX: these unfortunate exceptions don't fit into the ARIA table. This is
   // where the accessible role depends on both the role and ARIA state.
   if (aRole == roles::PUSHBUTTON) {
     if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) {
       // For simplicity, any existing pressed attribute except "" or "undefined"
       // indicates a toggle.
       return roles::TOGGLE_BUTTON;
     }
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1049,16 +1049,23 @@ DocAccessible::ARIAAttributeChanged(Acce
   if (aAttribute == nsGkAtoms::aria_valuenow &&
       (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
        elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
                         nsGkAtoms::_empty, eCaseMatters))) {
     FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
     return;
   }
 
+  if (aAttribute == nsGkAtoms::aria_current) {
+    RefPtr<AccEvent> event =
+      new AccStateChangeEvent(aAccessible, states::CURRENT);
+    FireDelayedEvent(event);
+    return;
+  }
+
   if (aAttribute == nsGkAtoms::aria_owns) {
     mNotificationController->ScheduleRelocation(aAccessible);
   }
 }
 
 void
 DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible)
 {
--- a/accessible/interfaces/nsIAccessibleRole.idl
+++ b/accessible/interfaces/nsIAccessibleRole.idl
@@ -996,9 +996,17 @@ interface nsIAccessibleRole : nsISupport
   const unsigned long ROLE_FOOTNOTE = 171;
 
   /**
    * A complete or self-contained composition in a document, page, application,
    * or site and that is, in principle, independently distributable or reusable,
    * e.g. in syndication.
    */
   const unsigned long ROLE_ARTICLE = 172;
+
+  /**
+   * A perceivable section containing content that is relevant to a specific,
+   * author-specified purpose and sufficiently important that users will likely
+   * want to be able to navigate to the section easily and to have it listed in
+   * a summary of the page.
+   */
+  const unsigned long ROLE_REGION = 173;
 };
--- a/accessible/interfaces/nsIAccessibleStates.idl
+++ b/accessible/interfaces/nsIAccessibleStates.idl
@@ -67,10 +67,11 @@ interface nsIAccessibleStates : nsISuppo
   const unsigned long  EXT_STATE_SINGLE_LINE             = 0x00000200;  // This text object can only contain 1 line of text    
   const unsigned long  EXT_STATE_TRANSIENT               = 0x00000400;  // 
   const unsigned long  EXT_STATE_VERTICAL                = 0x00000800;  // Especially used for sliders and scrollbars  
   const unsigned long  EXT_STATE_STALE                   = 0x00001000;  // Object not dead, but not up-to-date either
   const unsigned long  EXT_STATE_ENABLED                 = 0x00002000;  // A widget that is not unavailable
   const unsigned long  EXT_STATE_SENSITIVE               = 0x00004000;  // Same as ENABLED for now
   const unsigned long  EXT_STATE_EXPANDABLE              = 0x00008000;  // If COLLAPSED or EXPANDED
   const unsigned long  EXT_STATE_PINNED                  = 0x00010000;  // Indicates object is pinned.
+  const unsigned long  EXT_STATE_CURRENT                 = 0x00020000;  // Indicates object is the current item in its container
 };
 
--- a/accessible/ipc/win/PlatformChild.cpp
+++ b/accessible/ipc/win/PlatformChild.cpp
@@ -1,18 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/a11y/Compatibility.h"
 #include "mozilla/a11y/PlatformChild.h"
 #include "mozilla/mscom/EnsureMTA.h"
 #include "mozilla/mscom/InterceptorLog.h"
-#include "mozilla/WindowsVersion.h"
 
 #include "Accessible2.h"
 #include "Accessible2_2.h"
 #include "AccessibleHypertext2.h"
 #include "AccessibleTableCell.h"
 
 #include "AccessibleHypertext2_i.c"
 
@@ -44,30 +44,17 @@ static const mozilla::mscom::ArrayData s
 // we intend to instantiate them. Therefore RegisterProxy() must be called
 // via EnsureMTA.
 PlatformChild::PlatformChild()
   : mAccTypelib(mozilla::mscom::RegisterTypelib(L"oleacc.dll",
         mozilla::mscom::RegistrationFlags::eUseSystemDirectory))
   , mMiscTypelib(mozilla::mscom::RegisterTypelib(L"Accessible.tlb"))
   , mSdnTypelib(mozilla::mscom::RegisterTypelib(L"AccessibleMarshal.dll"))
 {
-  // The manifest for 32-bit Windows is embedded with resource ID 32.
-  // The manifest for 64-bit Windows is embedded with resource ID 64.
-  // Beginning with Windows 10 Creators Update, 32-bit builds use the 64-bit
-  // manifest.
-  WORD actCtxResourceId;
-#if defined(HAVE_64BIT_BUILD)
-  actCtxResourceId = 64;
-#else
-  if (IsWin10CreatorsUpdateOrLater()) {
-    actCtxResourceId = 64;
-  } else {
-    actCtxResourceId = 32;
-  }
-#endif
+  WORD actCtxResourceId = Compatibility::GetActCtxResourceId();
 
   mozilla::mscom::MTADeletePtr<mozilla::mscom::ActivationContextRegion> tmpActCtxMTA;
   mozilla::mscom::EnsureMTA([actCtxResourceId, &tmpActCtxMTA]() -> void {
     tmpActCtxMTA.reset(new mozilla::mscom::ActivationContextRegion(actCtxResourceId));
   });
   mActCtxMTA = Move(tmpActCtxMTA);
 
   mozilla::mscom::InterceptorLog::Init();
--- a/accessible/ipc/win/handler/AccessibleHandler.cpp
+++ b/accessible/ipc/win/handler/AccessibleHandler.cpp
@@ -217,27 +217,22 @@ AccessibleHandler::ReadHandlerPayload(IS
   if (!deserializer.Read(&mCachedData, &IA2Payload_Decode)) {
     return E_FAIL;
   }
 
   if (!mCachedData.mGeckoBackChannel) {
     return S_OK;
   }
 
-  long pid = static_cast<long>(::GetCurrentProcessId());
-
-  RefPtr<IHandlerControl> ctl;
-  HRESULT hr = gControlFactory.CreateInstance(nullptr, IID_IHandlerControl,
-                                              getter_AddRefs(ctl));
-  if (SUCCEEDED(hr)) {
-    hr = mCachedData.mGeckoBackChannel->put_HandlerControl(pid, ctl);
-    MOZ_ASSERT(SUCCEEDED(hr));
+  RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton());
+  if (!ctl) {
+    return E_OUTOFMEMORY;
   }
 
-  return hr;
+  return ctl->Register(WrapNotNull(mCachedData.mGeckoBackChannel));
 }
 
 REFIID
 AccessibleHandler::MarshalAs(REFIID aIid)
 {
   static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3,
                 "You have modified NEWEST_IA2_IID. This code needs updating.");
   if (aIid == IID_IAccessible2_3 || aIid == IID_IAccessible2_2 ||
--- a/accessible/ipc/win/handler/AccessibleHandlerControl.cpp
+++ b/accessible/ipc/win/handler/AccessibleHandlerControl.cpp
@@ -127,17 +127,18 @@ AccessibleHandlerControl::Create(Accessi
   }
 
   RefPtr<AccessibleHandlerControl> ctl(new AccessibleHandlerControl());
   ctl.forget(aOutObject);
   return S_OK;
 }
 
 AccessibleHandlerControl::AccessibleHandlerControl()
-  : mCacheGen(0)
+  : mIsRegistered(false)
+  , mCacheGen(0)
   , mIA2Proxy(mscom::RegisterProxy(L"ia2marshal.dll"))
   , mHandlerProxy(mscom::RegisterProxy())
 {
   MOZ_ASSERT(mIA2Proxy);
 }
 
 IMPL_IUNKNOWN1(AccessibleHandlerControl, IHandlerControl)
 
@@ -184,10 +185,24 @@ AccessibleHandlerControl::GetHandlerType
   if (!mHandlerProxy) {
     return E_UNEXPECTED;
   }
 
   return mHandlerProxy->GetTypeInfoForGuid(CLSID_AccessibleHandler,
                                            aOutTypeInfo);
 }
 
+HRESULT
+AccessibleHandlerControl::Register(NotNull<IGeckoBackChannel*> aGecko)
+{
+  if (mIsRegistered) {
+    return S_OK;
+  }
+
+  long pid = static_cast<long>(::GetCurrentProcessId());
+  HRESULT hr = aGecko->put_HandlerControl(pid, this);
+  mIsRegistered = SUCCEEDED(hr);
+  MOZ_ASSERT(mIsRegistered);
+  return hr;
+}
+
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/ipc/win/handler/AccessibleHandlerControl.h
+++ b/accessible/ipc/win/handler/AccessibleHandlerControl.h
@@ -66,20 +66,23 @@ public:
     return mCacheGen;
   }
 
   HRESULT GetNewText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewText);
   HRESULT GetOldText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldText);
 
   HRESULT GetHandlerTypeInfo(ITypeInfo** aOutTypeInfo);
 
+  HRESULT Register(NotNull<IGeckoBackChannel*> aGecko);
+
 private:
   AccessibleHandlerControl();
   ~AccessibleHandlerControl() = default;
 
+  bool mIsRegistered;
   uint32_t mCacheGen;
   detail::TextChange mTextChange;
   UniquePtr<mscom::RegisteredProxy> mIA2Proxy;
   UniquePtr<mscom::RegisteredProxy> mHandlerProxy;
 };
 
 extern mscom::SingletonFactory<AccessibleHandlerControl> gControlFactory;
 
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -589,17 +589,17 @@ var Output = {
 
     for (let androidEvent of aDetails) {
       androidEvent.type = 'Accessibility:Event';
       if (androidEvent.bounds) {
         androidEvent.bounds = AccessFu.adjustContentBounds(
           androidEvent.bounds, aBrowser);
       }
 
-      switch(androidEvent.eventType) {
+      switch (androidEvent.eventType) {
         case ANDROID_VIEW_TEXT_CHANGED:
           androidEvent.brailleOutput = this.brailleState.adjustText(
             androidEvent.text);
           break;
         case ANDROID_VIEW_TEXT_SELECTION_CHANGED:
           androidEvent.brailleOutput = this.brailleState.adjustSelection(
             androidEvent.brailleOutput);
           break;
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -319,17 +319,17 @@ this.ContentControl.prototype = {
     return true;
   },
 
   handleMoveByGranularity: function cc_handleMoveByGranularity(aMessage) {
     // XXX: Add sendToChild. Right now this is only used in Android, so no need.
     let direction = aMessage.json.direction;
     let granularity;
 
-    switch(aMessage.json.granularity) {
+    switch (aMessage.json.granularity) {
       case MOVEMENT_GRANULARITY_CHARACTER:
         granularity = Ci.nsIAccessiblePivot.CHAR_BOUNDARY;
         break;
       case MOVEMENT_GRANULARITY_WORD:
         granularity = Ci.nsIAccessiblePivot.WORD_BOUNDARY;
         break;
       default:
         return;
--- a/accessible/jsat/OutputGenerator.jsm
+++ b/accessible/jsat/OutputGenerator.jsm
@@ -209,17 +209,17 @@ var OutputGenerator = {
    * Adds math roles to the output, for a MathML accessible.
    * @param {Array} aOutput Output array.
    * @param {nsIAccessible} aAccessible current accessible object.
    * @param {String} aRoleStr aAccessible's role string.
    */
   _addMathRoles: function _addMathRoles(aOutput, aAccessible, aRoleStr) {
     // First, determine the actual role to use (e.g. mathmlfraction).
     let roleStr = aRoleStr;
-    switch(aAccessible.role) {
+    switch (aAccessible.role) {
       case Roles.MATHML_CELL:
       case Roles.MATHML_ENCLOSED:
       case Roles.MATHML_LABELED_ROW:
       case Roles.MATHML_ROOT:
       case Roles.MATHML_SQUARE_ROOT:
       case Roles.MATHML_TABLE:
       case Roles.MATHML_TABLE_ROW:
         // Use the default role string.
@@ -253,22 +253,22 @@ var OutputGenerator = {
         roleStr = null;
         break;
     }
 
     // Get the math role based on the position in the parent accessible
     // (e.g. numerator for the first child of a mathmlfraction).
     let mathRole = Utils.getMathRole(aAccessible);
     if (mathRole) {
-      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']
-        ({string: this._getOutputName(mathRole)});
+      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']({
+        string: this._getOutputName(mathRole)});
     }
     if (roleStr) {
-      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']
-        ({string: this._getOutputName(roleStr)});
+      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']({
+        string: this._getOutputName(roleStr)});
     }
   },
 
   /**
    * Adds MathML menclose notations to the output.
    * @param {Array} aOutput Output array.
    * @param {nsIAccessible} aAccessible current accessible object.
    */
@@ -679,24 +679,24 @@ this.UtteranceGenerator = {  // jshint i
 
       this._addName(utterance, aAccessible, aFlags);
       this._addLandmark(utterance, aAccessible);
 
       return utterance;
     },
 
     list: function list(aAccessible, aRoleStr, aState, aFlags) {
-      return this._getListUtterance
-        (aAccessible, aRoleStr, aFlags, aAccessible.childCount);
+      return this._getListUtterance(aAccessible, aRoleStr, aFlags,
+        aAccessible.childCount);
     },
 
     definitionlist:
       function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
-        return this._getListUtterance
-          (aAccessible, aRoleStr, aFlags, aAccessible.childCount / 2);
+        return this._getListUtterance(aAccessible, aRoleStr, aFlags,
+          aAccessible.childCount / 2);
       },
 
     application: function application(aAccessible, aRoleStr, aState, aFlags) {
       // Don't utter location of applications, it gets tiring.
       if (aAccessible.name != aAccessible.DOMNode.location) {
         return this.objectOutputFunctions.defaultFunc.apply(this,
           [aAccessible, aRoleStr, aState, aFlags]);
       }
--- a/accessible/jsat/PointerAdapter.jsm
+++ b/accessible/jsat/PointerAdapter.jsm
@@ -34,57 +34,57 @@ var PointerRelay = { // jshint ignore:li
    * value of |false| are cancelled and do not propogate to content.
    */
   get _eventsOfInterest() {
     delete this._eventsOfInterest;
 
     switch (Utils.widgetToolkit) {
       case 'android':
         this._eventsOfInterest = {
-          'touchstart' : true,
-          'touchmove' : true,
-          'touchend' : true };
+          'touchstart': true,
+          'touchmove': true,
+          'touchend': true };
         break;
 
       case 'gonk':
         this._eventsOfInterest = {
-          'touchstart' : true,
-          'touchmove' : true,
-          'touchend' : true,
-          'mousedown' : false,
-          'mousemove' : false,
+          'touchstart': true,
+          'touchmove': true,
+          'touchend': true,
+          'mousedown': false,
+          'mousemove': false,
           'mouseup': false,
           'click': false };
         break;
 
       default:
         // Desktop.
         this._eventsOfInterest = {
-          'mousemove' : true,
-          'mousedown' : true,
+          'mousemove': true,
+          'mousedown': true,
           'mouseup': true,
           'click': false
         };
         if ('ontouchstart' in Utils.win) {
           for (let eventType of ['touchstart', 'touchmove', 'touchend']) {
             this._eventsOfInterest[eventType] = true;
           }
         }
         break;
     }
 
     return this._eventsOfInterest;
   },
 
   _eventMap: {
-    'touchstart' : 'pointerdown',
-    'mousedown' : 'pointerdown',
-    'touchmove' : 'pointermove',
-    'mousemove' : 'pointermove',
-    'touchend' : 'pointerup',
+    'touchstart': 'pointerdown',
+    'mousedown': 'pointerdown',
+    'touchmove': 'pointermove',
+    'mousemove': 'pointermove',
+    'touchend': 'pointerup',
     'mouseup': 'pointerup'
   },
 
   start: function PointerRelay_start(aOnPointerEvent) {
     Logger.debug('PointerRelay.start');
     this.onPointerEvent = aOnPointerEvent;
     for (let eventType in this._eventsOfInterest) {
       Utils.win.addEventListener(eventType, this, true, true);
--- a/accessible/jsat/Utils.jsm
+++ b/accessible/jsat/Utils.jsm
@@ -488,17 +488,17 @@ this.Utils = { // jshint ignore:line
     return parent.role === Roles.LISTITEM && parent.childCount > 1 &&
       aStaticText.indexInParent === 0;
   },
 
   dispatchChromeEvent: function dispatchChromeEvent(aType, aDetails) {
     let details = {
       type: aType,
       details: JSON.stringify(
-        typeof aDetails === 'string' ? { eventType : aDetails } : aDetails)
+        typeof aDetails === 'string' ? { eventType: aDetails } : aDetails)
     };
     let window = this.win;
     let shell = window.shell || window.content.shell;
     if (shell) {
       // On B2G device.
       shell.sendChromeEvent(details);
     } else {
       // Dispatch custom event to have support for desktop and screen reader
@@ -632,17 +632,17 @@ this.Logger = { // jshint ignore:line
   },
 
   accessibleToString: function accessibleToString(aAccessible) {
     if (!aAccessible) {
       return '[ null ]';
     }
 
     try {
-      return'[ ' + Utils.AccService.getStringRole(aAccessible.role) +
+      return '[ ' + Utils.AccService.getStringRole(aAccessible.role) +
         ' | ' + aAccessible.name + ' ]';
     } catch (x) {
       return '[ defunct ]';
     }
   },
 
   eventToString: function eventToString(aEvent) {
     let str = Utils.AccService.getStringEventType(aEvent.eventType);
@@ -1063,11 +1063,11 @@ PrefCache.prototype = {
       try {
         this.callback(this.name, this.value, false);
       } catch (x) {
         Logger.logException(x);
       }
     }
   },
 
-  QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                           Ci.nsISupportsWeakReference])
 };
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -793,17 +793,17 @@ ConvertToNSArray(nsTArray<ProxyAccessibl
       return @"AXApplicationLog";
     if (roleAtom == nsGkAtoms::marquee)
       return @"AXApplicationMarquee";
     if (roleAtom == nsGkAtoms::math)
       return @"AXDocumentMath";
     if (roleAtom == nsGkAtoms::note_)
       return @"AXDocumentNote";
     if (roleAtom == nsGkAtoms::region)
-      return @"AXDocumentRegion";
+      return mRole == roles::REGION ? @"AXLandmarkRegion" : nil;
     if (roleAtom == nsGkAtoms::status)
       return @"AXApplicationStatus";
     if (roleAtom == nsGkAtoms::tabpanel)
       return @"AXTabPanel";
     if (roleAtom == nsGkAtoms::timer)
       return @"AXApplicationTimer";
     if (roleAtom == nsGkAtoms::tooltip)
       return @"AXUserInterfaceTooltip";
@@ -947,17 +947,16 @@ static const RoleDescrMap sRoleDescrMap[
   { @"AXApplicationStatus", NS_LITERAL_STRING("status") },
   { @"AXApplicationTimer", NS_LITERAL_STRING("timer") },
   { @"AXContentSeparator", NS_LITERAL_STRING("separator") },
   { @"AXDefinition", NS_LITERAL_STRING("definition") },
   { @"AXDocument", NS_LITERAL_STRING("document") },
   { @"AXDocumentArticle", NS_LITERAL_STRING("article") },
   { @"AXDocumentMath", NS_LITERAL_STRING("math") },
   { @"AXDocumentNote", NS_LITERAL_STRING("note") },
-  { @"AXDocumentRegion", NS_LITERAL_STRING("region") },
   { @"AXLandmarkApplication", NS_LITERAL_STRING("application") },
   { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
   { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
   { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
   { @"AXLandmarkMain", NS_LITERAL_STRING("main") },
   { @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") },
   { @"AXLandmarkRegion", NS_LITERAL_STRING("region") },
   { @"AXLandmarkSearch", NS_LITERAL_STRING("search") },
@@ -976,16 +975,19 @@ struct RoleDescrComparator
   }
 };
 
 - (NSString*)roleDescription
 {
   if (mRole == roles::DOCUMENT)
     return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
 
+  if (mRole == roles::FIGURE)
+    return utils::LocalizedString(NS_LITERAL_STRING("figure"));
+
   if (mRole == roles::HEADING)
     return utils::LocalizedString(NS_LITERAL_STRING("heading"));
 
   NSString* subrole = [self subrole];
 
   if (subrole) {
     size_t idx = 0;
     if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
--- a/accessible/tests/mochitest/aom/test_general.html
+++ b/accessible/tests/mochitest/aom/test_general.html
@@ -92,16 +92,16 @@
         'margin-left', 'text-align', 'text-indent', 'margin-right',
         'tag', 'margin-top', 'margin-bottom', 'display',
         'explicit-name'
       ];
     }
 
     is(anode.attributes.length, attrs.length, 'correct number of attributes');
     for (var i = 0; i < attrs.length; i++) {
-      is(anode.attributes[i], attrs[i],
-         `${attrs[i]} attribute is expected at ${i}th index`);
+      ok(attrs.indexOf(anode.attributes[i]) >= 0,
+         `${anode.attributes[i]} attribute is expected and found`);
     }
 
     finish();
   }
   </script>
 </head>
--- a/accessible/tests/mochitest/attributes.js
+++ b/accessible/tests/mochitest/attributes.js
@@ -91,17 +91,17 @@ function testGroupAttrs(aAccOrElmOrID, a
     };
     testAttrs(aAccOrElmOrID, attrs, true);
   }
 
   if (aLevel) {
     is(levelObj.value, aLevel,
        "Wrong group level for " + prettyName(aAccOrElmOrID));
 
-    var attrs = { "level" : String(aLevel) };
+    var attrs = { "level": String(aLevel) };
     testAttrs(aAccOrElmOrID, attrs, true);
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Text attributes.
 
 /**
@@ -154,17 +154,17 @@ function testTextAttrs(aID, aOffset, aAt
 
   for (var name in aDefAttrs) {
     if (!(name in expectedAttrs))
       expectedAttrs[name] = aDefAttrs[name];
   }
 
   attrs = getTextAttributes(aID, accessible, true, aOffset,
                             startOffset, endOffset);
-  
+
   if (!attrs)
     return;
 
   compareAttrs(errorMsg, attrs, expectedAttrs, aSkipUnexpectedAttrs);
 }
 
 /**
  * Test default text attributes.
@@ -176,28 +176,28 @@ function testTextAttrs(aID, aOffset, aAt
  * @param aSkipUnexpectedAttrs  [in] points the function doesn't fail if
  *                              unexpected attribute is encountered
  */
 function testDefaultTextAttrs(aID, aDefAttrs, aSkipUnexpectedAttrs)
 {
   var accessible = getAccessible(aID, [nsIAccessibleText]);
   if (!accessible)
     return;
-  
+
   var defAttrs = null;
-  try{
+  try {
     defAttrs = accessible.defaultTextAttributes;
   } catch (e) {
   }
-  
+
   if (!defAttrs) {
     ok(false, "Can't get default text attributes for " + aID);
     return;
   }
-  
+
   var errorMsg = ". Getting default text attributes for " + aID;
   compareAttrs(errorMsg, defAttrs, aDefAttrs, aSkipUnexpectedAttrs);
 }
 
 /**
  * Test text attributes for wrong offset.
  */
 function testTextAttrsWrongOffset(aID, aOffset)
@@ -313,22 +313,22 @@ function testAttrsInternal(aAccOrElmOrID
   var accessible = getAccessible(aAccOrElmOrID);
   if (!accessible)
     return;
 
   var attrs = null;
   try {
     attrs = accessible.attributes;
   } catch (e) { }
-  
+
   if (!attrs) {
     ok(false, "Can't get object attributes for " + prettyName(aAccOrElmOrID));
     return;
   }
-  
+
   var errorMsg = " for " + prettyName(aAccOrElmOrID);
   compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs, aAbsentAttrs);
 }
 
 function compareAttrs(aErrorMsg, aAttrs, aExpectedAttrs, aSkipUnexpectedAttrs,
                       aAbsentAttrs)
 {
   // Check if all obtained attributes are expected and have expected value.
@@ -351,17 +351,17 @@ function compareAttrs(aErrorMsg, aAttrs,
     }
   }
 
   // Check if all expected attributes are presented.
   for (var name in aExpectedAttrs) {
     var value = "";
     try {
       value = aAttrs.getStringProperty(name);
-    } catch(e) { }
+    } catch (e) { }
 
     if (!value)
       ok(false,
          "There is no expected attribute '" + name + "' " + aErrorMsg);
   }
 
   // Check if all unexpected attributes are absent.
   if (aAbsentAttrs) {
--- a/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html
+++ b/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html
@@ -57,17 +57,17 @@
         'doc-prologue',
         'doc-pullquote',
         'doc-qna',
         'doc-subtitle',
         'doc-tip',
         'doc-toc'
       ];
       for (let attr of dpub_attrs) {
-        testAttrs(attr, {"xml-roles" : attr}, true);
+        testAttrs(attr, {"xml-roles": attr}, true);
       }
       SimpleTest.finish();
     }
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
--- a/accessible/tests/mochitest/attributes/test_obj.html
+++ b/accessible/tests/mochitest/attributes/test_obj.html
@@ -17,115 +17,115 @@ https://bugzilla.mozilla.org/show_bug.cg
           src="../common.js"></script>
   <script type="application/javascript"
           src="../attributes.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
       // aria
-      testAttrs("atomic", {"atomic" : "true", "container-atomic" : "true"}, true);
-      testAttrs(getNode("atomic").firstChild, {"container-atomic" : "true"}, true);
-      testAbsentAttrs("atomic_false", {"atomic" : "false", "container-atomic" : "false"});
-      testAbsentAttrs(getNode("atomic_false").firstChild, {"container-atomic" : "false"});
+      testAttrs("atomic", {"atomic": "true", "container-atomic": "true"}, true);
+      testAttrs(getNode("atomic").firstChild, {"container-atomic": "true"}, true);
+      testAbsentAttrs("atomic_false", {"atomic": "false", "container-atomic": "false"});
+      testAbsentAttrs(getNode("atomic_false").firstChild, {"container-atomic": "false"});
 
-      testAttrs("autocomplete", {"autocomplete" : "true"}, true);
-      testAttrs("checkbox", {"checkable" : "true"}, true);
-      testAttrs("checkedCheckbox", {"checkable" : "true"}, true);
-      testAbsentAttrs("checkedMenuitem", {"checkable" : "true"}, true);
-      testAttrs("checkedMenuitemCheckbox", {"checkable" : "true"}, true);
-      testAttrs("checkedMenuitemRadio", {"checkable" : "true"}, true);
-      testAttrs("checkedOption", {"checkable" : "true"}, true);
-      testAttrs("checkedRadio", {"checkable" : "true"}, true);
-      testAttrs("checkedTreeitem", {"checkable" : "true"}, true);
-      testAttrs("dropeffect", {"dropeffect" : "copy"}, true);
-      testAttrs("grabbed", {"grabbed" : "true"}, true);
+      testAttrs("autocomplete", {"autocomplete": "true"}, true);
+      testAttrs("checkbox", {"checkable": "true"}, true);
+      testAttrs("checkedCheckbox", {"checkable": "true"}, true);
+      testAbsentAttrs("checkedMenuitem", {"checkable": "true"}, true);
+      testAttrs("checkedMenuitemCheckbox", {"checkable": "true"}, true);
+      testAttrs("checkedMenuitemRadio", {"checkable": "true"}, true);
+      testAttrs("checkedOption", {"checkable": "true"}, true);
+      testAttrs("checkedRadio", {"checkable": "true"}, true);
+      testAttrs("checkedTreeitem", {"checkable": "true"}, true);
+      testAttrs("dropeffect", {"dropeffect": "copy"}, true);
+      testAttrs("grabbed", {"grabbed": "true"}, true);
       testAttrs("haspopupTrue", { "haspopup": "true" }, true);
       testAbsentAttrs("haspopupFalse", { "haspopup": "false" });
       testAbsentAttrs("haspopupEmpty", { "haspopup": "" });
-      testAttrs("hidden", {"hidden" : "true"}, true);
+      testAttrs("hidden", {"hidden": "true"}, true);
       testAbsentAttrs("hidden_false", { "hidden": "false" });
-      testAbsentAttrs("modal", {"modal" : "true"});
-      testAttrs("sortAscending", {"sort" : "ascending"}, true);
-      testAttrs("sortDescending", {"sort" : "descending"}, true);
-      testAttrs("sortNone", {"sort" : "none"}, true);
-      testAttrs("sortOther", {"sort" : "other"}, true);
-      testAttrs("roledescr", {"roledescription" : "spreadshit"}, true);
-      testAttrs("currentPage", {"current" : "page"}, true);
+      testAbsentAttrs("modal", {"modal": "true"});
+      testAttrs("sortAscending", {"sort": "ascending"}, true);
+      testAttrs("sortDescending", {"sort": "descending"}, true);
+      testAttrs("sortNone", {"sort": "none"}, true);
+      testAttrs("sortOther", {"sort": "other"}, true);
+      testAttrs("roledescr", {"roledescription": "spreadshit"}, true);
+      testAttrs("currentPage", {"current": "page"}, true);
 
       // inherited attributes by subdocuments
       var subdoc = getAccessible("iframe").firstChild;
-      testAttrs(subdoc, {"busy" : "true"}, true);
+      testAttrs(subdoc, {"busy": "true"}, true);
 
       // live object attribute
 
       // HTML
-      testAttrs("output", {"live" : "polite"}, true);
+      testAttrs("output", {"live": "polite"}, true);
 
       // ARIA
-      testAttrs("live", {"live" : "polite"}, true);
-      testAttrs("live2", {"live" : "polite"}, true);
-      testAbsentAttrs("live3", {"live" : ""});
-      testAttrs("log", {"live" : "polite"}, true);
-      testAttrs("logAssertive", {"live" : "assertive"}, true);
-      testAttrs("marquee", {"live" : "off"}, true);
-      testAttrs("status", {"live" : "polite"}, true);
-      testAttrs("timer", {"live" : "off"}, true);
-      testAbsentAttrs("tablist", {"live" : "polite"});
+      testAttrs("live", {"live": "polite"}, true);
+      testAttrs("live2", {"live": "polite"}, true);
+      testAbsentAttrs("live3", {"live": ""});
+      testAttrs("log", {"live": "polite"}, true);
+      testAttrs("logAssertive", {"live": "assertive"}, true);
+      testAttrs("marquee", {"live": "off"}, true);
+      testAttrs("status", {"live": "polite"}, true);
+      testAttrs("timer", {"live": "off"}, true);
+      testAbsentAttrs("tablist", {"live": "polite"});
 
       // container-live object attribute
-      testAttrs("liveChild", {"container-live" : "polite"}, true);
-      testAttrs("live2Child", {"container-live" : "polite"}, true);
-      testAttrs("logChild", {"container-live" : "polite"}, true);
-      testAttrs("logAssertiveChild", {"container-live" : "assertive"}, true);
-      testAttrs("marqueeChild", {"container-live" : "off"}, true);
-      testAttrs("statusChild", {"container-live" : "polite"}, true);
-      testAttrs("timerChild", {"container-live" : "off"}, true);
-      testAbsentAttrs("tablistChild", {"container-live" : "polite"});
+      testAttrs("liveChild", {"container-live": "polite"}, true);
+      testAttrs("live2Child", {"container-live": "polite"}, true);
+      testAttrs("logChild", {"container-live": "polite"}, true);
+      testAttrs("logAssertiveChild", {"container-live": "assertive"}, true);
+      testAttrs("marqueeChild", {"container-live": "off"}, true);
+      testAttrs("statusChild", {"container-live": "polite"}, true);
+      testAttrs("timerChild", {"container-live": "off"}, true);
+      testAbsentAttrs("tablistChild", {"container-live": "polite"});
 
       // container-live-role object attribute
-      testAttrs("log", {"container-live-role" : "log"}, true);
-      testAttrs("logAssertive", {"container-live-role" : "log"}, true);
-      testAttrs("marquee", {"container-live-role" : "marquee"}, true);
-      testAttrs("status", {"container-live-role" : "status"}, true);
-      testAttrs("timer", {"container-live-role" : "timer"}, true);
-      testAttrs("logChild", {"container-live-role" : "log"}, true);
-      testAttrs("logAssertive", {"container-live-role" : "log"}, true);
-      testAttrs("logAssertiveChild", {"container-live-role" : "log"}, true);
-      testAttrs("marqueeChild", {"container-live-role" : "marquee"}, true);
-      testAttrs("statusChild", {"container-live-role" : "status"}, true);
-      testAttrs("timerChild", {"container-live-role" : "timer"}, true);
-      testAbsentAttrs("tablistChild", {"container-live-role" : "tablist"});
+      testAttrs("log", {"container-live-role": "log"}, true);
+      testAttrs("logAssertive", {"container-live-role": "log"}, true);
+      testAttrs("marquee", {"container-live-role": "marquee"}, true);
+      testAttrs("status", {"container-live-role": "status"}, true);
+      testAttrs("timer", {"container-live-role": "timer"}, true);
+      testAttrs("logChild", {"container-live-role": "log"}, true);
+      testAttrs("logAssertive", {"container-live-role": "log"}, true);
+      testAttrs("logAssertiveChild", {"container-live-role": "log"}, true);
+      testAttrs("marqueeChild", {"container-live-role": "marquee"}, true);
+      testAttrs("statusChild", {"container-live-role": "status"}, true);
+      testAttrs("timerChild", {"container-live-role": "timer"}, true);
+      testAbsentAttrs("tablistChild", {"container-live-role": "tablist"});
 
       // absent aria-label and aria-labelledby object attribute
-      testAbsentAttrs("label", {"label" : "foo"});
-      testAbsentAttrs("labelledby", {"labelledby" : "label"});
+      testAbsentAttrs("label", {"label": "foo"});
+      testAbsentAttrs("labelledby", {"labelledby": "label"});
 
       // container that has no default live attribute
-      testAttrs("liveGroup", {"live" : "polite"}, true);
-      testAttrs("liveGroupChild", {"container-live" : "polite"}, true);
-      testAttrs("liveGroup", {"container-live-role" : "group"}, true);
-      testAttrs("liveGroupChild", {"container-live-role" : "group"}, true);
+      testAttrs("liveGroup", {"live": "polite"}, true);
+      testAttrs("liveGroupChild", {"container-live": "polite"}, true);
+      testAttrs("liveGroup", {"container-live-role": "group"}, true);
+      testAttrs("liveGroupChild", {"container-live-role": "group"}, true);
 
       // text input type
       testAbsentAttrs("button", { "text-input-type": "button"});
       testAbsentAttrs("checkbox", { "text-input-type": "checkbox"});
       testAbsentAttrs("radio", { "text-input-type": "radio"});
-      testAttrs("email", {"text-input-type" : "email"}, true);
-      testAttrs("search", {"text-input-type" : "search"}, true);
-      testAttrs("tel", {"text-input-type" : "tel"}, true);
-      testAttrs("url", {"text-input-type" : "url"}, true);
+      testAttrs("email", {"text-input-type": "email"}, true);
+      testAttrs("search", {"text-input-type": "search"}, true);
+      testAttrs("tel", {"text-input-type": "tel"}, true);
+      testAttrs("url", {"text-input-type": "url"}, true);
 
       // ARIA
-      testAttrs("searchbox", {"text-input-type" : "search"}, true);
+      testAttrs("searchbox", {"text-input-type": "search"}, true);
 
       // html
-      testAttrs("radio", {"checkable" : "true"}, true); 
-      testAttrs("checkbox", {"checkable" : "true"}, true); 
-      testAttrs("draggable", {"draggable" : "true"}, true);
+      testAttrs("radio", {"checkable": "true"}, true);
+      testAttrs("checkbox", {"checkable": "true"}, true);
+      testAttrs("draggable", {"draggable": "true"}, true);
       testAttrs("th1", { "abbr": "SS#" }, true);
       testAttrs("th2", { "abbr": "SS#" }, true);
       testAttrs("th2", { "axis": "social" }, true);
 
       // don't barf on an empty abbr element.
       testAbsentAttrs("th3", { "abbr": "" }, true);
 
       // application accessible
@@ -137,17 +137,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         }
         testAttrs(getApplicationAccessible(), attrs, false);
       }
 
       // no object attributes
       testAbsentAttrs(getAccessible("listitem").firstChild, { "tag": "" });
 
       // experimental aria
-      testAttrs("experimental", {"blah" : "true"}, true);
+      testAttrs("experimental", {"blah": "true"}, true);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
--- a/accessible/tests/mochitest/attributes/test_obj_group.html
+++ b/accessible/tests/mochitest/attributes/test_obj_group.html
@@ -144,36 +144,36 @@
       testGroupAttrs("tree3_ti1b", 2, 2, 2);
       testGroupAttrs("tree3_ti2", 2, 2, 1);
       testGroupAttrs("tree3_ti2a", 1, 2, 2);
       testGroupAttrs("tree3_ti2b", 2, 2, 2);
 
       //////////////////////////////////////////////////////////////////////////
       // ARIA grid
       testGroupAttrs("grid_row1", 1, 2);
-      testAbsentAttrs("grid_cell1", {"posinset":"", "setsize":""});
-      testAbsentAttrs("grid_cell2", {"posinset":"", "setsize":""});
+      testAbsentAttrs("grid_cell1", {"posinset": "", "setsize": ""});
+      testAbsentAttrs("grid_cell2", {"posinset": "", "setsize": ""});
 
       testGroupAttrs("grid_row2", 2, 2);
-      testAbsentAttrs("grid_cell3", {"posinset":"", "setsize":""});
-      testAbsentAttrs("grid_cell4", {"posinset":"", "setsize":""});
+      testAbsentAttrs("grid_cell3", {"posinset": "", "setsize": ""});
+      testAbsentAttrs("grid_cell4", {"posinset": "", "setsize": ""});
 
       //////////////////////////////////////////////////////////////////////////
       // ARIA treegrid
       testGroupAttrs("treegrid_row1", 1, 2, 1);
-      testAbsentAttrs("treegrid_cell1", {"posinset":"", "setsize":""});
-      testAbsentAttrs("treegrid_cell2", {"posinset":"", "setsize":""});
+      testAbsentAttrs("treegrid_cell1", {"posinset": "", "setsize": ""});
+      testAbsentAttrs("treegrid_cell2", {"posinset": "", "setsize": ""});
 
       testGroupAttrs("treegrid_row2", 1, 1, 2);
-      testAbsentAttrs("treegrid_cell3", {"posinset":"", "setsize":""});
-      testAbsentAttrs("treegrid_cell4", {"posinset":"", "setsize":""});
+      testAbsentAttrs("treegrid_cell3", {"posinset": "", "setsize": ""});
+      testAbsentAttrs("treegrid_cell4", {"posinset": "", "setsize": ""});
 
       testGroupAttrs("treegrid_row3", 2, 2, 1);
-      testAbsentAttrs("treegrid_cell5", {"posinset":"", "setsize":""});
-      testAbsentAttrs("treegrid_cell6", {"posinset":"", "setsize":""});
+      testAbsentAttrs("treegrid_cell5", {"posinset": "", "setsize": ""});
+      testAbsentAttrs("treegrid_cell6", {"posinset": "", "setsize": ""});
 
       //////////////////////////////////////////////////////////////////////////
       // HTML headings
       testGroupAttrs("h1", 0, 0, 1);
       testGroupAttrs("h2", 0, 0, 2);
       testGroupAttrs("h3", 0, 0, 3);
       testGroupAttrs("h4", 0, 0, 4);
       testGroupAttrs("h5", 0, 0, 5);
--- a/accessible/tests/mochitest/attributes/test_tag.html
+++ b/accessible/tests/mochitest/attributes/test_tag.html
@@ -15,24 +15,24 @@
   <script type="application/javascript"
           src="../attributes.js"></script>
 
   <script type="application/javascript">
 
     function doTest()
     {
       // And some AT may look for this
-      testAttrs("nav", {"tag" : "nav"}, true);
-      testAttrs("header", {"tag" : "header"}, true);
-      testAttrs("footer", {"tag" : "footer"}, true);
-      testAttrs("article", {"tag" : "article"}, true);
-      testAttrs("aside", {"tag" : "aside"}, true);
-      testAttrs("section", {"tag" : "section"}, true);
-      testAttrs("main", {"tag" : "article"}, true);
-      testAttrs("form", {"tag" : "article"}, true);
+      testAttrs("nav", {"tag": "nav"}, true);
+      testAttrs("header", {"tag": "header"}, true);
+      testAttrs("footer", {"tag": "footer"}, true);
+      testAttrs("article", {"tag": "article"}, true);
+      testAttrs("aside", {"tag": "aside"}, true);
+      testAttrs("section", {"tag": "section"}, true);
+      testAttrs("main", {"tag": "article"}, true);
+      testAttrs("form", {"tag": "article"}, true);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
--- a/accessible/tests/mochitest/attributes/test_xml-roles.html
+++ b/accessible/tests/mochitest/attributes/test_xml-roles.html
@@ -15,86 +15,87 @@
   <script type="application/javascript"
           src="../attributes.js"></script>
 
   <script type="application/javascript">
 
     function doTest()
     {
       // Some AT may look for this
-      testAttrs("nav", {"xml-roles" : "navigation"}, true);
-      testAttrs("header", {"xml-roles" : "banner"}, true);
-      testAbsentAttrs("article_header", {"xml-roles" : "banner"});
-      testAbsentAttrs("section_header", {"xml-roles" : "banner"});
-      testAttrs("footer", {"xml-roles" : "contentinfo"}, true);
-      testAbsentAttrs("article_footer", {"xml-roles" : "contentinfo"});
-      testAbsentAttrs("section_footer", {"xml-roles" : "contentinfo"});
-      testAttrs("aside", {"xml-roles" : "complementary"}, true);
-      testAttrs("section", {"xml-roles" : "region"}, true);
-      testAttrs("main", {"xml-roles" : "main"}, true); // // ARIA override
-      testAttrs("form", {"xml-roles" : "form"}, true);
-      testAttrs("feed", {"xml-roles" : "feed"}, true);
-      testAttrs("article", {"xml-roles" : "article"}, true);
-      testAttrs("main_element", {"xml-roles" : "main"}, true);
+      testAttrs("nav", {"xml-roles": "navigation"}, true);
+      testAttrs("header", {"xml-roles": "banner"}, true);
+      testAbsentAttrs("article_header", {"xml-roles": "banner"});
+      testAbsentAttrs("section_header", {"xml-roles": "banner"});
+      testAttrs("footer", {"xml-roles": "contentinfo"}, true);
+      testAbsentAttrs("article_footer", {"xml-roles": "contentinfo"});
+      testAbsentAttrs("section_footer", {"xml-roles": "contentinfo"});
+      testAttrs("aside", {"xml-roles": "complementary"}, true);
+      testAttrs("section", {"xml-roles": "region"}, true);
+      testAttrs("main", {"xml-roles": "main"}, true); // // ARIA override
+      testAttrs("form", {"xml-roles": "form"}, true);
+      testAttrs("feed", {"xml-roles": "feed"}, true);
+      testAttrs("article", {"xml-roles": "article"}, true);
+      testAttrs("main_element", {"xml-roles": "main"}, true);
+      testAttrs("figure", {"xml-roles": "figure"}, true);
 
-      testAttrs("search", {"xml-roles" : "searchbox"}, true);
+      testAttrs("search", {"xml-roles": "searchbox"}, true);
 
-      testAttrs("open-1", {"xml-roles" : "open-fence"}, true);
-      testAttrs("open-2", {"xml-roles" : "open-fence"}, true);
-      testAttrs("open-3", {"xml-roles" : "open-fence"}, true);
-      testAttrs("open-4", {"xml-roles" : "open-fence"}, true);
-      testAttrs("open-5", {"xml-roles" : "open-fence"}, true);
-      testAttrs("open-6", {"xml-roles" : "open-fence"}, true);
-      testAttrs("open-7", {"xml-roles" : "open-fence"}, true);
+      testAttrs("open-1", {"xml-roles": "open-fence"}, true);
+      testAttrs("open-2", {"xml-roles": "open-fence"}, true);
+      testAttrs("open-3", {"xml-roles": "open-fence"}, true);
+      testAttrs("open-4", {"xml-roles": "open-fence"}, true);
+      testAttrs("open-5", {"xml-roles": "open-fence"}, true);
+      testAttrs("open-6", {"xml-roles": "open-fence"}, true);
+      testAttrs("open-7", {"xml-roles": "open-fence"}, true);
 
-      testAttrs("sep-1", {"xml-roles" : "separator"}, true);
-      testAttrs("sep-2", {"xml-roles" : "separator"}, true);
-      testAttrs("sep-3", {"xml-roles" : "separator"}, true);
-      testAttrs("sep-4", {"xml-roles" : "separator"}, true);
-      testAttrs("sep-5", {"xml-roles" : "separator"}, true);
-      testAttrs("sep-6", {"xml-roles" : "separator"}, true);
-      testAttrs("sep-7", {"xml-roles" : "separator"}, true);
+      testAttrs("sep-1", {"xml-roles": "separator"}, true);
+      testAttrs("sep-2", {"xml-roles": "separator"}, true);
+      testAttrs("sep-3", {"xml-roles": "separator"}, true);
+      testAttrs("sep-4", {"xml-roles": "separator"}, true);
+      testAttrs("sep-5", {"xml-roles": "separator"}, true);
+      testAttrs("sep-6", {"xml-roles": "separator"}, true);
+      testAttrs("sep-7", {"xml-roles": "separator"}, true);
 
-      testAttrs("close-1", {"xml-roles" : "close-fence"}, true);
-      testAttrs("close-2", {"xml-roles" : "close-fence"}, true);
-      testAttrs("close-3", {"xml-roles" : "close-fence"}, true);
-      testAttrs("close-4", {"xml-roles" : "close-fence"}, true);
-      testAttrs("close-5", {"xml-roles" : "close-fence"}, true);
-      testAttrs("close-6", {"xml-roles" : "close-fence"}, true);
-      testAttrs("close-7", {"xml-roles" : "close-fence"}, true);
+      testAttrs("close-1", {"xml-roles": "close-fence"}, true);
+      testAttrs("close-2", {"xml-roles": "close-fence"}, true);
+      testAttrs("close-3", {"xml-roles": "close-fence"}, true);
+      testAttrs("close-4", {"xml-roles": "close-fence"}, true);
+      testAttrs("close-5", {"xml-roles": "close-fence"}, true);
+      testAttrs("close-6", {"xml-roles": "close-fence"}, true);
+      testAttrs("close-7", {"xml-roles": "close-fence"}, true);
 
-      testAttrs("num", {"xml-roles" : "numerator"}, true);
-      testAttrs("den", {"xml-roles" : "denominator"}, true);
+      testAttrs("num", {"xml-roles": "numerator"}, true);
+      testAttrs("den", {"xml-roles": "denominator"}, true);
 
-      testAttrs("sub-1", {"xml-roles" : "subscript"}, true);
-      testAttrs("sub-2", {"xml-roles" : "subscript"}, true);
-      testAttrs("sub-3", {"xml-roles" : "subscript"}, true);
-      testAttrs("sup-1", {"xml-roles" : "superscript"}, true);
-      testAttrs("sup-2", {"xml-roles" : "superscript"}, true);
-      testAttrs("sup-3", {"xml-roles" : "superscript"}, true);
-      testAttrs("sup-4", {"xml-roles" : "superscript"}, true);
-      testAttrs("presub-1", {"xml-roles" : "presubscript"}, true);
-      testAttrs("presub-2", {"xml-roles" : "presubscript"}, true);
-      testAttrs("presup-1", {"xml-roles" : "presuperscript"}, true);
+      testAttrs("sub-1", {"xml-roles": "subscript"}, true);
+      testAttrs("sub-2", {"xml-roles": "subscript"}, true);
+      testAttrs("sub-3", {"xml-roles": "subscript"}, true);
+      testAttrs("sup-1", {"xml-roles": "superscript"}, true);
+      testAttrs("sup-2", {"xml-roles": "superscript"}, true);
+      testAttrs("sup-3", {"xml-roles": "superscript"}, true);
+      testAttrs("sup-4", {"xml-roles": "superscript"}, true);
+      testAttrs("presub-1", {"xml-roles": "presubscript"}, true);
+      testAttrs("presub-2", {"xml-roles": "presubscript"}, true);
+      testAttrs("presup-1", {"xml-roles": "presuperscript"}, true);
 
-      testAttrs("under-1", {"xml-roles" : "underscript"}, true);
-      testAttrs("under-2", {"xml-roles" : "underscript"}, true);
-      testAttrs("over-1", {"xml-roles" : "overscript"}, true);
-      testAttrs("over-2", {"xml-roles" : "overscript"}, true);
+      testAttrs("under-1", {"xml-roles": "underscript"}, true);
+      testAttrs("under-2", {"xml-roles": "underscript"}, true);
+      testAttrs("over-1", {"xml-roles": "overscript"}, true);
+      testAttrs("over-2", {"xml-roles": "overscript"}, true);
 
-      testAttrs("root-index-1", {"xml-roles" : "root-index"}, true);
+      testAttrs("root-index-1", {"xml-roles": "root-index"}, true);
 
-      testAttrs("base-1", {"xml-roles" : "base"}, true);
-      testAttrs("base-2", {"xml-roles" : "base"}, true);
-      testAttrs("base-3", {"xml-roles" : "base"}, true);
-      testAttrs("base-4", {"xml-roles" : "base"}, true);
-      testAttrs("base-5", {"xml-roles" : "base"}, true);
-      testAttrs("base-6", {"xml-roles" : "base"}, true);
-      testAttrs("base-7", {"xml-roles" : "base"}, true);
-      testAttrs("base-8", {"xml-roles" : "base"}, true);
+      testAttrs("base-1", {"xml-roles": "base"}, true);
+      testAttrs("base-2", {"xml-roles": "base"}, true);
+      testAttrs("base-3", {"xml-roles": "base"}, true);
+      testAttrs("base-4", {"xml-roles": "base"}, true);
+      testAttrs("base-5", {"xml-roles": "base"}, true);
+      testAttrs("base-6", {"xml-roles": "base"}, true);
+      testAttrs("base-7", {"xml-roles": "base"}, true);
+      testAttrs("base-8", {"xml-roles": "base"}, true);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
@@ -135,16 +136,22 @@
      title="modify HTML5 header and footer accessibility API mapping">
     Bug 849624
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
      title="ARIA 1.1: Support role 'searchbox'">
     Bug 1121518
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1356049"
+     title="Map ARIA figure role">
+    Bug 1356049
+  </a>
+
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <nav id="nav">a nav</nav>
   <header id="header">a header</header>
   <footer id="footer">a footer</footer>
@@ -158,16 +165,17 @@
   </section>
   <aside id="aside">by the way I am an aside</aside>
   <section id="section">a section</section>
   <article id="main" role="main">a main area</article>
   <article id="form" role="form">a form area</article>
   <div id="feed" role="feed">a feed</div>
   <article id="article">article</article>
   <main id="main_element">another main area</main>
+  <div id="figure" role="figure">a figure</div>
 
   <input id="search" type="search"/>
 
   <!-- open-fence, separator, close-fence -->
   <math><mo id="open-1">(</mo><mi>x</mi><mo id="sep-1">,</mo><mi>y</mi><mo id="close-1">)</mo></math>
   <math><mrow><mo id="open-2">(</mo><mi>x</mi><mo id="sep-2">,</mo><mi>y</mi><mo id="close-2">)</mo></mrow></math>
   <math><mstyle><mo id="open-3">(</mo><mi>x</mi><mo id="sep-3">,</mo><mi>y</mi><mo id="close-3">)</mo></mstyle></math>
   <math><msqrt><mo id="open-4">(</mo><mi>x</mi><mo id="sep-4">,</mo><mi>y</mi><mo id="close-4">)</mo></msqrt></math>
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -248,17 +248,17 @@ const DONOTFAIL_IF_NO_INTERFACE = 2;
 function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf)
 {
   if (!aAccOrElmOrID)
     return null;
 
   var elm = null;
 
   if (aAccOrElmOrID instanceof nsIAccessible) {
-    try { elm = aAccOrElmOrID.DOMNode; } catch(e) { }
+    try { elm = aAccOrElmOrID.DOMNode; } catch (e) { }
 
   } else if (aAccOrElmOrID instanceof nsIDOMNode) {
     elm = aAccOrElmOrID;
 
   } else {
     elm = document.getElementById(aAccOrElmOrID);
     if (!elm) {
       ok(false, "Can't get DOM element for " + aAccOrElmOrID);
@@ -545,17 +545,17 @@ function testAccessibleTree(aAccOrElmOrI
 
         // nsIAccessible::parent
         var parent = null;
         try { parent = child.parent; } catch (e) {}
         is(parent, acc, "Wrong parent of " + prettyName(child));
 
         // nsIAccessible::indexInParent
         var indexInParent = -1;
-        try { indexInParent = child.indexInParent; } catch(e) {}
+        try { indexInParent = child.indexInParent; } catch (e) {}
         is(indexInParent, i,
            "Wrong index in parent of " + prettyName(child));
 
         // nsIAccessible::nextSibling
         var expectedNextSibling = (i < childCount - 1) ?
           children.queryElementAt(i + 1, nsIAccessible) : null;
         var nextSibling = null;
         try { nextSibling = child.nextSibling; } catch (e) {}
--- a/accessible/tests/mochitest/elm/test_HTMLSpec.html
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -301,18 +301,18 @@
         ]
       };
       testElm("code_container", obj);
 
       //////////////////////////////////////////////////////////////////////////
       // HTML:col and HTML:colgroup under table
 
       obj =
-        { TABLE : [
-          { ROW :[
+        { TABLE: [
+          { ROW: [
             { role: ROLE_CELL },
             { role: ROLE_CELL },
             { role: ROLE_CELL }
           ] }
         ] };
       testElm("colNcolgroup_table", obj);
 
       //////////////////////////////////////////////////////////////////////////
@@ -854,17 +854,17 @@
       //////////////////////////////////////////////////////////////////////////
       // HTML:keygen
 
       obj = {
         role: ROLE_COMBOBOX,
         states: STATE_COLLAPSED | STATE_HASPOPUP,
         extraStates: EXT_STATE_EXPANDABLE,
         actions: "open",
-        children: [ 
+        children: [
           { COMBOBOX_LIST: [
             { role: ROLE_COMBOBOX_OPTION }, // high grade
             { role: ROLE_COMBOBOX_OPTION } // medium grade
           ] }
         ]
       };
       testElm("keygen", obj);
 
@@ -1267,17 +1267,17 @@
 
       //////////////////////////////////////////////////////////////////////////
       // HTML:u contained by paragraph
 
       obj = {
         role: ROLE_PARAGRAPH,
         textAttrs: {
           0: { },
-          6: { "text-underline-style" : "solid" }
+          6: { "text-underline-style": "solid" }
         },
         children: [
           { role: ROLE_TEXT_LEAF }, // plain text
           { role: ROLE_TEXT_LEAF } // HTML:u text
         ]
       };
       testElm("u_container", obj);
 
--- a/accessible/tests/mochitest/elm/test_figure.html
+++ b/accessible/tests/mochitest/elm/test_figure.html
@@ -28,17 +28,17 @@
 
       todo(false, "figure name gets extra whitespace in the end!");
       testName("figure", "figure caption ");
       testName("figcaption", null);
 
       testRelation("figure", RELATION_LABELLED_BY, "figcaption");
       testRelation("figcaption", RELATION_LABEL_FOR, "figure");
 
-      testAttrs("figure", {"xml-roles" : "figure"}, true);
+      testAttrs("figure", {"xml-roles": "figure"}, true);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
--- a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html
+++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html
@@ -32,23 +32,23 @@
 
       // nsIAccessible::name
       var applicationName = "";
       if (LINUX || SOLARIS) {
         applicationName = appInfo.name;
       } else {
         try {
           applicationName = brandBundle.GetStringFromName("brandShortName");
-        } catch(e) {
+        } catch (e) {
         }
 
         if (applicationName == "")
           applicationName = "Gecko based application";
       }
-      is (accessible.name, applicationName, "wrong application accessible name");
+      is(accessible.name, applicationName, "wrong application accessible name");
 
       // nsIAccessibleApplication
       is(accessible.appName, appInfo.name, "Wrong application name");
       is(accessible.appVersion, appInfo.version, "Wrong application version");
       is(accessible.platformName, "Gecko", "Wrong platform name");
       is(accessible.platformVersion, appInfo.platformVersion,
          "Wrong platform version");
 
--- a/accessible/tests/mochitest/events/test_aria_objattr.html
+++ b/accessible/tests/mochitest/events/test_aria_objattr.html
@@ -45,18 +45,18 @@
     function updateARIAHidden(aID, aIsDefined, aChildId)
     {
       this.__proto__ = new updateAttribute(aID, "aria-hidden",
                                            aIsDefined ? "true" : "false");
 
       this.finalCheck = function updateARIAHidden()
       {
         if (aIsDefined) {
-          testAttrs(aID, {"hidden" : "true"}, true);
-          testAttrs(aChildId, {"hidden" : "true"}, true);
+          testAttrs(aID, {"hidden": "true"}, true);
+          testAttrs(aChildId, {"hidden": "true"}, true);
         } else {
           testAbsentAttrs(aID, { "hidden": "true"});
           testAbsentAttrs(aChildId, { "hidden": "true"});
         }
       }
     }
 
     // Debug stuff.
--- a/accessible/tests/mochitest/events/test_aria_statechange.html
+++ b/accessible/tests/mochitest/events/test_aria_statechange.html
@@ -63,16 +63,35 @@
       };
 
       this.getID = function busyify_getID()
       {
         return prettyName(aID) + " aria-busy changed to '" + aIsBusy + "'";
       };
     }
 
+    function makeCurrent(aID, aIsCurrent, aValue)
+    {
+      this.DOMNode = getNode(aID);
+
+      this.eventSeq = [
+        new stateChangeChecker(EXT_STATE_CURRENT, true, aIsCurrent, this.DOMNode)
+      ];
+
+      this.invoke = function makeCurrent_invoke()
+      {
+        this.DOMNode.setAttribute("aria-current", aValue);
+      };
+
+      this.getID = function makeCurrent_getID()
+      {
+        return prettyName(aID) + " aria-current changed to " + aValue;
+      };
+    }
+
     function setAttrOfMixedType(aID, aAttr, aState, aValue)
     {
       this.DOMNode = getNode(aID);
 
       this.eventSeq = [
         new stateChangeChecker(aState, kOrdinalState,
                                aValue == "true", this.DOMNode)
       ];
@@ -143,16 +162,22 @@
       gQueue.push(new busyify("aria_doc", true));
       gQueue.push(new busyify("aria_doc", false));
 
       buildQueueForAttrOfMixedType(gQueue, "pressable", setPressed);
       buildQueueForAttrOfMixedType(gQueue, "pressable_native", setPressed);
       buildQueueForAttrOfMixedType(gQueue, "checkable", setChecked);
       buildQueueForAttrOfBoolType(gQueue, "checkableBool", setChecked);
 
+      gQueue.push(new makeCurrent("current_page_1", false, "false"));
+      gQueue.push(new makeCurrent("current_page_2", true, "page"));
+      gQueue.push(new makeCurrent("current_page_2", false, "false"));
+      gQueue.push(new makeCurrent("current_page_3", true, "true"));
+      gQueue.push(new makeCurrent("current_page_3", false, ""));
+
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -178,16 +203,21 @@
      title="Pressed state is not exposed on a button element with aria-pressed attribute">
     Mozilla Bug 989958
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
      title="Support ARIA 1.1 switch role">
     Mozilla Bug 1136563
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1355921"
+     title="Elements with a defined, non-false value for aria-current should expose ATK_STATE_ACTIVE">
+    Mozilla Bug 1355921
+  </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
   <div id="eventdump"></div>
 
   <!-- aria-expanded -->
@@ -199,10 +229,15 @@
 
   <!-- aria-pressed -->
   <div id="pressable" role="button"></div>
   <button id="pressable_native"></button>
 
   <!-- aria-checked -->
   <div id="checkable" role="checkbox"></div>
   <div id="checkableBool" role="switch"></div>
+
+  <!-- aria-current -->
+  <div id="current_page_1" role="link" aria-current="page">1</div>
+  <div id="current_page_2" role="link" aria-current="false">2</div>
+  <div id="current_page_3" role="link">3</div>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_dragndrop.html
+++ b/accessible/tests/mochitest/events/test_dragndrop.html
@@ -31,17 +31,17 @@
 
       this.invoke = function changeGrabbed_invoke() {
         if (aGrabValue != undefined) {
           this.DOMNode.setAttribute("aria-grabbed", aGrabValue);
         }
       }
 
       this.check = function changeGrabbed_check() {
-        testAttrs(aNodeOrID, {"grabbed" : aGrabValue}, true);
+        testAttrs(aNodeOrID, {"grabbed": aGrabValue}, true);
       }
 
       this.getID = function changeGrabbed_getID() {
         return prettyName(aNodeOrID) + " aria-grabbed changed";
       }
     }
 
     // aria dropeffect invoker
@@ -51,17 +51,17 @@
 
       this.invoke = function changeDropeffect_invoke() {
         if (aDropeffectValue != undefined) {
           this.DOMNode.setAttribute("aria-dropeffect", aDropeffectValue);
         }
       }
 
       this.check = function changeDropeffect_check() {
-        testAttrs(aNodeOrID, {"dropeffect" : aDropeffectValue}, true);
+        testAttrs(aNodeOrID, {"dropeffect": aDropeffectValue}, true);
       }
 
       this.getID = function changeDropeffect_getID() {
         return prettyName(aNodeOrID) + " aria-dropeffect changed";
       }
     }
 
     function doTests()
--- a/accessible/tests/mochitest/events/test_textselchange.html
+++ b/accessible/tests/mochitest/events/test_textselchange.html
@@ -22,17 +22,17 @@
     var gQueue = null;
 
     // gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     function getOnclickSeq(aID)
     {
       return [
-        new caretMoveChecker(0, aID), 
+        new caretMoveChecker(0, aID),
         new unexpectedInvokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
       ];
     }
 
     function doTests()
     {
       // test caret move events and caret offsets
       gQueue = new eventQueue();
--- a/accessible/tests/mochitest/events/test_valuechange.html
+++ b/accessible/tests/mochitest/events/test_valuechange.html
@@ -35,28 +35,28 @@
         ];
 
       this.invoke = function changeARIAValue_invoke() {
 
         // Note: this should not fire an EVENT_VALUE_CHANGE when aria-valuetext
         // is not empty
         if (aValuenow != undefined)
           this.DOMNode.setAttribute("aria-valuenow", aValuenow);
- 
+
         // Note: this should always fire an EVENT_VALUE_CHANGE
         if (aValuetext != undefined)
           this.DOMNode.setAttribute("aria-valuetext", aValuetext);
       }
 
       this.check = function changeARIAValue_check() {
         var acc = getAccessible(aNodeOrID, [nsIAccessibleValue]);
         if (!acc)
           return;
 
-        // Note: always test against valuetext first because the existence of 
+        // Note: always test against valuetext first because the existence of
         // aria-valuetext takes precedence over aria-valuenow in gecko.
         is(acc.value, (aValuetext != undefined)? aValuetext : aValuenow,
             "Wrong value of " + prettyName(aNodeOrID));
       }
 
       this.getID = function changeARIAValue_getID() {
         return prettyName(aNodeOrID) + " value changed";
       }
--- a/accessible/tests/mochitest/grid.js
+++ b/accessible/tests/mochitest/grid.js
@@ -76,17 +76,17 @@ function grid(aTableIdentifier)
   }
 
   this.handleKeyEvent = function handleKeyEvent(aEvent)
   {
     if (aEvent.target.localName != "td")
       return;
 
     var cell = aEvent.target;
-    switch(aEvent.keyCode) {
+    switch (aEvent.keyCode) {
       case nsIDOMKeyEvent.DOM_VK_UP:
         var colsCount = this.getColsCount();
         var idx = this.getIndexByCell(cell);
         var upidx = idx - colsCount;
         if (upidx >= 0) {
           cell.removeAttribute("tabindex");
           var upcell = this.getCellAtIndex(upidx);
           upcell.setAttribute("tabindex", "0");
--- a/accessible/tests/mochitest/hittest/test_browser.html
+++ b/accessible/tests/mochitest/hittest/test_browser.html
@@ -25,27 +25,27 @@
       var tgtX = hitX + hitWidth / 2;
       var tgtY = hitY + hitHeight / 2;
 
       var rootAcc = getRootAccessible();
       var docAcc = getAccessible(document);
       var outerDocAcc = docAcc.parent;
 
       var hitAcc = rootAcc.getDeepestChildAtPoint(tgtX, tgtY);
-      is(hitAcc, hititem, "Hit match at " + tgtX + "," + tgtY + 
+      is(hitAcc, hititem, "Hit match at " + tgtX + "," + tgtY +
                           ". Found: " + prettyName(hitAcc));
       var hitAcc2 = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
-      is(hitAcc, hitAcc2, "Hit match at " + tgtX + "," + tgtY + 
+      is(hitAcc, hitAcc2, "Hit match at " + tgtX + "," + tgtY +
                           ". Found: " + prettyName(hitAcc2));
 
       hitAcc = outerDocAcc.getChildAtPoint(tgtX, tgtY);
-      is(hitAcc, docAcc, "Hit match at " + tgtX + "," + tgtY + 
+      is(hitAcc, docAcc, "Hit match at " + tgtX + "," + tgtY +
                          ". Found: " + prettyName(hitAcc));
       hitAcc = docAcc.getChildAtPoint(tgtX, tgtY);
-      is(hitAcc, hittest, "Hit match at " + tgtX + "," + tgtY + 
+      is(hitAcc, hittest, "Hit match at " + tgtX + "," + tgtY +
                           ". Found: " + prettyName(hitAcc));
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
--- a/accessible/tests/mochitest/hittest/test_shadowroot.html
+++ b/accessible/tests/mochitest/hittest/test_shadowroot.html
@@ -58,15 +58,16 @@
     // This routine adds the comment children of each 'component' to its
     // shadow root.
     var components = document.querySelectorAll('.components');
     for (var i = 0; i < components.length; i++) {
       var component = components[i];
       var shadow = component.createShadowRoot();
       for (var child = component.firstChild; child; child = child.nextSibling) {
         if (child.nodeType === 8)
+          // eslint-disable-next-line no-unsanitized/property
           shadow.innerHTML = child.data;
       }
     }
   </script>
 
 </body>
 </html>
--- a/accessible/tests/mochitest/jsat/test_content_text.html
+++ b/accessible/tests/mochitest/jsat/test_content_text.html
@@ -120,17 +120,17 @@
            new ExpectedAnnouncement('navigating'),
            new ExpectedEditState({
             editing: false,
             multiline: false,
             atStart: true,
             atEnd: false })],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(
-            [{ string : 'entry' }],
+            [{ string: 'entry' }],
             { focused: 'html'})],
           [ContentMessages.activateCurrent(0),
            new ExpectedClickAction(),
            new ExpectedAnnouncement('editing'),
            new ExpectedEditState({
             editing: true,
             multiline: false,
             atStart: true,
@@ -146,17 +146,17 @@
             editing: false,
             multiline: false,
             atStart: true,
             atEnd: false
            },{ focused: 'html' })
          ],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(
-            [{ string : 'entry' }],
+            [{ string: 'entry' }],
             { focused: 'html'})],
           [ContentMessages.activateCurrent(0),
            new ExpectedClickAction(),
            new ExpectedAnnouncement('editing'),
            new ExpectedEditState({
             editing: true,
             multiline: false,
             atStart: true,
--- a/accessible/tests/mochitest/jsat/test_landmarks.html
+++ b/accessible/tests/mochitest/jsat/test_landmarks.html
@@ -57,21 +57,21 @@
         expectedUtterance: [
           [{"string": "footer"}, "a footer within an article"],
           ["a footer within an article", {"string": "footer"}]],
         expectedBraille: [
           [{"string": "footerAbbr"}, "a footer within an article"],
           ["a footer within an article", {"string": "footerAbbr"}]]
       }, {
         accOrElmOrID: "section_header",
-        expectedUtterance: [[{"string":"header"}, "a header within a section"],
-                            ["a header within a section", {"string":"header"}]],
+        expectedUtterance: [[{"string": "header"}, "a header within a section"],
+                            ["a header within a section", {"string": "header"}]],
         expectedBraille: [
-          [{"string":"headerAbbr"}, "a header within a section"],
-          ["a header within a section", {"string":"headerAbbr"}]]
+          [{"string": "headerAbbr"}, "a header within a section"],
+          ["a header within a section", {"string": "headerAbbr"}]]
       }, {
         accOrElmOrID: "section_footer",
         expectedUtterance: [
           [{"string": "footer"}, "a footer within a section"],
           ["a footer within a section", {"string": "footer"}]],
         expectedBraille: [
           [{"string": "footerAbbr"}, "a footer within a section"],
           ["a footer within a section", {"string": "footerAbbr"}]]
--- a/accessible/tests/mochitest/jsat/test_output.html
+++ b/accessible/tests/mochitest/jsat/test_output.html
@@ -60,19 +60,19 @@ https://bugzilla.mozilla.org/show_bug.cg
           expectedUtterance: [[{"string": "headingLevel", "args": [1]},
             "Test heading"], ["Test heading",
             {"string": "headingLevel", "args": [1]}]],
           expectedBraille: [[{"string": "headingAbbr"}, "Test heading"],
                             ["Test heading", {"string": "headingAbbr"}]]
         }, {
           accOrElmOrID: "list",
           expectedUtterance: [[{"string": "list"},
-            {"string": "listItemsCount", "count":1}, {"string": "listStart"},
+            {"string": "listItemsCount", "count": 1}, {"string": "listStart"},
             "1.", "list one"], ["1.", "list one", {"string": "listStart"},
-            {"string": "list"}, {"string": "listItemsCount", "count":1}]
+            {"string": "list"}, {"string": "listItemsCount", "count": 1}]
           ],
           expectedBraille: [[{"string": "listAbbr"}, "list one"],
             ["list one", {"string": "listAbbr"}]]
         }, {
           accOrElmOrID: "dlist",
           expectedUtterance: [[{"string": "definitionlist"},
             {"string": "listItemsCount", "count": 0.5}, "dd one"], ["dd one",
             {"string": "definitionlist"},
@@ -94,32 +94,32 @@ https://bugzilla.mozilla.org/show_bug.cg
           expectedUtterance: [[{"string": "list"},
             {"string": "listItemsCount", "count": 1}, {"string": "listStart"},
             "list two"], ["list two", {"string": "listStart"},
             {"string": "list"}, {"string": "listItemsCount", "count": 1}]
           ],
           expectedBraille: [["*", "list two"], ["*", "list two"]]
         }, {
           accOrElmOrID: "cell",
-          expectedUtterance: [[{"string":"table"},
+          expectedUtterance: [[{"string": "table"},
             {"string": "tblColumnInfo", "count": 1},
             {"string": "tblRowInfo", "count": 1}, "Fruits and vegetables",
             {"string": "columnInfo", "args": [1]},
             {"string": "rowInfo", "args": [1]}, {"string": "list"},
             {"string": "listItemsCount", "count": 4}, {"string": "listStart"},
             {"string": "link"}, "Apples", {"string": "link"}, "Bananas",
             {"string": "link"}, "Peaches", {"string": "listEnd"},
             {"string": "link"}, "Plums"], ["Apples", {"string": "link"},
             {"string": "listStart"}, "Bananas", {"string": "link"}, "Peaches",
             {"string": "link"}, "Plums", {"string": "link"},
             {"string": "listEnd"}, {"string": "list"},
             {"string": "listItemsCount", "count": 4},
             {"string": "columnInfo", "args": [1]},
             {"string": "rowInfo", "args": [1]}, "Fruits and vegetables",
-            {"string":"table"}, {"string": "tblColumnInfo", "count": 1},
+            {"string": "table"}, {"string": "tblColumnInfo", "count": 1},
             {"string": "tblRowInfo", "count": 1}]],
           expectedBraille: [[{"string": "cellInfoAbbr", "args": [ 1, 1]},
             {"string": "listAbbr"}, {"string": "linkAbbr"}, "Apples",
             {"string": "linkAbbr"}, "Bananas", {"string": "linkAbbr"},
             "Peaches", {"string": "linkAbbr"}, "Plums"], ["Apples",
             {"string": "linkAbbr"}, "Bananas", {"string": "linkAbbr"},
             "Peaches", {"string": "linkAbbr"}, "Plums", {"string": "linkAbbr"},
             {"string": "listAbbr"},
@@ -404,19 +404,19 @@ https://bugzilla.mozilla.org/show_bug.cg
           expectedUtterance: [[{"string": "listboxoption"}, "555-12345"],
                               ["555-12345", {"string": "listboxoption"}]],
           expectedBraille: [[{"string": "listboxoptionAbbr"}, "555-12345"],
                             ["555-12345", {"string": "listboxoptionAbbr"}]]
         }, {
           accOrElmOrID: "columnheader",
           oldAccOrElmOrID: "grid",
           expectedUtterance: [[{"string": "columnInfo", "args": [1]},
-            {"string": "rowInfo", "args" :[1]}, "Sunday"], ["Sunday",
+            {"string": "rowInfo", "args": [1]}, "Sunday"], ["Sunday",
             {"string": "columnInfo", "args": [1]},
-            {"string": "rowInfo", "args" :[1]}]],
+            {"string": "rowInfo", "args": [1]}]],
           expectedBraille: [[{"string": "cellInfoAbbr", "args": [1, 1]},
             "Sunday"], ["Sunday", {"string": "cellInfoAbbr", "args": [1, 1]}]]
         }, {
           accOrElmOrID: "rowheader",
           oldAccOrElmOrID: "grid",
           expectedUtterance: [[{"string": "columnInfo", "args": [1]},
             {"string": "rowInfo", "args": [2]}, "Sunday", "Week 1"], ["Week 1",
             {"string": "columnInfo", "args": [1]},
--- a/accessible/tests/mochitest/jsat/test_output_mathml.html
+++ b/accessible/tests/mochitest/jsat/test_output_mathml.html
@@ -14,162 +14,162 @@
           src="jsatcommon.js"></script>
   <script type="application/javascript">
 
     function doTest() {
       // Test the following accOrElmOrID.
       var tests = [{
           accOrElmOrID: "math-1",
           expectedUtterance: [
-            [{"string":"open-fence"},"(","x",",","y",{"string":"close-fence"},")"],
-            ["(",{"string":"open-fence"},"x",",","y",")",{"string":"close-fence"}]
+            [{"string": "open-fence"},"(","x",",","y",{"string": "close-fence"},")"],
+            ["(",{"string": "open-fence"},"x",",","y",")",{"string": "close-fence"}]
           ],
           expectedBraille: [
-            [{"string":"open-fenceAbbr"},"(","x",",","y",{"string":"close-fenceAbbr"},")"],
-            ["(",{"string":"open-fenceAbbr"},"x",",","y",")",{"string":"close-fenceAbbr"}]
+            [{"string": "open-fenceAbbr"},"(","x",",","y",{"string": "close-fenceAbbr"},")"],
+            ["(",{"string": "open-fenceAbbr"},"x",",","y",")",{"string": "close-fenceAbbr"}]
           ]
         }, {
           accOrElmOrID: "mfrac-1",
           expectedUtterance: [
-            [{"string":"mathmlfraction"},{"string":"numerator"},"a",{"string":"denominator"},"b"],
-            ["a",{"string":"numerator"},"b",{"string":"denominator"},{"string":"mathmlfraction"}]
+            [{"string": "mathmlfraction"},{"string": "numerator"},"a",{"string": "denominator"},"b"],
+            ["a",{"string": "numerator"},"b",{"string": "denominator"},{"string": "mathmlfraction"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlfractionAbbr"},{"string":"numeratorAbbr"},"a",{"string":"denominatorAbbr"},"b"],
-            ["a",{"string":"numeratorAbbr"},"b",{"string":"denominatorAbbr"},{"string":"mathmlfractionAbbr"}]
+            [{"string": "mathmlfractionAbbr"},{"string": "numeratorAbbr"},"a",{"string": "denominatorAbbr"},"b"],
+            ["a",{"string": "numeratorAbbr"},"b",{"string": "denominatorAbbr"},{"string": "mathmlfractionAbbr"}]
           ]
         }, {
           accOrElmOrID: "mfrac-2",
           expectedUtterance: [
-            [{"string":"mathmlfractionwithoutbar"},{"string":"numerator"},"a",{"string":"denominator"},"b"],
-            ["a",{"string":"numerator"},"b",{"string":"denominator"},{"string":"mathmlfractionwithoutbar"}]
+            [{"string": "mathmlfractionwithoutbar"},{"string": "numerator"},"a",{"string": "denominator"},"b"],
+            ["a",{"string": "numerator"},"b",{"string": "denominator"},{"string": "mathmlfractionwithoutbar"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlfractionwithoutbarAbbr"},{"string":"numeratorAbbr"},"a",{"string":"denominatorAbbr"},"b"],
-            ["a",{"string":"numeratorAbbr"},"b",{"string":"denominatorAbbr"},{"string":"mathmlfractionwithoutbarAbbr"}]
+            [{"string": "mathmlfractionwithoutbarAbbr"},{"string": "numeratorAbbr"},"a",{"string": "denominatorAbbr"},"b"],
+            ["a",{"string": "numeratorAbbr"},"b",{"string": "denominatorAbbr"},{"string": "mathmlfractionwithoutbarAbbr"}]
           ]
         }, {
           accOrElmOrID: "msub-1",
           expectedUtterance: [
-            [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b"],
-            ["a",{"string":"base"},"b",{"string":"subscript"},{"string":"mathmlscripted"}]
+            [{"string": "mathmlscripted"},{"string": "base"},"a",{"string": "subscript"},"b"],
+            ["a",{"string": "base"},"b",{"string": "subscript"},{"string": "mathmlscripted"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+            [{"string": "mathmlscriptedAbbr"},{"string": "baseAbbr"},"a",{"string": "subscriptAbbr"},"b"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "subscriptAbbr"},{"string": "mathmlscriptedAbbr"}]
           ]
         }, {
           accOrElmOrID: "msup-1",
           expectedUtterance: [
-            [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"superscript"},"b"],
-            ["a",{"string":"base"},"b",{"string":"superscript"},{"string":"mathmlscripted"}]
+            [{"string": "mathmlscripted"},{"string": "base"},"a",{"string": "superscript"},"b"],
+            ["a",{"string": "base"},"b",{"string": "superscript"},{"string": "mathmlscripted"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"superscriptAbbr"},"b"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"superscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+            [{"string": "mathmlscriptedAbbr"},{"string": "baseAbbr"},"a",{"string": "superscriptAbbr"},"b"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "superscriptAbbr"},{"string": "mathmlscriptedAbbr"}]
           ]
         }, {
           accOrElmOrID: "msubsup-1",
           expectedUtterance: [
-            [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b",{"string":"superscript"},"c"],
-            ["a",{"string":"base"},"b",{"string":"subscript"},"c",{"string":"superscript"},{"string":"mathmlscripted"}]
+            [{"string": "mathmlscripted"},{"string": "base"},"a",{"string": "subscript"},"b",{"string": "superscript"},"c"],
+            ["a",{"string": "base"},"b",{"string": "subscript"},"c",{"string": "superscript"},{"string": "mathmlscripted"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b",{"string":"superscriptAbbr"},"c"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},"c",{"string":"superscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+            [{"string": "mathmlscriptedAbbr"},{"string": "baseAbbr"},"a",{"string": "subscriptAbbr"},"b",{"string": "superscriptAbbr"},"c"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "subscriptAbbr"},"c",{"string": "superscriptAbbr"},{"string": "mathmlscriptedAbbr"}]
           ]
         }, {
           accOrElmOrID: "mmultiscripts-1",
           expectedUtterance: [
-            [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b",{"string":"superscript"},"c",{"string":"superscript"},"d",{"string":"presubscript"},"e",{"string":"presubscript"},"f",{"string":"presuperscript"},"g"],
-            ["a",{"string":"base"},"b",{"string":"subscript"},"c",{"string":"superscript"},"d",{"string":"superscript"},"e",{"string":"presubscript"},"f",{"string":"presubscript"},"g",{"string":"presuperscript"},{"string":"mathmlscripted"}]
+            [{"string": "mathmlscripted"},{"string": "base"},"a",{"string": "subscript"},"b",{"string": "superscript"},"c",{"string": "superscript"},"d",{"string": "presubscript"},"e",{"string": "presubscript"},"f",{"string": "presuperscript"},"g"],
+            ["a",{"string": "base"},"b",{"string": "subscript"},"c",{"string": "superscript"},"d",{"string": "superscript"},"e",{"string": "presubscript"},"f",{"string": "presubscript"},"g",{"string": "presuperscript"},{"string": "mathmlscripted"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b",{"string":"superscriptAbbr"},"c",{"string":"superscriptAbbr"},"d",{"string":"presubscriptAbbr"},"e",{"string":"presubscriptAbbr"},"f",{"string":"presuperscriptAbbr"},"g"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},"c",{"string":"superscriptAbbr"},"d",{"string":"superscriptAbbr"},"e",{"string":"presubscriptAbbr"},"f",{"string":"presubscriptAbbr"},"g",{"string":"presuperscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+            [{"string": "mathmlscriptedAbbr"},{"string": "baseAbbr"},"a",{"string": "subscriptAbbr"},"b",{"string": "superscriptAbbr"},"c",{"string": "superscriptAbbr"},"d",{"string": "presubscriptAbbr"},"e",{"string": "presubscriptAbbr"},"f",{"string": "presuperscriptAbbr"},"g"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "subscriptAbbr"},"c",{"string": "superscriptAbbr"},"d",{"string": "superscriptAbbr"},"e",{"string": "presubscriptAbbr"},"f",{"string": "presubscriptAbbr"},"g",{"string": "presuperscriptAbbr"},{"string": "mathmlscriptedAbbr"}]
           ]
         }, {
           accOrElmOrID: "munder-1",
           expectedUtterance: [
-            [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"underscript"},"b"],
-            ["a",{"string":"base"},"b",{"string":"underscript"},{"string":"mathmlscripted"}]
+            [{"string": "mathmlscripted"},{"string": "base"},"a",{"string": "underscript"},"b"],
+            ["a",{"string": "base"},"b",{"string": "underscript"},{"string": "mathmlscripted"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"underscriptAbbr"},"b"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"underscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+            [{"string": "mathmlscriptedAbbr"},{"string": "baseAbbr"},"a",{"string": "underscriptAbbr"},"b"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "underscriptAbbr"},{"string": "mathmlscriptedAbbr"}]
           ]
         }, {
           accOrElmOrID: "mover-1",
           expectedUtterance: [
-            [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"overscript"},"b"],
-            ["a",{"string":"base"},"b",{"string":"overscript"},{"string":"mathmlscripted"}]
+            [{"string": "mathmlscripted"},{"string": "base"},"a",{"string": "overscript"},"b"],
+            ["a",{"string": "base"},"b",{"string": "overscript"},{"string": "mathmlscripted"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"overscriptAbbr"},"b"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"overscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+            [{"string": "mathmlscriptedAbbr"},{"string": "baseAbbr"},"a",{"string": "overscriptAbbr"},"b"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "overscriptAbbr"},{"string": "mathmlscriptedAbbr"}]
           ]
         }, {
           accOrElmOrID: "munderover-1",
           expectedUtterance: [
-            [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"underscript"},"b",{"string":"overscript"},"c"],
-            ["a",{"string":"base"},"b",{"string":"underscript"},"c",{"string":"overscript"},{"string":"mathmlscripted"}]
+            [{"string": "mathmlscripted"},{"string": "base"},"a",{"string": "underscript"},"b",{"string": "overscript"},"c"],
+            ["a",{"string": "base"},"b",{"string": "underscript"},"c",{"string": "overscript"},{"string": "mathmlscripted"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"underscriptAbbr"},"b",{"string":"overscriptAbbr"},"c"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"underscriptAbbr"},"c",{"string":"overscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+            [{"string": "mathmlscriptedAbbr"},{"string": "baseAbbr"},"a",{"string": "underscriptAbbr"},"b",{"string": "overscriptAbbr"},"c"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "underscriptAbbr"},"c",{"string": "overscriptAbbr"},{"string": "mathmlscriptedAbbr"}]
           ]
         }, {
           accOrElmOrID: "mroot-1",
           expectedUtterance: [
-            [{"string":"mathmlroot"},{"string":"base"},"a",{"string":"root-index"},"b"],
-            ["a",{"string":"base"},"b",{"string":"root-index"},{"string":"mathmlroot"}]
+            [{"string": "mathmlroot"},{"string": "base"},"a",{"string": "root-index"},"b"],
+            ["a",{"string": "base"},"b",{"string": "root-index"},{"string": "mathmlroot"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlrootAbbr"},{"string":"baseAbbr"},"a",{"string":"root-indexAbbr"},"b"],
-            ["a",{"string":"baseAbbr"},"b",{"string":"root-indexAbbr"},{"string":"mathmlrootAbbr"}]
+            [{"string": "mathmlrootAbbr"},{"string": "baseAbbr"},"a",{"string": "root-indexAbbr"},"b"],
+            ["a",{"string": "baseAbbr"},"b",{"string": "root-indexAbbr"},{"string": "mathmlrootAbbr"}]
           ]
         }, {
           accOrElmOrID: "mtable-1",
           expectedUtterance: [
-            [{"string":"mathmltable"},{"string":"tblColumnInfo","count":3},{"string":"tblRowInfo","count":2},{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[1]},"a",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[1]},"b",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[1]},"c",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[2]},"d",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[2]},"e",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[2]},"f"],
-            ["a",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[1]},"b",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[1]},"c",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[1]},"d",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[2]},"e",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[2]},"f",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[2]},{"string":"mathmltable"},{"string":"tblColumnInfo","count":3},{"string":"tblRowInfo","count":2}]
+            [{"string": "mathmltable"},{"string": "tblColumnInfo","count": 3},{"string": "tblRowInfo","count": 2},{"string": "columnInfo","args": [1]},{"string": "rowInfo","args": [1]},"a",{"string": "columnInfo","args": [2]},{"string": "rowInfo","args": [1]},"b",{"string": "columnInfo","args": [3]},{"string": "rowInfo","args": [1]},"c",{"string": "columnInfo","args": [1]},{"string": "rowInfo","args": [2]},"d",{"string": "columnInfo","args": [2]},{"string": "rowInfo","args": [2]},"e",{"string": "columnInfo","args": [3]},{"string": "rowInfo","args": [2]},"f"],
+            ["a",{"string": "columnInfo","args": [1]},{"string": "rowInfo","args": [1]},"b",{"string": "columnInfo","args": [2]},{"string": "rowInfo","args": [1]},"c",{"string": "columnInfo","args": [3]},{"string": "rowInfo","args": [1]},"d",{"string": "columnInfo","args": [1]},{"string": "rowInfo","args": [2]},"e",{"string": "columnInfo","args": [2]},{"string": "rowInfo","args": [2]},"f",{"string": "columnInfo","args": [3]},{"string": "rowInfo","args": [2]},{"string": "mathmltable"},{"string": "tblColumnInfo","count": 3},{"string": "tblRowInfo","count": 2}]
           ],
           expectedBraille: [
-            [{"string":"mathmltableAbbr"},{"string":"tblColumnInfoAbbr","count":3},{"string":"tblRowInfoAbbr","count":2},{"string":"cellInfoAbbr","args":[1,1]},"a",{"string":"cellInfoAbbr","args":[2,1]},"b",{"string":"cellInfoAbbr","args":[3,1]},"c",{"string":"cellInfoAbbr","args":[1,2]},"d",{"string":"cellInfoAbbr","args":[2,2]},"e",{"string":"cellInfoAbbr","args":[3,2]},"f"],
-            ["a",{"string":"cellInfoAbbr","args":[1,1]},"b",{"string":"cellInfoAbbr","args":[2,1]},"c",{"string":"cellInfoAbbr","args":[3,1]},"d",{"string":"cellInfoAbbr","args":[1,2]},"e",{"string":"cellInfoAbbr","args":[2,2]},"f",{"string":"cellInfoAbbr","args":[3,2]},{"string":"mathmltableAbbr"},{"string":"tblColumnInfoAbbr","count":3},{"string":"tblRowInfoAbbr","count":2}]
+            [{"string": "mathmltableAbbr"},{"string": "tblColumnInfoAbbr","count": 3},{"string": "tblRowInfoAbbr","count": 2},{"string": "cellInfoAbbr","args": [1,1]},"a",{"string": "cellInfoAbbr","args": [2,1]},"b",{"string": "cellInfoAbbr","args": [3,1]},"c",{"string": "cellInfoAbbr","args": [1,2]},"d",{"string": "cellInfoAbbr","args": [2,2]},"e",{"string": "cellInfoAbbr","args": [3,2]},"f"],
+            ["a",{"string": "cellInfoAbbr","args": [1,1]},"b",{"string": "cellInfoAbbr","args": [2,1]},"c",{"string": "cellInfoAbbr","args": [3,1]},"d",{"string": "cellInfoAbbr","args": [1,2]},"e",{"string": "cellInfoAbbr","args": [2,2]},"f",{"string": "cellInfoAbbr","args": [3,2]},{"string": "mathmltableAbbr"},{"string": "tblColumnInfoAbbr","count": 3},{"string": "tblRowInfoAbbr","count": 2}]
           ]
       }, {
           accOrElmOrID: "menclose-1",
           expectedUtterance: [
-            [{"string":"mathmlenclosed"},{"string":"notation-longdiv"},"a"],
-            ["a",{"string":"notation-longdiv"},{"string":"mathmlenclosed"}]
+            [{"string": "mathmlenclosed"},{"string": "notation-longdiv"},"a"],
+            ["a",{"string": "notation-longdiv"},{"string": "mathmlenclosed"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlenclosedAbbr"},{"string":"notation-longdivAbbr"},"a"],
-            ["a",{"string":"notation-longdivAbbr"},{"string":"mathmlenclosedAbbr"}]
+            [{"string": "mathmlenclosedAbbr"},{"string": "notation-longdivAbbr"},"a"],
+            ["a",{"string": "notation-longdivAbbr"},{"string": "mathmlenclosedAbbr"}]
           ]
         }, {
           accOrElmOrID: "menclose-2",
           expectedUtterance: [
-            [{"string":"mathmlenclosed"},{"string":"notation-circle"},"a"],
-            ["a",{"string":"notation-circle"},{"string":"mathmlenclosed"}]
+            [{"string": "mathmlenclosed"},{"string": "notation-circle"},"a"],
+            ["a",{"string": "notation-circle"},{"string": "mathmlenclosed"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlenclosedAbbr"},{"string":"notation-circleAbbr"},"a"],
-            ["a",{"string":"notation-circleAbbr"},{"string":"mathmlenclosedAbbr"}]
+            [{"string": "mathmlenclosedAbbr"},{"string": "notation-circleAbbr"},"a"],
+            ["a",{"string": "notation-circleAbbr"},{"string": "mathmlenclosedAbbr"}]
           ]
         }, {
           accOrElmOrID: "menclose-3",
           expectedUtterance: [
-            [{"string":"mathmlenclosed"},{"string":"notation-left"},{"string":"notation-top"},{"string":"notation-bottom"},"a"],
-            ["a",{"string":"notation-left"},{"string":"notation-top"},{"string":"notation-bottom"},{"string":"mathmlenclosed"}]
+            [{"string": "mathmlenclosed"},{"string": "notation-left"},{"string": "notation-top"},{"string": "notation-bottom"},"a"],
+            ["a",{"string": "notation-left"},{"string": "notation-top"},{"string": "notation-bottom"},{"string": "mathmlenclosed"}]
           ],
           expectedBraille: [
-            [{"string":"mathmlenclosedAbbr"},{"string":"notation-leftAbbr"},{"string":"notation-topAbbr"},{"string":"notation-bottomAbbr"},"a"],
-            ["a",{"string":"notation-leftAbbr"},{"string":"notation-topAbbr"},{"string":"notation-bottomAbbr"},{"string":"mathmlenclosedAbbr"}]
+            [{"string": "mathmlenclosedAbbr"},{"string": "notation-leftAbbr"},{"string": "notation-topAbbr"},{"string": "notation-bottomAbbr"},"a"],
+            ["a",{"string": "notation-leftAbbr"},{"string": "notation-topAbbr"},{"string": "notation-bottomAbbr"},{"string": "mathmlenclosedAbbr"}]
           ]
         }];
 
       // Test all possible utterance order preference values.
       function testOutputOrder(aOutputOrder) {
         return function() {
           SpecialPowers.pushPrefEnv({
             "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]]
--- a/accessible/tests/mochitest/name/markup.js
+++ b/accessible/tests/mochitest/name/markup.js
@@ -64,17 +64,17 @@ var gTestIterator =
 
     this.ruleIdx++;
     if (this.ruleIdx == this.ruleElms.length) {
       // When test is finished then name is empty and no explict-name.
       var defaultName = this.ruleSetElm.hasAttribute("defaultName") ?
         this.ruleSetElm.getAttribute("defaultName") : null;
       testName(this.elm, defaultName,
                "Default name test (" + gTestIterator.testID + "). ");
-      testAbsentAttrs(this.elm, {"explicit-name" : "true"});
+      testAbsentAttrs(this.elm, {"explicit-name": "true"});
 
       this.markupIdx++;
       if (this.markupIdx == this.markupElms.length) {
         //disableLogging("tree"); // debugging
         SimpleTest.finish();
         return;
       }
 
@@ -212,19 +212,19 @@ function testNameForAttrRule(aElm, aRule
       name += labelElm.getAttribute("textequiv");
     }
   }
 
   var msg = "Attribute '" + attr + "' test (" + gTestIterator.testID + "). ";
   testName(aElm, name, msg);
 
   if (aRule.getAttribute("explict-name") != "false")
-    testAttrs(aElm, {"explicit-name" : "true"}, true);
+    testAttrs(aElm, {"explicit-name": "true"}, true);
   else
-    testAbsentAttrs(aElm, {"explicit-name" : "true"});
+    testAbsentAttrs(aElm, {"explicit-name": "true"});
 
   // If @recreated attribute is used then this attribute change recreates an
   // accessible. Wait for reorder event in this case or otherwise proceed next
   // test immediately.
   if (aRule.hasAttribute("recreated")) {
     waitForEvent(EVENT_REORDER, aElm.parentNode,
                  gTestIterator.iterateNext, gTestIterator);
     aElm.removeAttribute(attr);
@@ -282,17 +282,17 @@ function testNameForElmRule(aElm, aRule)
   if (!labelElm) {
     ok(false, msg + " Failed to find '" + tagname + "' element.");
     gTestIterator.iterateNext();
     return;
   }
 
   var msg = "Element '" + tagname + "' test (" + gTestIterator.testID + ").";
   testName(aElm, labelElm.getAttribute("textequiv"), msg);
-  testAttrs(aElm, {"explicit-name" : "true"}, true);
+  testAttrs(aElm, {"explicit-name": "true"}, true);
 
   var parentNode = labelElm.parentNode;
 
   if (gDumpToConsole) {
     dump("\nProcessed elm rule. Wait for reorder event on " +
          prettyName(parentNode) + "\n");
   }
   waitForEvent(EVENT_REORDER, parentNode,
@@ -300,17 +300,17 @@ function testNameForElmRule(aElm, aRule)
 
   parentNode.removeChild(labelElm);
 }
 
 function testNameForSubtreeRule(aElm, aRule)
 {
   var msg = "From subtree test (" + gTestIterator.testID + ").";
   testName(aElm, aElm.getAttribute("textequiv"), msg);
-  testAbsentAttrs(aElm, {"explicit-name" : "true"});
+  testAbsentAttrs(aElm, {"explicit-name": "true"});
 
   if (gDumpToConsole) {
     dump("\nProcessed from subtree rule. Wait for reorder event on " +
          prettyName(aElm) + "\n");
   }
   waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
 
   while (aElm.firstChild)
@@ -369,12 +369,12 @@ function evaluateXPath(aNode, aExpr, aRe
   while (res = result.iterateNext())
     found.push(res);
 
   return found;
 }
 
 function htmlDocResolver(aPrefix) {
   var ns = {
-    'html' : 'http://www.w3.org/1999/xhtml'
+    'html': 'http://www.w3.org/1999/xhtml'
   };
   return ns[aPrefix] || null;
 }
--- a/accessible/tests/mochitest/name/test_general.html
+++ b/accessible/tests/mochitest/name/test_general.html
@@ -22,41 +22,41 @@
       // Simple label provided via ARIA
       testName("btn_simple_aria_label", "I am a button");
 
       // aria-label and aria-labelledby, expect aria-labelledby
       testName("btn_both_aria_labels", "text I am a button, two");
 
       //////////////////////////////////////////////////////////////////////////
       // aria-labelledby
-      
+    
       // Single relation. The value of 'aria-labelledby' contains the ID of
       // an element. Gets the name from text node of that element.
       testName("btn_labelledby_text", "text");
 
       // Multiple relations. The value of 'aria-labelledby' contains the IDs
       // of elements. Gets the name from text nodes of those elements.
       testName("btn_labelledby_texts", "text1 text2");
 
       //////////////////////////////////////////////////////////////////////////
       // Name from named accessible
 
       testName("input_labelledby_namedacc", "Data");
 
       //////////////////////////////////////////////////////////////////////////
       // Name from subtree (single relation labelled_by).
-      
+    
       // Gets the name from text nodes contained by nested elements
       testName("btn_labelledby_mixed", "nomore text");
 
       // Gets the name from text nodes contained by nested elements, ignores
       // hidden elements (bug 443081).
       testName("btn_labelledby_mixed_hidden_child", "nomore text2");
 
-      // Gets the name from hidden text nodes contained by nested elements, 
+      // Gets the name from hidden text nodes contained by nested elements,
       // (label element is hidden entirely), (bug 443081).
       testName("btn_labelledby_mixed_hidden", "lala more hidden text");
 
       // Gets the name from text nodes contained by nested elements having block
       // representation (every text node value in the name should be devided by
       // spaces)
       testName("btn_labelledby_mixed_block", "text more text");
 
@@ -157,17 +157,17 @@
 
       //////////////////////////////////////////////////////////////////////////
       // textarea name
 
       // textarea's name should have the value, which initially is specified by
       // a text child.
       testName("textareawithchild", "Story Foo is ended.");
 
-      // new textarea name should reflect the value change. 
+      // new textarea name should reflect the value change.
       var elem = document.getElementById("textareawithchild");
       elem.value = "Bar";
 
       testName("textareawithchild", "Story Bar is ended.");
 
       //////////////////////////////////////////////////////////////////////////
       // controls having a value used as a part of computed name
 
--- a/accessible/tests/mochitest/role.js
+++ b/accessible/tests/mochitest/role.js
@@ -95,16 +95,17 @@ const ROLE_PARAGRAPH = nsIAccessibleRole
 const ROLE_PARENT_MENUITEM = nsIAccessibleRole.ROLE_PARENT_MENUITEM;
 const ROLE_PASSWORD_TEXT = nsIAccessibleRole.ROLE_PASSWORD_TEXT;
 const ROLE_PROGRESSBAR = nsIAccessibleRole.ROLE_PROGRESSBAR;
 const ROLE_PROPERTYPAGE = nsIAccessibleRole.ROLE_PROPERTYPAGE;
 const ROLE_PUSHBUTTON = nsIAccessibleRole.ROLE_PUSHBUTTON;
 const ROLE_RADIOBUTTON = nsIAccessibleRole.ROLE_RADIOBUTTON;
 const ROLE_RADIO_GROUP = nsIAccessibleRole.ROLE_RADIO_GROUP;
 const ROLE_RADIO_MENU_ITEM = nsIAccessibleRole.ROLE_RADIO_MENU_ITEM;
+const ROLE_REGION = nsIAccessibleRole.ROLE_REGION;
 const ROLE_RICH_OPTION = nsIAccessibleRole.ROLE_RICH_OPTION;
 const ROLE_ROW = nsIAccessibleRole.ROLE_ROW;
 const ROLE_ROWHEADER = nsIAccessibleRole.ROLE_ROWHEADER;
 const ROLE_SCROLLBAR = nsIAccessibleRole.ROLE_SCROLLBAR;
 const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION;
 const ROLE_SEPARATOR = nsIAccessibleRole.ROLE_SEPARATOR;
 const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER;
 const ROLE_SPINBUTTON = nsIAccessibleRole.ROLE_SPINBUTTON;
@@ -149,17 +150,17 @@ function getRole(aAccOrElmOrID)
 {
   var acc = getAccessible(aAccOrElmOrID);
   if (!acc)
     return -1;
 
   var role = -1;
   try {
     role = acc.role;
-  } catch(e) {
+  } catch (e) {
     ok(false, "Role for " + aAccOrElmOrID + " could not be retrieved!");
   }
 
   return role;
 }
 
 /**
  * Analogy of SimpleTest.is function used to check the role.
--- a/accessible/tests/mochitest/role/test_aria.html
+++ b/accessible/tests/mochitest/role/test_aria.html
@@ -26,16 +26,17 @@
       testRole("aria_checkbox", ROLE_CHECKBUTTON);
       testRole("aria_columnheader", ROLE_COLUMNHEADER);
       testRole("aria_combobox", ROLE_COMBOBOX);
       testRole("aria_dialog", ROLE_DIALOG);
       testRole("aria_directory", ROLE_LIST);
       testRole("aria_document", ROLE_DOCUMENT);
       testRole("aria_form", ROLE_FORM);
       testRole("aria_feed", ROLE_GROUPING);
+      testRole("aria_figure", ROLE_FIGURE);
       testRole("aria_grid", ROLE_TABLE);
       testRole("aria_gridcell", ROLE_GRID_CELL);
       testRole("aria_group", ROLE_GROUPING);
       testRole("aria_heading", ROLE_HEADING);
       testRole("aria_img", ROLE_GRAPHIC);
       testRole("aria_link", ROLE_LINK);
       testRole("aria_list", ROLE_LIST);
       testRole("aria_listbox", ROLE_LISTBOX);
@@ -48,17 +49,21 @@
       testRole("aria_menuitem", ROLE_MENUITEM);
       testRole("aria_menuitemcheckbox", ROLE_CHECK_MENU_ITEM);
       testRole("aria_menuitemradio", ROLE_RADIO_MENU_ITEM);
       testRole("aria_note", ROLE_NOTE);
       testRole("aria_presentation", ROLE_TEXT); // weak role
       testRole("aria_progressbar", ROLE_PROGRESSBAR);
       testRole("aria_radio", ROLE_RADIOBUTTON);
       testRole("aria_radiogroup", ROLE_RADIO_GROUP);
-      testRole("aria_region", ROLE_PANE);
+      testRole("aria_region_no_name", ROLE_TEXT);
+      testRole("aria_region_has_label", ROLE_REGION);
+      testRole("aria_region_has_labelledby", ROLE_REGION);
+      testRole("aria_region_has_title", ROLE_REGION);
+      testRole("aria_region_empty_name", ROLE_TEXT);
       testRole("aria_row", ROLE_ROW);
       testRole("aria_rowheader", ROLE_ROWHEADER);
       testRole("aria_scrollbar", ROLE_SCROLLBAR);
       testRole("aria_searchbox", ROLE_ENTRY);
       testRole("aria_separator", ROLE_SEPARATOR);
       testRole("aria_slider", ROLE_SLIDER);
       testRole("aria_spinbutton", ROLE_SPINBUTTON);
       testRole("aria_status", ROLE_STATUSBAR);
@@ -102,21 +107,21 @@
       var weak_landmarks = ["banner", "complementary", "contentinfo",
           "main", "navigation", "search"];
       for (l in weak_landmarks)
         testRole(weak_landmarks[l], ROLE_SECTION);
 
       for (l in weak_landmarks) {
         var id = weak_landmarks[l] + "_table";
         testRole(id, ROLE_TABLE);
-        
+    
         var accessibleTable = getAccessible(id, [nsIAccessibleTable], null,
                                             DONOTFAIL_IF_NO_INTERFACE);
         ok(!!accessibleTable, "landmarked table should have nsIAccessibleTable");
-        
+    
         if (accessibleTable)
           is(accessibleTable.getCellAt(0,0).firstChild.name, "hi", "no cell");
       }
 
       //////////////////////////////////////////////////////////////////////////
       // test gEmptyRoleMap
       testRole("buttontable_row", ROLE_NOTHING);
       testRole("buttontable_cell", ROLE_NOTHING);
@@ -186,16 +191,21 @@
      title="Support ARIA 1.1 switch role">
     Bug 1136563
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
      title="Support ARIA 1.1 searchbox role">
     Bug 1121518
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1356049"
+     title="Map ARIA figure role">
+    Bug 1356049
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <span id="aria_alert" role="alert"/>
   <span id="aria_alertdialog" role="alertdialog"/>
   <span id="aria_application" role="application"/>
@@ -204,16 +214,17 @@
   <span id="aria_checkbox" role="checkbox"/>
   <span id="aria_columnheader" role="columnheader"/>
   <span id="aria_combobox" role="combobox"/>
   <span id="aria_dialog" role="dialog"/>
   <span id="aria_directory" role="directory"/>
   <span id="aria_document" role="document"/>
   <span id="aria_form" role="form"/>
   <span id="aria_feed" role="feed"/>
+  <span id="aria_figure" role="figure"/>
   <span id="aria_grid" role="grid"/>
   <span id="aria_gridcell" role="gridcell"/>
   <span id="aria_group" role="group"/>
   <span id="aria_heading" role="heading"/>
   <span id="aria_img" role="img"/>
   <span id="aria_link" role="link"/>
   <span id="aria_list" role="list"/>
   <span id="aria_listbox" role="listbox"/>
@@ -226,17 +237,21 @@
   <span id="aria_menuitem" role="menuitem"/>
   <span id="aria_menuitemcheckbox" role="menuitemcheckbox"/>
   <span id="aria_menuitemradio" role="menuitemradio"/>
   <span id="aria_note" role="note"/>
   <span id="aria_presentation" role="presentation" tabindex="0"/>
   <span id="aria_progressbar" role="progressbar"/>
   <span id="aria_radio" role="radio"/>
   <span id="aria_radiogroup" role="radiogroup"/>
-  <span id="aria_region" role="region"/>
+  <span id="aria_region_no_name" role="region"/>
+  <span id="aria_region_has_label" role="region" aria-label="label"/>
+  <span id="aria_region_has_labelledby" role="region" aria-labelledby="label"/><span id="label" aria-label="label">
+  <span id="aria_region_has_title" role="region" title="title"/>
+  <span id="aria_region_empty_name" role="region" aria-label="" title="" aria-labelledby="empty"/><span id="empty"/>
   <span id="aria_row" role="row"/>
   <span id="aria_rowheader" role="rowheader"/>
   <span id="aria_scrollbar" role="scrollbar"/>
   <span id="aria_searchbox" role="textbox"/>
   <span id="aria_separator" role="separator"/>
   <span id="aria_slider" role="slider"/>
   <span id="aria_spinbutton" role="spinbutton"/>
   <span id="aria_status" role="status"/>
--- a/accessible/tests/mochitest/selectable/test_aria.html
+++ b/accessible/tests/mochitest/selectable/test_aria.html
@@ -67,17 +67,17 @@
       select.addItemToSelection(0);
       testSelectableSelection(id, [ "listbox2_item1" ]);
       select.removeItemFromSelection(0);
       testSelectableSelection(id, [ ]);
       select.selectAll();
       testSelectableSelection(id, [ "listbox2_item1", "listbox2_item2" ]);
       select.unselectAll();
       testSelectableSelection(id, [ ]);
-      
+    
       //////////////////////////////////////////////////////////////////////////
       // role="grid"
 
       id = "grid1";
       ok(isAccessible(id, [nsIAccessibleSelectable]),
          "No selectable accessible for " + id);
 
       testSelectableSelection(id, [ ]);
--- a/accessible/tests/mochitest/states.js
+++ b/accessible/tests/mochitest/states.js
@@ -32,16 +32,17 @@ const STATE_PROTECTED = nsIAccessibleSta
 const STATE_READONLY = nsIAccessibleStates.STATE_READONLY;
 const STATE_REQUIRED = nsIAccessibleStates.STATE_REQUIRED;
 const STATE_SELECTABLE = nsIAccessibleStates.STATE_SELECTABLE;
 const STATE_SELECTED = nsIAccessibleStates.STATE_SELECTED;
 const STATE_TRAVERSED = nsIAccessibleStates.STATE_TRAVERSED;
 const STATE_UNAVAILABLE = nsIAccessibleStates.STATE_UNAVAILABLE;
 
 const EXT_STATE_ACTIVE = nsIAccessibleStates.EXT_STATE_ACTIVE;
+const EXT_STATE_CURRENT = nsIAccessibleStates.EXT_STATE_CURRENT;
 const EXT_STATE_DEFUNCT = nsIAccessibleStates.EXT_STATE_DEFUNCT;
 const EXT_STATE_EDITABLE = nsIAccessibleStates.EXT_STATE_EDITABLE;
 const EXT_STATE_ENABLED = nsIAccessibleStates.EXT_STATE_ENABLED;
 const EXT_STATE_EXPANDABLE = nsIAccessibleStates.EXT_STATE_EXPANDABLE;
 const EXT_STATE_HORIZONTAL = nsIAccessibleStates.EXT_STATE_HORIZONTAL;
 const EXT_STATE_MODAL = nsIAccessibleStates.EXT_STATE_MODAL;
 const EXT_STATE_MULTI_LINE = nsIAccessibleStates.EXT_STATE_MULTI_LINE;
 const EXT_STATE_PINNED = nsIAccessibleStates.EXT_STATE_PINNED;
@@ -182,17 +183,17 @@ function testStatesInSubtree(aAccOrElmOr
     // Right now, text leafs don't get tested because the states are not being
     // propagated.
     testStates(acc, aState, aExtraState, aAbsentState);
 
   // Iterate over its children to see if the state got propagated.
   var children = null;
   try {
     children = acc.children;
-  } catch(e) {}
+  } catch (e) {}
   ok(children, "Could not get children for " + aAccOrElmOrID +"!");
 
   if (children) {
     for (var i = 0; i < children.length; i++) {
       var childAcc = children.queryElementAt(i, nsIAccessible);
       testStatesInSubtree(childAcc, aState, aExtraState, aAbsentState);
     }
   }
--- a/accessible/tests/mochitest/states/test_aria.html
+++ b/accessible/tests/mochitest/states/test_aria.html
@@ -41,17 +41,17 @@
           testStates(acc, STATE_FOCUSABLE);
         }
       }
 
       // Iterate over its children to see if the state got propagated.
       var children = null;
       try {
         children = acc.children;
-      } catch(e) {}
+      } catch (e) {}
       ok(children, "Could not get children for " + aAccOrElmOrID +"!");
 
       if (children) {
         for (var i = 0; i < children.length; i++) {
           var childAcc = children.queryElementAt(i, nsIAccessible);
           testAriaDisabledTree(childAcc);
         }
       }
@@ -89,16 +89,17 @@
         testStates(checkboxElem, STATE_MIXED, 0);
       }
 
       // aria-checked
       testStates("aria_checked_checkbox", STATE_CHECKED);
       testStates("aria_mixed_checkbox", STATE_MIXED);
       testStates("aria_checked_switch", STATE_CHECKED);
       testStates("aria_mixed_switch", 0, 0, STATE_MIXED); // unsupported
+      testStates("aria_mixed_switch", 0, 0, STATE_CHECKED); // not checked due to being unsupported
 
       // test disabled group and all its descendants to see if they are
       // disabled, too. See bug 429285.
       testAriaDisabledTree("group");
 
       // aria-modal
       testStates("aria_modal", 0, EXT_STATE_MODAL);
       testStates("aria_modal_false", 0, 0, 0, EXT_STATE_MODAL);
@@ -261,16 +262,23 @@
       testStates("aria_vtreegrid", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
 
       // indeterminate ARIA progressbars (no aria-valuenow or aria-valuetext attribute)
       // should expose mixed state
       testStates("aria_progressbar", STATE_MIXED);
       testStates("aria_progressbar_valuenow", 0, 0, STATE_MIXED);
       testStates("aria_progressbar_valuetext", 0, 0, STATE_MIXED);
 
+      // aria-current
+      testStates("current_page_1", 0, EXT_STATE_CURRENT);
+      testStates("page_2", 0, 0, EXT_STATE_CURRENT);
+      testStates("page_3", 0, 0, EXT_STATE_CURRENT);
+      testStates("page_4", 0, 0, EXT_STATE_CURRENT);
+      testStates("current_foo", 0, EXT_STATE_CURRENT);
+
       testStates("aria_listbox", STATE_FOCUSABLE);
       testStates("aria_grid", STATE_FOCUSABLE);
       testStates("aria_tree", STATE_FOCUSABLE);
       testStates("aria_treegrid", STATE_FOCUSABLE);
       testStates("aria_listbox_disabled", 0, 0, STATE_FOCUSABLE);
       testStates("aria_grid_disabled", 0, 0, STATE_FOCUSABLE);
       testStates("aria_tree_disabled", 0, 0, STATE_FOCUSABLE);
       testStates("aria_treegrid_disabled", 0, 0, STATE_FOCUSABLE);
@@ -355,16 +363,21 @@
      title="Pressed state is not exposed on a button element with aria-pressed attribute">
     Mozilla Bug 989958
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
      title="Support ARIA 1.1 switch role">
     Mozilla Bug 1136563
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1355921"
+     title="Elements with a defined, non-false value for aria-current should expose ATK_STATE_ACTIVE">
+    Mozilla Bug 1355921
+  </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <div id="textbox_autocomplete_inline" role="textbox" aria-autocomplete="inline"></div>
   <div id="textbox_autocomplete_list" role="textbox" aria-autocomplete="list"></div>
@@ -626,10 +639,16 @@
   <div id="aria_treegrid_disabled" role="treegrid" aria-disabled="true">
     <div role="row"><div role="gridcell">H</div></div>
     <div role="row"><div role="gridcell">h</div></div>
   </div>
 
   <!-- Test that directory is readonly -->
   <div id="aria_directory" role="directory"></div>
 
+  <!-- aria-current -->
+  <div id="current_page_1" role="link" aria-current="page">1</div>
+  <div id="page_2" role="link" aria-current="false">2</div>
+  <div id="page_3" role="link">3</div>
+  <div id="page_4" role="link" aria-current="">4</div>
+  <div id="current_foo" aria-current="foo">foo</div>
 </body>
 </html>
--- a/accessible/tests/mochitest/states/test_docarticle.html
+++ b/accessible/tests/mochitest/states/test_docarticle.html
@@ -29,17 +29,17 @@
 
         document.designMode = "on";
 
         testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY);
         testStates("aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
         testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
         testStates("article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
         testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
-  
+
         document.designMode = "off";
 
         testStates(docAcc, STATE_READONLY);
         testStates("aria_article", STATE_READONLY);
         testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
         testStates("article", STATE_READONLY);
         testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
       }
--- a/accessible/tests/mochitest/table.js
+++ b/accessible/tests/mochitest/table.js
@@ -298,17 +298,17 @@ function testTableIndexes(aIdentifier, a
           try {
             strIdx = attrs.getStringProperty("table-cell-index");
           } catch (e) {
             ok(false,
                cellId + ": no cell index from object attributes on the cell accessible at index " + idx + ".");
           }
 
           if (strIdx) {
-            is (parseInt(strIdx), idx,
+            is(parseInt(strIdx), idx,
                 cellId + ": cell index from object attributes of cell accessible isn't corrent.");
           }
 
           // cell: table
           try {
             is(cellAcc.table, tableAcc,
                cellId + ": wrong table accessible for the cell.");
 
@@ -399,22 +399,22 @@ function testTableSelection(aIdentifier,
   is(acc.selectedColumnCount, selCols.length,
      msg + "Wrong count of selected columns for " + prettyName(aIdentifier));
 
   // getSelectedColumns test
   var actualSelColsCountObj = { value: null };
   var actualSelCols = acc.getSelectedColumnIndices(actualSelColsCountObj);
 
   var actualSelColsCount = actualSelColsCountObj.value;
-  is (actualSelColsCount, selCols.length,
+  is(actualSelColsCount, selCols.length,
       msg + "Wrong count of selected columns for " + prettyName(aIdentifier) +
       "from getSelectedColumns.");
 
   for (var i = 0; i < actualSelColsCount; i++) {
-    is (actualSelCols[i], selCols[i],
+    is(actualSelCols[i], selCols[i],
         msg + "Column at index " + selCols[i] + " should be selected.");
   }
 
   // Rows selection tests.
   var selRows = [];
 
   // isRowSelected test
   for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
@@ -439,22 +439,22 @@ function testTableSelection(aIdentifier,
   is(acc.selectedRowCount, selRows.length,
      msg + "Wrong count of selected rows for " + prettyName(aIdentifier));
 
   // getSelectedRows test
   var actualSelrowCountObj = { value: null };
   var actualSelRows = acc.getSelectedRowIndices(actualSelrowCountObj);
 
   var actualSelrowCount = actualSelrowCountObj.value;
-  is (actualSelrowCount, selRows.length,
+  is(actualSelrowCount, selRows.length,
       msg + "Wrong count of selected rows for " + prettyName(aIdentifier) +
       "from getSelectedRows.");
 
   for (var i = 0; i < actualSelrowCount; i++) {
-    is (actualSelRows[i], selRows[i],
+    is(actualSelRows[i], selRows[i],
         msg + "Row at index " + selRows[i] + " should be selected.");
   }
 
   // Cells selection tests.
   var selCells = [];
 
   // isCellSelected test
   for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
--- a/accessible/tests/mochitest/table/test_layoutguess.html
+++ b/accessible/tests/mochitest/table/test_layoutguess.html
@@ -61,17 +61,17 @@
       testAbsentAttrs("table6.2.3", attr);
 
       // table with abbr element
       testAbsentAttrs("table6.3", attr);
 
       // table with abbr element having empty text node
       testAbsentAttrs("table6.4", attr);
 
-      // table with abbr element and non-empty text node 
+      // table with abbr element and non-empty text node
       testAttrs("table6.5", attr, true);
 
       // layout table with nested table
       testAttrs("table9", attr, true);
 
       // layout table with 1 column
       testAttrs("table10", attr, true);
 
--- a/accessible/tests/mochitest/table/test_table_2.html
+++ b/accessible/tests/mochitest/table/test_table_2.html
@@ -10,21 +10,21 @@
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
 
   <script type="text/javascript">
 
 function doTest()
 {
-  // Test table with role=alert. 
+  // Test table with role=alert.
   var tableInterfaceExposed = true;
   var accTable3 = getAccessible("table3", [nsIAccessibleTable], null, DONOTFAIL_IF_NO_INTERFACE);
   if (!accTable3)
-    tableInterfaceExposed = false;  
+    tableInterfaceExposed = false;
   ok(tableInterfaceExposed, "table interface is not exposed");
 
   if (tableInterfaceExposed) {
     testRole(accTable3, ROLE_ALERT);
 
     is(accTable3.getCellAt(0,0).firstChild.name, "cell0", "wrong cell");
     is(accTable3.getCellAt(0,1).firstChild.name, "cell1", "wrong cell");
   }
--- a/accessible/tests/mochitest/test_OuterDocAccessible.html
+++ b/accessible/tests/mochitest/test_OuterDocAccessible.html
@@ -39,33 +39,33 @@ https://bugzilla.mozilla.org/show_bug.cg
 
           // see bug 440770, no actions wanted on outer doc
           is(outerDocAcc.actionCount, 0,
              "Wrong number of actions for internal frame!");
 
           try {
             outerDocAcc.getActionName(0);
             do_throw("No exception thrown for actionName!");
-          } catch(e) {
+          } catch (e) {
             ok(e.result, ns_error_invalid_arg,
                "Wrong return value for actionName call!");
           }
 
           try {
             actionTempStr = outerDocAcc.getActionDescription(0);
             do_throw("No exception thrown for actionDescription!");
-          } catch(e) {
+          } catch (e) {
             ok(e.result, ns_error_invalid_arg,
                "Wrong return value for actionDescription call!");
           }
 
           try {
             outerDocAcc.doAction(0);
             do_throw("No exception thrown for doAction!");
-          } catch(e) {
+          } catch (e) {
             ok(e.result, ns_error_invalid_arg,
                "Wrong return value for doAction call!");
           }
         }
       }
 
       SimpleTest.finish();
     }
--- a/accessible/tests/mochitest/test_aria_token_attrs.html
+++ b/accessible/tests/mochitest/test_aria_token_attrs.html
@@ -31,16 +31,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       testStates("button_pressed_true", STATE_PRESSED, 0, STATE_CHECKABLE);
       testStates("button_pressed_false", 0, 0, STATE_CHECKABLE | STATE_PRESSED);
       testStates("button_pressed_empty", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
       testStates("button_pressed_undefined", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
       testStates("button_pressed_absent", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
 
       // test (checkbox) checkable and checked states
       testStates("checkbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+      testStates("checkbox_checked_mixed", (STATE_CHECKABLE | STATE_MIXED), 0, STATE_CHECKED);
       testStates("checkbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("checkbox_checked_empty", STATE_CHECKABLE , 0, STATE_CHECKED);
       testStates("checkbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("checkbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
 
       // test native checkbox checked state and aria-checked state (if conflict, native wins)
       testStates("native_checkbox_nativechecked_ariatrue", (STATE_CHECKABLE | STATE_CHECKED));
       testStates("native_checkbox_nativechecked_ariafalse", (STATE_CHECKABLE | STATE_CHECKED));
@@ -93,51 +94,55 @@ https://bugzilla.mozilla.org/show_bug.cg
       testStates("option_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
       testStates("option_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("option_checked_empty", 0 , 0, STATE_CHECKABLE | STATE_CHECKED);
       testStates("option_checked_undefined", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
       testStates("option_checked_absent", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
 
       // test (menuitem) checkable and checked states, which are unsupported on this role
       testStates("menuitem_checked_true", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+      testStates("menuitem_checked_mixed", 0, 0, (STATE_CHECKABLE | STATE_CHECKED | STATE_MIXED));
       testStates("menuitem_checked_false", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
       testStates("menuitem_checked_empty", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
       testStates("menuitem_checked_undefined", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
       testStates("menuitem_checked_absent", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
 
       // test (menuitemcheckbox) checkable and checked states
       testStates("menuitemcheckbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+      testStates("menuitemcheckbox_checked_mixed", (STATE_CHECKABLE | STATE_MIXED), 0, STATE_CHECKED);
       testStates("menuitemcheckbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemcheckbox_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemcheckbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemcheckbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
 
       // test (menuitemcheckbox) readonly states
       testStates("menuitemcheckbox_readonly_true", STATE_READONLY);
       testStates("menuitemcheckbox_readonly_false", 0, 0, STATE_READONLY);
       testStates("menuitemcheckbox_readonly_empty", 0, 0, STATE_READONLY);
       testStates("menuitemcheckbox_readonly_undefined", 0, 0, STATE_READONLY);
       testStates("menuitemcheckbox_readonly_absent", 0, 0, STATE_READONLY);
 
       // test (menuitemradio) checkable and checked states
       testStates("menuitemradio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+      testStates("menuitemradio_checked_mixed", STATE_CHECKABLE, 0, (STATE_MIXED | STATE_CHECKED));
       testStates("menuitemradio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemradio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemradio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemradio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
 
       // test (menuitemradio) readonly states
       testStates("menuitemradio_readonly_true", STATE_READONLY);
       testStates("menuitemradio_readonly_false", 0, 0, STATE_READONLY);
       testStates("menuitemradio_readonly_empty", 0, 0, STATE_READONLY);
       testStates("menuitemradio_readonly_undefined", 0, 0, STATE_READONLY);
       testStates("menuitemradio_readonly_absent", 0, 0, STATE_READONLY);
 
       // test (radio) checkable and checked states
       testStates("radio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+      testStates("radio_checked_mixed", STATE_CHECKABLE, 0, (STATE_MIXED | STATE_CHECKED));
       testStates("radio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("radio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("radio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("radio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
 
       // test (radiogroup) readonly states
       testStates("radiogroup_readonly_true", STATE_READONLY);
       testStates("radiogroup_readonly_false", 0, 0, STATE_READONLY);
@@ -230,16 +235,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   <div id="button_pressed_true" role="button" aria-pressed="true">This button has aria-pressed="true" and should get ROLE_TOGGLE_BUTTON. It should also get STATE_PRESSED.</div>
   <div id="button_pressed_false" role="button" aria-pressed="false">This button has aria-pressed="false" and should get ROLE_TOGGLE_BUTTON.</div>
   <div id="button_pressed_empty" role="button" aria-pressed="">This button has aria-pressed="" and should <emph>not</emph> get ROLE_BUTTON.</div>
   <div id="button_pressed_undefined" role="button" aria-pressed="undefined">This button has aria-pressed="undefined" and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div>
   <div id="button_pressed_absent" role="button">This button has <emph>no</emph> aria-pressed attribute and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div>
 
   <div id="checkbox_checked_true" role="checkbox" aria-checked="true">This checkbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+  <div id="checkbox_checked_mixed" role="checkbox" aria-checked="mixed">This checkbox has aria-checked="mixed" and should get STATE_CHECKABLE. It should also get STATE_MIXED.</div>
   <div id="checkbox_checked_false" role="checkbox" aria-checked="false">This checkbox has aria-checked="false" and should get STATE_CHECKABLE.</div>
   <div id="checkbox_checked_empty" role="checkbox" aria-checked="">This checkbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
   <div id="checkbox_checked_undefined" role="checkbox" aria-checked="undefined">This checkbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
   <div id="checkbox_checked_absent" role="checkbox">This checkbox has <emph>no</emph> aria-checked attribute and should get STATE_CHECKABLE.</div>
 
   <form action="">
     <input id="native_checkbox_nativechecked_ariatrue" type="checkbox" checked="checked" aria-checked="true"/>
     <input id="native_checkbox_nativechecked_ariafalse" type="checkbox" checked="checked" aria-checked="false"/>
@@ -291,47 +297,51 @@ https://bugzilla.mozilla.org/show_bug.cg
     <div id="option_checked_undefined" role="option" aria-checked="undefined">item</div>
   </div>
   <div id="listbox_multiselectable_absent" role="listbox">
     <div id="option_checked_absent" role="option">item</div>
   </div>
 
   <div role="menu">
     <div id="menuitem_checked_true" role="menuitem" aria-checked="true">Generic menuitems don't support aria-checked.</div>
+    <div id="menuitem_checked_mixed" role="menuitem" aria-checked="mixed">Generic menuitems don't support aria-checked.</div>
     <div id="menuitem_checked_false" role="menuitem" aria-checked="false">Generic menuitems don't support aria-checked.</div>
     <div id="menuitem_checked_empty" role="menuitem" aria-checked="">Generic menuitems don't support aria-checked.</div>
     <div id="menuitem_checked_undefined" role="menuitem" aria-checked="undefined">Generic menuitems don't support aria-checked.</div>
     <div id="menuitem_checked_absent" role="menuitem">Generic menuitems don't support aria-checked.</div>
 
     <div id="menuitemcheckbox_checked_true" role="menuitemcheckbox" aria-checked="true">This menuitemcheckbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+    <div id="menuitemcheckbox_checked_mixed" role="menuitemcheckbox" aria-checked="mixed">This menuitemcheckbox has aria-checked="mixed" and should get STATE_CHECKABLE. It should also get STATE_MIXED.</div>
     <div id="menuitemcheckbox_checked_false" role="menuitemcheckbox" aria-checked="false">This menuitemcheckbox has aria-checked="false" and should get STATE_CHECKABLE.</div>
     <div id="menuitemcheckbox_checked_empty" role="menuitemcheckbox" aria-checked="">This menuitemcheckbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
     <div id="menuitemcheckbox_checked_undefined" role="menuitemcheckbox" aria-checked="undefined">This menuitemcheckbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
     <div id="menuitemcheckbox_checked_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-checked attribute and should <emph>not</emph> get STATE_CHECKABLE.</div>
 
     <div id="menuitemcheckbox_readonly_true" role="menuitemcheckbox" aria-readonly="true">This menuitemcheckbox has aria-readonly="true" and should get STATE_READONLY.</div>
     <div id="menuitemcheckbox_readonly_false" role="menuitemcheckbox" aria-readonly="false">This menuitemcheckbox has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
     <div id="menuitemcheckbox_readonly_empty" role="menuitemcheckbox" aria-readonly="">This menuitemcheckbox has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
     <div id="menuitemcheckbox_readonly_undefined" role="menuitemcheckbox" aria-readonly="undefined">This menuitemcheckbox has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
     <div id="menuitemcheckbox_readonly_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
 
     <div id="menuitemradio_checked_true" role="menuitemradio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+    <div id="menuitemradio_checked_mixed" role="menuitemradio" aria-checked="mixed">This menuitem has aria-checked="mixed" and should get STATE_CHECKABLE. It should not get STATE_MIXED.</div>
     <div id="menuitemradio_checked_false" role="menuitemradio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
     <div id="menuitemradio_checked_empty" role="menuitemradio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
     <div id="menuitemradio_checked_undefined" role="menuitemradio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
     <div id="menuitemradio_checked_absent" role="menuitemradio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
   </div>
 
     <div id="menuitemradio_readonly_true" role="menuitemradio" aria-readonly="true">This menuitemradio has aria-readonly="true" and should get STATE_READONLY.</div>
     <div id="menuitemradio_readonly_false" role="menuitemradio" aria-readonly="false">This menuitemradio has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
     <div id="menuitemradio_readonly_empty" role="menuitemradio" aria-readonly="">This menuitemradio has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
     <div id="menuitemradio_readonly_undefined" role="menuitemradio" aria-readonly="undefined">This menuitemradio has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
     <div id="menuitemradio_readonly_absent" role="menuitemradio">This menuitemradio has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
 
   <div id="radio_checked_true" role="radio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_CHECKED.</div>
+  <div id="radio_checked_mixed" role="radio" aria-checked="mixed">This radio button has aria-checked="mixed" and should get STATE_CHECKABLE. It should not get STATE_MIXED.</div>
   <div id="radio_checked_false" role="radio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
   <div id="radio_checked_empty" role="radio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
   <div id="radio_checked_undefined" role="radio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
   <div id="radio_checked_absent" role="radio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
 
   <div id="radiogroup_readonly_true" role="radiogroup" aria-readonly="true">
     <div role="radio">yes</div>
     <div role="radio">no</div>
--- a/accessible/tests/mochitest/test_nsIAccessibleDocument.html
+++ b/accessible/tests/mochitest/test_nsIAccessibleDocument.html
@@ -56,25 +56,25 @@ https://bugzilla.mozilla.org/show_bug.cg
         // uncomment the below two lines to enable the test.
 //        is(docAcc.docType, "HTML",
 //           "Wrong type of document!");
 
         // Test for correct nsIDOMDocument retrieval.
         var domDoc = null;
         try {
           domDoc = docAcc.DOMDocument.QueryInterface(nsIDOMDocument);
-        } catch(e) {}
+        } catch (e) {}
         ok(domDoc, "no nsIDOMDocument for this doc accessible!");
         is(domDoc, document, "Document nodes do not match!");
 
         // Test for correct nsIDOMWindow retrieval.
         var domWindow = null;
         try {
           domWindow = docAcc.window.QueryInterface(nsIDOMWindow);
-        } catch(e) {}
+        } catch (e) {}
         ok(domWindow, "no nsIDOMWindow for this doc accessible!");
         is(domWindow, window, "Window nodes do not match!");
       }
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
--- a/accessible/tests/mochitest/test_nsIAccessibleImage.html
+++ b/accessible/tests/mochitest/test_nsIAccessibleImage.html
@@ -43,25 +43,25 @@ https://bugzilla.mozilla.org/show_bug.cg
       if (parentY.value < 0)
         parentY.value = -1;
 
       // See if asking image for child at image's screen coordinates gives
       // correct accessible. getChildAtPoint operates on screen coordinates.
       var tempAcc = null;
       try {
         tempAcc = aAcc.getChildAtPoint(screenX.value, screenY.value);
-      } catch(e) {}
+      } catch (e) {}
       is(tempAcc, aAcc,
          "Wrong accessible returned for position of " + aID + "!");
 
       // get image's parent.
       var imageParentAcc = null;
       try {
         imageParentAcc = aAcc.parent;
-      } catch(e) {}
+      } catch (e) {}
       ok(imageParentAcc, "no parent accessible for " + aID + "!");
 
       if (imageParentAcc) {
         // See if parent's screen coordinates plus image's parent relative
         // coordinates equal to image's screen coordinates.
         var parentAccX = {}, parentAccY = {}, parentAccWidth = {},
             parentAccHeight = {};
         imageParentAcc.getBounds(parentAccX, parentAccY, parentAccWidth,
--- a/accessible/tests/mochitest/textattrs/test_general.html
+++ b/accessible/tests/mochitest/textattrs/test_general.html
@@ -548,17 +548,17 @@
        //////////////////////////////////////////////////////////////////////////
       // area19, "HTML5 mark tag" test
       // text enclosed in mark tag will have a different background color
       ID = "area19";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
 
       attrs = {};
       testTextAttrs(ID, 0, attrs, defAttrs, 0, 10);
-      
+    
       tempElem = getNode(ID).firstChild.nextSibling;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem);
       attrs = { "background-color": gComputedStyle.backgroundColor };
       testTextAttrs(ID, 11, attrs, defAttrs, 10, 17);
 
       attrs = {};
       testTextAttrs(ID, 18, attrs, defAttrs, 17, 28);
 
--- a/accessible/tests/mochitest/tree/test_brokencontext.html
+++ b/accessible/tests/mochitest/tree/test_brokencontext.html
@@ -65,17 +65,17 @@
 
     // HTML table display:inline inside table. We shouldn't be fooled
     // by the outside table and shouldn't create table accessible and table cell
     // accessible in this case.
     checkIfNotAccessible("inline_table2");
     checkIfNotAccessible("tr_in_inline_table2");
     checkIfTDGeneric("td_in_inline_table2");
 
-    // HTML table display:block inside table. 
+    // HTML table display:block inside table.
     checkIfNotAccessible("block_table");
     checkIfNotAccessible("tr_in_block_table");
     checkIfTDGeneric("td_in_block_table");
 
     ////////////////////////////////////////////////////////////////////////////
     // HTML list elements outside list context.
 
     ok(!isAccessible("presentation_ul"),
--- a/accessible/tests/mochitest/tree/test_txtctrl.html
+++ b/accessible/tests/mochitest/tree/test_txtctrl.html
@@ -68,17 +68,17 @@
           {
             role: ROLE_TEXT_LEAF,
             children: []
           }
         ]
       };
 
       testAccessibleTree("txc5", accTree);
-      
+    
       // input@type="tel", value
       accTree = {
         role: ROLE_ENTRY,
         children: [
           { // text child
             role: ROLE_TEXT_LEAF,
             children: []
           }
--- a/accessible/tests/mochitest/treeupdate/test_table.html
+++ b/accessible/tests/mochitest/treeupdate/test_table.html
@@ -31,20 +31,20 @@
       this.eventSeq = [
         new invokerChecker(EVENT_REORDER, aTableID)
       ];
 
       this.finalCheck = function appendCaption_finalCheck()
       {
         var tree =
           { TABLE: [
-            { CAPTION: [ 
+            { CAPTION: [
               { TEXT_LEAF: [] }
             ] },
-            { ROW: [ 
+            { ROW: [
               { CELL: [ {TEXT_LEAF: [] }]},
               { CELL: [ {TEXT_LEAF: [] }]}
             ] }
           ] };
         testAccessibleTree(aTableID, tree);
       }
 
       this.getID = function appendCaption_getID()
--- a/accessible/tests/mochitest/treeview.js
+++ b/accessible/tests/mochitest/treeview.js
@@ -143,17 +143,17 @@ nsTreeView.prototype =
   isSorted: function isSorted() {},
   toggleOpenState: function toggleOpenState(aIndex)
   {
     var data = this.getDataForIndex(aIndex);
 
     data.open = !data.open;
     var rowCount = this.getRowCountIntl(data.children);
 
-    if (data.open) 
+    if (data.open)
       this.mTree.rowCountChanged(aIndex + 1, rowCount);
     else
       this.mTree.rowCountChanged(aIndex + 1, -rowCount);
   },
   selectionChanged: function selectionChanged() {},
   cycleHeader: function cycleHeader(aCol) {},
   cycleCell: function cycleCell(aRow, aCol)
   {
@@ -213,17 +213,17 @@ nsTreeView.prototype =
   },
 
   getRowCountIntl: function getRowCountIntl(aChildren)
   {
     var rowCount = 0;
     for (var childIdx = 0; childIdx < aChildren.length; childIdx++) {
       rowCount++;
 
-      var data = aChildren[childIdx];      
+      var data = aChildren[childIdx];
       if (data.open)
         rowCount += this.getRowCountIntl(data.children);
     }
 
     return rowCount;
   },
 
   getInfoByIndexIntl: function getInfoByIndexIntl(aRowIdx, aInfo,
--- a/accessible/windows/msaa/Compatibility.cpp
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -1,22 +1,27 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Compatibility.h"
 
+#include "mozilla/WindowsVersion.h"
+#include "nsExceptionHandler.h"
+#include "nsUnicharUtils.h"
 #include "nsWindowsDllInterceptor.h"
 #include "nsWinUtils.h"
 #include "Statistics.h"
 
 #include "mozilla/Preferences.h"
 
+#include <shlobj.h>
+
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 /**
  * Return true if module version is lesser than the given version.
  */
 bool
 IsModuleVersionLessThan(HMODULE aModuleHandle, DWORD aMajor, DWORD aMinor)
@@ -198,8 +203,183 @@ Compatibility::Init()
     // SEH handlers.
     if (!sVectoredExceptionHandler) {
       sVectoredExceptionHandler =
         AddVectoredExceptionHandler(TRUE, &DetectInSendMessageExCompat);
     }
   }
 }
 
+#if !defined(HAVE_64BIT_BUILD)
+
+static bool
+ReadCOMRegDefaultString(const nsString& aRegPath, nsAString& aOutBuf)
+{
+  aOutBuf.Truncate();
+
+  // Get the required size and type of the registry value.
+  // We expect either REG_SZ or REG_EXPAND_SZ.
+  DWORD type;
+  DWORD bufLen = 0;
+  LONG result = ::RegGetValue(HKEY_CLASSES_ROOT, aRegPath.get(),
+                              nullptr, RRF_RT_ANY, &type, nullptr, &bufLen);
+  if (result != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ)) {
+    return false;
+  }
+
+  // Now obtain the value
+  DWORD flags = type == REG_SZ ? RRF_RT_REG_SZ : RRF_RT_REG_EXPAND_SZ;
+
+  aOutBuf.SetLength((bufLen + 1) / sizeof(char16_t));
+
+  result = ::RegGetValue(HKEY_CLASSES_ROOT, aRegPath.get(), nullptr,
+                         flags, nullptr, aOutBuf.BeginWriting(), &bufLen);
+  if (result != ERROR_SUCCESS) {
+    aOutBuf.Truncate();
+    return false;
+  }
+
+  // Truncate terminator
+  aOutBuf.Truncate((bufLen + 1) / sizeof(char16_t) - 1);
+  return true;
+}
+
+static bool
+IsSystemOleAcc(nsCOMPtr<nsIFile>& aFile)
+{
+  // Use FOLDERID_SystemX86 so that Windows doesn't give us a redirected
+  // system32 if we're a 32-bit process running on a 64-bit OS. This is
+  // necessary because the values that we are reading from the registry
+  // are not redirected; they reference SysWOW64 directly.
+  PWSTR systemPath = nullptr;
+  HRESULT hr = ::SHGetKnownFolderPath(FOLDERID_SystemX86, 0, nullptr,
+                                      &systemPath);
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  nsCOMPtr<nsIFile> oleAcc;
+  nsresult rv = NS_NewLocalFile(nsDependentString(systemPath), false,
+                                getter_AddRefs(oleAcc));
+
+  ::CoTaskMemFree(systemPath);
+  systemPath = nullptr;
+
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  rv = oleAcc->Append(NS_LITERAL_STRING("oleacc.dll"));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  bool isEqual;
+  rv = oleAcc->Equals(aFile, &isEqual);
+  return NS_SUCCEEDED(rv) && isEqual;
+}
+
+static bool
+IsTypelibPreferred()
+{
+  // If IAccessible's Proxy/Stub CLSID is kUniversalMarshalerClsid, then any
+  // external a11y clients are expecting to use a typelib.
+  NS_NAMED_LITERAL_STRING(kUniversalMarshalerClsid,
+      "{00020424-0000-0000-C000-000000000046}");
+
+  NS_NAMED_LITERAL_STRING(kIAccessiblePSClsidPath,
+      "Interface\\{618736E0-3C3D-11CF-810C-00AA00389B71}\\ProxyStubClsid32");
+
+  nsAutoString psClsid;
+  if (!ReadCOMRegDefaultString(kIAccessiblePSClsidPath, psClsid)) {
+    return false;
+  }
+
+  return psClsid.Equals(kUniversalMarshalerClsid,
+                        nsCaseInsensitiveStringComparator());
+}
+
+static bool
+IsIAccessibleTypelibRegistered()
+{
+  // The system default IAccessible typelib is always registered with version
+  // 1.1, under the neutral locale (LCID 0).
+  NS_NAMED_LITERAL_STRING(kIAccessibleTypelibRegPath,
+    "TypeLib\\{1EA4DBF0-3C3B-11CF-810C-00AA00389B71}\\1.1\\0\\win32");
+
+  nsAutoString typelibPath;
+  if (!ReadCOMRegDefaultString(kIAccessibleTypelibRegPath, typelibPath)) {
+    return false;
+  }
+
+  nsCOMPtr<nsIFile> libTestFile;
+  nsresult rv = NS_NewLocalFile(typelibPath, false, getter_AddRefs(libTestFile));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  return IsSystemOleAcc(libTestFile);
+}
+
+static bool
+IsIAccessiblePSRegistered()
+{
+  NS_NAMED_LITERAL_STRING(kIAccessiblePSRegPath,
+    "CLSID\\{03022430-ABC4-11D0-BDE2-00AA001A1953}\\InProcServer32");
+
+  nsAutoString proxyStubPath;
+  if (!ReadCOMRegDefaultString(kIAccessiblePSRegPath, proxyStubPath)) {
+    return false;
+  }
+
+  nsCOMPtr<nsIFile> libTestFile;
+  nsresult rv = NS_NewLocalFile(proxyStubPath, false, getter_AddRefs(libTestFile));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  return IsSystemOleAcc(libTestFile);
+}
+
+static bool
+UseIAccessibleProxyStub()
+{
+  // If a typelib is preferred then external clients are expecting to use
+  // typelib marshaling, so we should use that whenever available.
+  if (IsTypelibPreferred() && IsIAccessibleTypelibRegistered()) {
+    return false;
+  }
+
+  // Otherwise we try the proxy/stub
+  if (IsIAccessiblePSRegistered()) {
+    return true;
+  }
+
+  // If we reach this point then something is seriously wrong with the
+  // IAccessible configuration in the computer's registry. Let's annotate this
+  // so that we can easily determine this condition during crash analysis.
+  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IAccessibleConfig"),
+                                     NS_LITERAL_CSTRING("NoSystemTypeLibOrPS"));
+  return false;
+}
+
+#endif // !defined(HAVE_64BIT_BUILD)
+
+uint16_t
+Compatibility::GetActCtxResourceId()
+{
+#if defined(HAVE_64BIT_BUILD)
+  // The manifest for 64-bit Windows is embedded with resource ID 64.
+  return 64;
+#else
+  // The manifest for 32-bit Windows is embedded with resource ID 32.
+  // Beginning with Windows 10 Creators Update, 32-bit builds always use the
+  // 64-bit manifest. Older builds of Windows may or may not require the 64-bit
+  // manifest: UseIAccessibleProxyStub() determines the course of action.
+  if (mozilla::IsWin10CreatorsUpdateOrLater() ||
+      UseIAccessibleProxyStub()) {
+    return 64;
+  }
+
+  return 32;
+#endif // defined(HAVE_64BIT_BUILD)
+}
+
--- a/accessible/windows/msaa/Compatibility.h
+++ b/accessible/windows/msaa/Compatibility.h
@@ -34,16 +34,22 @@ public:
    */
   static bool IsWE() { return !!(sConsumers & WE); }
 
   /**
    * Return true if Dolphin mode is enabled.
    */
   static bool IsDolphin() { return !!(sConsumers & DOLPHIN); }
 
+  /**
+   * @return ID of a11y manifest resource to be passed to
+   * mscom::ActivationContext
+   */
+  static uint16_t GetActCtxResourceId();
+
 private:
   Compatibility();
   Compatibility(const Compatibility&);
   Compatibility& operator = (const Compatibility&);
 
   /**
    * Initialize compatibility mode. Called by platform (see Platform.h) during
    * accessibility initialization.
--- a/addon-sdk/source/lib/sdk/places/host/host-tags.js
+++ b/addon-sdk/source/lib/sdk/places/host/host-tags.js
@@ -32,28 +32,30 @@ const EVENT_MAP = {
 
 function tag (message) {
   let data = message.data;
   let resData = {
     id: message.id,
     event: message.event
   };
 
-  resData.data = taggingService.tagURI(newURI(data.url), data.tags);
+  if (data.tags && data.tags.length > 0)
+    resData.data = taggingService.tagURI(newURI(data.url), data.tags);
   respond(resData);
 }
 
 function untag (message) {
   let data = message.data;
   let resData = {
     id: message.id,
     event: message.event
   };
 
-  resData.data = taggingService.untagURI(newURI(data.url), data.tags);
+  if (!data.tags || data.tags.length > 0)
+    resData.data = taggingService.untagURI(newURI(data.url), data.tags);
   respond(resData);
 }
 
 function getURLsByTag (message) {
   let data = message.data;
   let resData = {
     id: message.id,
     event: message.event
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -108,20 +108,16 @@ DEFAULT_FENNEC_PREFS = {
 
 # When launching a temporary new Firefox profile, use these preferences.
 DEFAULT_FIREFOX_PREFS = {
     'browser.startup.homepage' : 'about:blank',
     'startup.homepage_welcome_url' : 'about:blank',
     'devtools.browsertoolbox.panel': 'jsdebugger',
     'devtools.chrome.enabled' : True,
 
-    # From:
-    # https://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388
-    # Make url-classifier updates so rare that they won't affect tests.
-    'urlclassifier.updateinterval' : 172800,
     # Point the url-classifier to a nonexistent local URL for fast failures.
     'browser.safebrowsing.downloads.remote.url': 'http://localhost/safebrowsing-dummy/downloads',
     'browser.safebrowsing.provider.google.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.google.updateURL' : 'http://localhost/safebrowsing-dummy/update',
     'browser.safebrowsing.provider.google4.gethashURL' : 'http://localhost/safebrowsing4-dummy/gethash',
     'browser.safebrowsing.provider.google4.updateURL' : 'http://localhost/safebrowsing4-dummy/update',
     'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
--- a/addon-sdk/source/test/preferences/firefox.json
+++ b/addon-sdk/source/test/preferences/firefox.json
@@ -1,13 +1,12 @@
 {
   "browser.startup.homepage": "about:blank",
   "startup.homepage_welcome_url": "about:blank",
   "devtools.browsertoolbox.panel": "jsdebugger",
   "devtools.chrome.enabled": true,
-  "urlclassifier.updateinterval": 172800,
   "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.safebrowsing.provider.google4.gethashURL": "http://localhost/safebrowsing4-dummy/gethash",
   "browser.safebrowsing.provider.google4.updateURL": "http://localhost/safebrowsing4-dummy/update",
   "browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update"
 }
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -294,16 +294,17 @@ pref("browser.urlbar.clickSelectsAll", t
 pref("browser.urlbar.doubleClickSelectsAll", true);
 #else
 pref("browser.urlbar.doubleClickSelectsAll", false);
 #endif
 
 // Control autoFill behavior
 pref("browser.urlbar.autoFill", true);
 pref("browser.urlbar.autoFill.typed", true);
+pref("browser.urlbar.speculativeConnect.enabled", true);
 
 // 0: Match anywhere (e.g., middle of words)
 // 1: Match on word boundaries and then try matching anywhere
 // 2: Match only on word boundaries (e.g., after / or .)
 // 3: Match at the beginning of the url or title
 pref("browser.urlbar.matchBehavior", 1);
 pref("browser.urlbar.filter.javascript", true);
 
@@ -1061,22 +1062,24 @@ pref("security.sandbox.gpu.level", 0);
 // This pref is discussed in bug 1083344, the naming is inspired from its
 // Windows counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox" (nightly only)
 // 1 -> "preliminary content sandboxing enabled: write access to
 //       home directory is prevented"
 // 2 -> "preliminary content sandboxing enabled with profile protection:
 //       write access to home directory is prevented, read and write access
 //       to ~/Library and profile directories are prevented (excluding
-//       $PROFILE/{extensions,weave})"
+//       $PROFILE/{extensions,chrome})"
+// 3 -> "no global read/write access, read access permitted to
+//       $PROFILE/{extensions,chrome}"
 // This setting is read when the content process is started. On Mac the content
 // process is killed when all windows are closed, so a change will take effect
 // when the 1st window is opened.
 #if defined(NIGHTLY_BUILD)
-pref("security.sandbox.content.level", 2);
+pref("security.sandbox.content.level", 3);
 #else
 pref("security.sandbox.content.level", 1);
 #endif
 #endif
 
 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is introduced as part of bug 742434, the naming is inspired from
 // its Windows/Mac counterpart, but on Linux it's an integer which means:
@@ -1136,18 +1139,16 @@ pref("browser.taskbar.previews.cachetime
 pref("browser.taskbar.lists.enabled", true);
 pref("browser.taskbar.lists.frequent.enabled", true);
 pref("browser.taskbar.lists.recent.enabled", false);
 pref("browser.taskbar.lists.maxListItemCount", 7);
 pref("browser.taskbar.lists.tasks.enabled", true);
 pref("browser.taskbar.lists.refreshInSeconds", 120);
 #endif
 
-// The sync engines to use.
-pref("services.sync.registerEngines", "Bookmarks,Form,History,Password,Prefs,Tab,Addons,ExtensionStorage");
 // Preferences to be synced by default
 pref("services.sync.prefs.sync.accessibility.blockautorefresh", true);
 pref("services.sync.prefs.sync.accessibility.browsewithcaret", true);
 pref("services.sync.prefs.sync.accessibility.typeaheadfind", true);
 pref("services.sync.prefs.sync.accessibility.typeaheadfind.linksonly", true);
 pref("services.sync.prefs.sync.addons.ignoreUserEnabledChanges", true);
 // The addons prefs related to repository verification are intentionally
 // not synced for security reasons. If a system is compromised, a user
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -172,21 +172,23 @@
         // defined, get the generic message
         var errTitle = document.getElementById("et_" + err);
         var errDesc  = document.getElementById("ed_" + err);
         if (!errTitle || !errDesc) {
           errTitle = document.getElementById("et_generic");
           errDesc  = document.getElementById("ed_generic");
         }
 
+        // eslint-disable-next-line no-unsanitized/property
         document.querySelector(".title-text").innerHTML = errTitle.innerHTML;
 
         var sd = document.getElementById("errorShortDescText");
         if (sd) {
           if (gIsCertError) {
+          // eslint-disable-next-line no-unsanitized/property
             sd.innerHTML = errDesc.innerHTML;
           } else {
             sd.textContent = getDescription();
           }
         }
         if (showCaptivePortalUI) {
           initPageCaptivePortal();
           return;
@@ -196,16 +198,17 @@
           return;
         }
         addAutofocus("errorTryAgain");
 
         document.body.className = "neterror";
 
         var ld = document.getElementById("errorLongDesc");
         if (ld) {
+        // eslint-disable-next-line no-unsanitized/property
           ld.innerHTML = errDesc.innerHTML;
         }
 
         if (err == "sslv3Used") {
           document.getElementById("learnMoreContainer").style.display = "block";
           let learnMoreLink = document.getElementById("learnMoreLink");
           learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-sslv3-error-messages-firefox";
           document.body.className = "certerror";
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -329,16 +329,17 @@ function showSnippets() {
   }
   _snippetsShown = true;
 
   let snippets = gSnippetsMap.get("snippets");
   // If there are remotely fetched snippets, try to to show them.
   if (snippets) {
     // Injecting snippets can throw if they're invalid XML.
     try {
+      // eslint-disable-next-line no-unsanitized/property
       snippetsElt.innerHTML = snippets;
       // Scripts injected by innerHTML are inactive, so we have to relocate them
       // through DOM manipulation to activate their contents.
       Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) {
         let relocatedScript = document.createElement("script");
         relocatedScript.type = "text/javascript";
         relocatedScript.text = elt.text;
         elt.parentNode.replaceChild(relocatedScript, elt);
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -119,16 +119,17 @@ var gEMEHandler = {
       });
     }
 
     let iconURL = "chrome://browser/skin/drm-icon.svg#chains-black";
 
     // Do a little dance to get rich content into the notification:
     let fragment = document.createDocumentFragment();
     let descriptionContainer = document.createElement("description");
+    // eslint-disable-next-line no-unsanitized/property
     descriptionContainer.innerHTML = message;
     while (descriptionContainer.childNodes.length) {
       fragment.appendChild(descriptionContainer.childNodes[0]);
     }
 
     box.appendNotification(fragment, notificationId, iconURL, box.PRIORITY_WARNING_MEDIUM,
                            buttons);
   },
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1307,18 +1307,17 @@ var gBrowserInit = {
     SidebarUI.init();
 
     // Certain kinds of automigration rely on this notification to complete
     // their tasks BEFORE the browser window is shown. SessionStore uses it to
     // restore tabs into windows AFTER important parts like gMultiProcessBrowser
     // have been initialized.
     Services.obs.notifyObservers(window, "browser-window-before-show");
 
-    gUIDensity.update();
-    gPrefService.addObserver(gUIDensity.prefDomain, gUIDensity);
+    gUIDensity.init();
 
     let isResistFingerprintingEnabled = gPrefService.getBoolPref("privacy.resistFingerprinting");
 
     // Set a sane starting width/height for all resolutions on new profiles.
     if (isResistFingerprintingEnabled) {
       // When the fingerprinting resistance is enabled, making sure that we don't
       // have a maximum window to interfere with generating rounded window dimensions.
       document.documentElement.setAttribute("sizemode", "normal");
@@ -1770,17 +1769,17 @@ var gBrowserInit = {
     FullScreen.uninit();
 
     gSync.uninit();
 
     gExtensionsNotifications.uninit();
 
     Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
-    gPrefService.removeObserver(gUIDensity.prefDomain, gUIDensity);
+    gUIDensity.uninit();
 
     try {
       gBrowser.removeProgressListener(window.XULBrowserWindow);
       gBrowser.removeTabsProgressListener(window.TabsProgressListener);
     } catch (ex) {
     }
 
     PlacesToolbarHelper.uninit();
@@ -5118,17 +5117,17 @@ nsBrowserAccess.prototype = {
     let browser = win.gBrowser.getBrowserForTab(tab);
 
     if (needToFocusWin || (!loadInBackground && aIsExternal))
       win.focus();
 
     return browser;
   },
 
-  openURI(aURI, aOpener, aWhere, aFlags) {
+  openURI(aURI, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
     // This function should only ever be called if we're opening a URI
     // from a non-remote browser window (via nsContentTreeOwner).
     if (aOpener && Cu.isCrossProcessWrapper(aOpener)) {
       Cu.reportError("nsBrowserAccess.openURI was passed a CPOW for aOpener. " +
                      "openURI should only ever be called from non-remote browsers.");
       throw Cr.NS_ERROR_FAILURE;
     }
 
@@ -5150,21 +5149,19 @@ nsBrowserAccess.prototype = {
       if (isExternal &&
           gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
         aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
       else
         aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
     }
 
     let referrer = aOpener ? makeURI(aOpener.location.href) : null;
-    let triggeringPrincipal = null;
     let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_UNSET;
     if (aOpener && aOpener.document) {
       referrerPolicy = aOpener.document.referrerPolicy;
-      triggeringPrincipal = aOpener.document.nodePrincipal;
     }
     let isPrivate = aOpener
                   ? PrivateBrowsingUtils.isContentWindowPrivate(aOpener)
                   : PrivateBrowsingUtils.isWindowPrivate(window);
 
     switch (aWhere) {
       case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
         // FIXME: Bug 408379. So how come this doesn't send the
@@ -5188,28 +5185,28 @@ nsBrowserAccess.prototype = {
         let forceNotRemote = !!aOpener;
         let userContextId = aOpener && aOpener.document
                               ? aOpener.document.nodePrincipal.originAttributes.userContextId
                               : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
         let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
         let browser = this._openURIInNewTab(aURI, referrer, referrerPolicy,
                                             isPrivate, isExternal,
                                             forceNotRemote, userContextId,
-                                            openerWindow, triggeringPrincipal);
+                                            openerWindow, aTriggeringPrincipal);
         if (browser)
           newWindow = browser.contentWindow;
         break;
       default : // OPEN_CURRENTWINDOW or an illegal value
         newWindow = window.content;
         if (aURI) {
           let loadflags = isExternal ?
                             Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
                             Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
           gBrowser.loadURIWithFlags(aURI.spec, {
-                                    triggeringPrincipal,
+                                    aTriggeringPrincipal,
                                     flags: loadflags,
                                     referrerURI: referrer,
                                     referrerPolicy,
                                     });
         }
         if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
           window.focus();
     }
@@ -5451,36 +5448,60 @@ var gTabletModePageCounter = {
 };
 
 function displaySecurityInfo() {
   BrowserPageInfo(null, "securityTab");
 }
 
 // Updates the UI density (for touch and compact mode) based on the uidensity pref.
 var gUIDensity = {
+  MODE_NORMAL: 0,
   MODE_COMPACT: 1,
   MODE_TOUCH: 2,
-  prefDomain: "browser.uidensity",
+  uiDensityPref: "browser.uidensity",
+  autoTouchModePref: "browser.touchmode.auto",
+
+  init() {
+    this.update();
+    gPrefService.addObserver(this.uiDensityPref, this);
+    gPrefService.addObserver(this.autoTouchModePref, this);
+  },
+
+  uninit() {
+    gPrefService.removeObserver(this.uiDensityPref, this);
+    gPrefService.removeObserver(this.autoTouchModePref, this);
+  },
 
   observe(aSubject, aTopic, aPrefName) {
-    if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
+    if (aTopic != "nsPref:changed" ||
+        (aPrefName != this.uiDensityPref &&
+         aPrefName != this.autoTouchModePref)) {
       return;
+    }
 
     this.update();
   },
 
-  update() {
-    let mode;
+  getCurrentDensity() {
     // Automatically override the uidensity to touch in Windows tablet mode.
     if (AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
         WindowsUIUtils.inTabletMode &&
-        gPrefService.getBoolPref("browser.touchmode.auto")) {
-      mode = this.MODE_TOUCH;
-    } else {
-      mode = gPrefService.getIntPref(this.prefDomain);
+        gPrefService.getBoolPref(this.autoTouchModePref)) {
+      return { mode: this.MODE_TOUCH, overridden: true };
+    }
+    return { mode: gPrefService.getIntPref(this.uiDensityPref), overridden: false };
+  },
+
+  setCurrentMode(mode) {
+    gPrefService.setIntPref(this.uiDensityPref, mode);
+  },
+
+  update(mode) {
+    if (mode == null) {
+      mode = this.getCurrentDensity().mode;
     }
 
     let doc = document.documentElement;
     switch (mode) {
     case this.MODE_COMPACT:
       doc.setAttribute("uidensity", "compact");
       break;
     case this.MODE_TOUCH:
@@ -8171,47 +8192,47 @@ function switchToTabHavingURI(aURI, aOpe
   return false;
 }
 
 var RestoreLastSessionObserver = {
   init() {
     if (SessionStore.canRestoreLastSession &&
         !PrivateBrowsingUtils.isWindowPrivate(window)) {
       if (Services.prefs.getBoolPref("browser.tabs.restorebutton")) {
-        let {restoreTabsButton} = gBrowser.tabContainer;
+        let {restoreTabsButton, restoreTabsButtonWrapperWidth} = gBrowser.tabContainer;
         let restoreTabsButtonWrapper = restoreTabsButton.parentNode;
         restoreTabsButtonWrapper.setAttribute("session-exists", "true");
         gBrowser.tabContainer.updateSessionRestoreVisibility();
+        restoreTabsButton.style.maxWidth = `${restoreTabsButtonWrapperWidth}px`;
         gBrowser.tabContainer.addEventListener("TabOpen", this);
       }
       Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
       goSetCommandEnabled("Browser:RestoreLastSession", true);
     }
   },
 
   handleEvent(event) {
     switch (event.type) {
      case "TabOpen":
         this.removeRestoreButton();
         break;
     }
   },
 
   removeRestoreButton() {
-    let {restoreTabsButton, restoreTabsButtonWrapperWidth} = gBrowser.tabContainer;
+    let {restoreTabsButton} = gBrowser.tabContainer;
     let restoreTabsButtonWrapper = restoreTabsButton.parentNode;
-    restoreTabsButtonWrapper.removeAttribute("session-exists");
     gBrowser.tabContainer.addEventListener("transitionend", function maxWidthTransitionHandler(e) {
-      if (e.propertyName == "max-width") {
+      if (e.target == gBrowser.tabContainer && e.propertyName == "max-width") {
         gBrowser.tabContainer.updateSessionRestoreVisibility();
         gBrowser.tabContainer.removeEventListener("transitionend", maxWidthTransitionHandler);
       }
     });
-    restoreTabsButton.style.maxWidth = `${restoreTabsButtonWrapperWidth}px`;
-    requestAnimationFrame(() => restoreTabsButton.style.maxWidth = 0);
+    restoreTabsButtonWrapper.removeAttribute("session-exists");
+    restoreTabsButton.style.maxWidth = 0;
     gBrowser.tabContainer.removeEventListener("TabOpen", this);
   },
 
   observe() {
     // The last session can only be restored once so there's
     // no way we need to re-enable our menu item.
     Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
     goSetCommandEnabled("Browser:RestoreLastSession", false);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -471,17 +471,17 @@
                            label="&copyURLCmd.label;"
                            command="PageAction:copyURL"/>
             <toolbarbutton id="page-action-email-link-button"
                            class="subviewbutton subviewbutton-iconic"
                            label="&emailPageCmd.label;"
                            command="PageAction:emailLink"/>
             <toolbarbutton id="page-action-send-to-device-button"
                            class="subviewbutton subviewbutton-iconic subviewbutton-nav"
-                           label="&sendToDevice.label;"
+                           label="&sendToDevice.label2;"
                            closemenu="none"
                            oncommand="gPageActionButton.showSendToDeviceView(this);"/>
           </vbox>
         </panelview>
         <panelview id="page-action-sendToDeviceView"
                    class="PanelUI-subView"
                    title="&sendToDevice.viewTitle;">
           <vbox id="page-action-sendToDeviceView-body" class="panel-subview-body">
@@ -845,18 +845,18 @@
                          tooltiptext="&urlbar.microphoneBlocked.tooltip;"/>
                   <image data-permission-id="screen" class="blocked-permission-icon screen-icon" role="button"
                          tooltiptext="&urlbar.screenBlocked.tooltip;"/>
                   <image data-permission-id="persistent-storage" class="blocked-permission-icon persistent-storage-icon" role="button"
                          tooltiptext="&urlbar.persistentStorageBlocked.tooltip;"/>
                 </box>
                 <box id="notification-popup-box"
                      hidden="true"
-                     onmouseover="document.getElementById('identity-icon').classList.add('no-hover');"
-                     onmouseout="document.getElementById('identity-icon').classList.remove('no-hover');"
+                     onmouseover="document.getElementById('identity-box').classList.add('no-hover');"
+                     onmouseout="document.getElementById('identity-box').classList.remove('no-hover');"
                      align="center">
                   <image id="default-notification-icon" class="notification-anchor-icon" role="button"
                          tooltiptext="&urlbar.defaultNotificationAnchor.tooltip;"/>
                   <image id="geo-notification-icon" class="notification-anchor-icon geo-icon" role="button"
                          tooltiptext="&urlbar.geolocationNotificationAnchor.tooltip;"/>
                   <image id="addons-notification-icon" class="notification-anchor-icon install-icon" role="button"
                          tooltiptext="&urlbar.addonsNotificationAnchor.tooltip;"/>
                   <image id="indexedDB-notification-icon" class="notification-anchor-icon indexedDB-icon" role="button"
@@ -1083,17 +1083,21 @@
                        onclick="BrowserGoHome(event);"
                        cui-areatype="toolbar"
                        aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
       </hbox>
 
       <toolbarbutton id="nav-bar-overflow-button"
                      class="toolbarbutton-1 chromeclass-toolbar-additional overflow-button"
                      skipintoolbarset="true"
-                     tooltiptext="&navbarOverflow.label;"/>
+                     tooltiptext="&navbarOverflow.label;">
+        <box class="toolbarbutton-animatable-box">
+          <image class="toolbarbutton-animatable-image"/>
+        </box>
+      </toolbarbutton>
 
       <toolbaritem id="PanelUI-button"
                    class="chromeclass-toolbar-additional"
                    removable="false">
         <toolbarbutton id="PanelUI-menu-button"
                        class="toolbarbutton-1 badged-button"
                        consumeanchor="PanelUI-button"
                        label="&brandShortName;"
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -615,16 +615,17 @@ ContentSearchUIController.prototype = {
   },
 
   _updateSearchWithHeader() {
     if (!this._strings) {
       return;
     }
     let searchWithHeader = document.getElementById("contentSearchSearchWithHeader");
     if (this.input.value) {
+      // eslint-disable-next-line no-unsanitized/property
       searchWithHeader.innerHTML = this._strings.searchForSomethingWith;
       searchWithHeader.querySelector(".contentSearchSearchWithHeaderSearchText").textContent = this.input.value;
     } else {
       searchWithHeader.textContent = this._strings.searchWithHeader;
     }
   },
 
   _speculativeConnect() {
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -111,17 +111,17 @@ nsContextMenu.prototype = {
         onCanvas: this.onCanvas,
         onEditableArea: this.onEditableArea,
         onPassword: this.onPassword,
         srcUrl: this.mediaURL,
         frameUrl: gContextMenuContentData ? gContextMenuContentData.docLocation : undefined,
         pageUrl: this.browser ? this.browser.currentURI.spec : undefined,
         linkText: this.linkTextStr,
         linkUrl: this.linkURL,
-        selectionText: this.isTextSelected ? this.selectionInfo.text : undefined,
+        selectionText: this.isTextSelected ? this.selectionInfo.fullText : undefined,
         frameId: this.frameOuterWindowID,
       };
       subject.wrappedJSObject = subject;
       Services.obs.notifyObservers(subject, "on-build-contextmenu");
     }
 
     this.isFrameImage = document.getElementById("isFrameImage");
     this.ellipsis = "\u2026";
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2155,17 +2155,17 @@
         "canGoBack", "canGoForward", "goBack", "goForward", "permitUnload",
         "reload", "reloadWithFlags", "stop", "loadURI", "loadURIWithFlags",
         "goHome", "homePage", "gotoIndex", "currentURI", "documentURI",
         "preferences", "imageDocument", "isRemoteBrowser", "messageManager",
         "getTabBrowser", "finder", "fastFind", "sessionHistory", "contentTitle",
         "characterSet", "fullZoom", "textZoom", "webProgress",
         "addProgressListener", "removeProgressListener", "audioPlaybackStarted",
         "audioPlaybackStopped", "pauseMedia", "stopMedia",
-        "blockMedia", "resumeMedia", "mute", "unmute", "blockedPopups", "lastURI",
+        "resumeMedia", "mute", "unmute", "blockedPopups", "lastURI",
         "purgeSessionHistory", "stopScroll", "startScroll",
         "userTypedValue", "userTypedClear", "mediaBlocked"
       ]</field>
 
       <method name="_createLazyBrowser">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
@@ -2221,17 +2221,16 @@
                       // initializing the reload.
                       aTab.addEventListener("SSTabRestoring", () => {
                         browser[name](params);
                       }, { once: true });
                       gBrowser._insertBrowser(aTab);
                     };
                   };
                   break;
-                case "blockMedia":
                 case "resumeMedia":
                   getter = () => {
                     return () => {
                       // No need to insert a browser, so we just call the browser's
                       // method.
                       aTab.addEventListener("SSTabRestoring", () => {
                         browser[name]();
                       }, { once: true });
--- a/browser/base/content/test/general/browser_bookmark_popup.js
+++ b/browser/base/content/test/general/browser_bookmark_popup.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 /**
  * Test opening and closing the bookmarks panel.
  */
 
 let bookmarkPanel = document.getElementById("editBookmarkPanel");
-let bookmarkStar = BookmarkingUI.star;
+let bookmarkStar = AppConstants.MOZ_PHOTON_THEME ? BookmarkingUI.star : BookmarkingUI.button;
 let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle");
 let editBookmarkPanelRemoveButtonRect;
 
 StarUI._closePanelQuickForTesting = true;
 
 async function test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
                                 shouldAutoClose, popupHideFn, isBookmarkRemoved}) {
   await BrowserTestUtils.withNewTab({gBrowser, url: "about:home"}, async function(browser) {
--- a/browser/base/content/test/general/browser_bug520538.js
+++ b/browser/base/content/test/general/browser_bug520538.js
@@ -1,15 +1,16 @@
 function test() {
   var tabCount = gBrowser.tabs.length;
   gBrowser.selectedBrowser.focus();
   window.browserDOMWindow.openURI(makeURI("about:blank"),
                                   null,
                                   Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
-                                  Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+                                  Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
+                                  Services.scriptSecurityManager.getSystemPrincipal());
   is(gBrowser.tabs.length, tabCount + 1,
      "'--new-tab about:blank' opens a new tab");
   is(gBrowser.selectedTab, gBrowser.tabs[tabCount],
      "'--new-tab about:blank' selects the new tab");
   is(document.activeElement, gURLBar.inputField,
      "'--new-tab about:blank' focuses the location bar");
   gBrowser.removeCurrentTab();
 }
--- a/browser/base/content/test/general/browser_bug537474.js
+++ b/browser/base/content/test/general/browser_bug537474.js
@@ -1,7 +1,8 @@
 add_task(async function() {
   let browserLoadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   window.browserDOMWindow.openURI(makeURI("about:"), null,
-                                  Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null)
+                                  Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null,
+                                  Services.scriptSecurityManager.getSystemPrincipal())
   await browserLoadedPromise;
   is(gBrowser.currentURI.spec, "about:", "page loads in the current content window");
 });
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
 [browser_appmenu_reflows.js]
 [browser_startup.js]
+[browser_startup_content.js]
+skip-if = !e10s
 [browser_startup_images.js]
 skip-if = !debug
 [browser_tabclose_grow_reflows.js]
 [browser_tabclose_reflows.js]
 [browser_tabopen_reflows.js]
 [browser_tabopen_squeeze_reflows.js]
 [browser_tabswitch_reflows.js]
 [browser_toolbariconcolor_restyles.js]
--- a/browser/base/content/test/performance/browser_appmenu_reflows.js
+++ b/browser/base/content/test/performance/browser_appmenu_reflows.js
@@ -71,17 +71,42 @@ const EXPECTED_APPMENU_OPEN_REFLOWS = [
   [
     "handleEvent@resource:///modules/PanelMultiView.jsm",
     "openPopup@chrome://global/content/bindings/popup.xml",
   ],
 ];
 
 const EXPECTED_APPMENU_SUBVIEW_REFLOWS = [
   /**
-   * Nothing here! Please don't add anything new!
+   * The synced tabs view has labels that are multiline. Because of bugs in
+   * XUL layout relating to multiline text in scrollable containers, we need
+   * to manually read their height in order to ensure container heights are
+   * correct. Unfortunately this requires 2 sync reflows.
+   *
+   * If we add more views where this is necessary, we may need to duplicate
+   * these expected reflows further.
+   *
+   * Because the test dirties the frame tree by manipulating margins,
+   * getBoundingClientRect() in the descriptionHeightWorkaround code
+   * seems to sometimes fire multiple times. Bug 1363361 will change how the
+   * test dirties the frametree, after which this (2 hits in that method)
+   * should become deterministic and we can re-enable the subview testing
+   * for the remotetabs subview (this is bug 1376822). In the meantime,
+   * that subview only is excluded from this test.
+  [
+    "descriptionHeightWorkaround@resource:///modules/PanelMultiView.jsm",
+    "onTransitionEnd@resource:///modules/PanelMultiView.jsm",
+  ],
+  [
+    "descriptionHeightWorkaround@resource:///modules/PanelMultiView.jsm",
+    "onTransitionEnd@resource:///modules/PanelMultiView.jsm",
+  ],
+   */
+  /**
+   * Please don't add anything new!
    */
 ];
 
 add_task(async function() {
   await ensureNoPreloadedBrowser();
 
   await SpecialPowers.pushPrefEnv({
     set: [["browser.photon.structure.enabled", true]],
@@ -105,18 +130,27 @@ add_task(async function() {
     // exhausted, we go back up a level.
     async function openSubViewsRecursively(currentView) {
       let navButtons = Array.from(currentView.querySelectorAll(".subviewbutton-nav"));
       if (!navButtons) {
         return;
       }
 
       for (let button of navButtons) {
+        // We skip the remote tabs subview, see the comments above
+        // in EXPECTED_APPMENU_SUBVIEW_REFLOWS. bug 1376822 tracks
+        // re-enabling this.
+        if (button.id == "appMenu-library-remotetabs-button") {
+          info("Skipping " + button.id);
+          continue;
+        }
+        info("Click " + button.id);
         button.click();
         await BrowserTestUtils.waitForEvent(PanelUI.panel, "ViewShown");
+        info("Shown " + PanelUI.multiView.instance._currentSubView.id);
         // Unfortunately, I can't find a better accessor to the current
         // subview, so I have to reach the PanelMultiView instance
         // here.
         await openSubViewsRecursively(PanelUI.multiView.instance._currentSubView);
         PanelUI.multiView.goBack();
         await BrowserTestUtils.waitForEvent(PanelUI.panel, "ViewShown");
       }
     }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This test records which services, JS components and JS modules are loaded
+ * when creating a new content process.
+ *
+ * If you made changes that cause this test to fail, it's likely because you
+ * are loading more JS code during content process startup.
+ *
+ * If your code isn't strictly required to show a page, consider loading it
+ * lazily. If you can't, consider delaying its load until after we have started
+ * handling user events.
+ */
+
+"use strict";
+
+const blacklist = {
+  components: new Set([
+    "PushComponents.js",
+    "TelemetryStartup.js",
+  ]),
+  modules: new Set([
+    "resource:///modules/ContentWebRTC.jsm",
+    "resource://gre/modules/InlineSpellChecker.jsm",
+    "resource://gre/modules/InlineSpellCheckerContent.jsm",
+    "resource://gre/modules/Promise.jsm",
+    "resource://gre/modules/Task.jsm",
+    "resource://gre/modules/debug.js",
+    "resource://gre/modules/osfile.jsm",
+  ]),
+  services: new Set([
+    "@mozilla.org/base/telemetry-startup;1",
+    "@mozilla.org/push/Service;1",
+  ])
+};
+
+add_task(async function() {
+  SimpleTest.requestCompleteLog();
+
+  let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
+                                                         forceNewProcess: true});
+
+  let mm = gBrowser.selectedBrowser.messageManager;
+  let promise = BrowserTestUtils.waitForMessage(mm, "Test:LoadedScripts");
+
+  // Load a custom frame script to avoid using ContentTask which loads Task.jsm
+  mm.loadFrameScript("data:text/javascript,(" + function() {
+    /* eslint-env mozilla/frame-script */
+    const {classes: Cc, interfaces: Ci, manager: Cm} = Components;
+    Cm.QueryInterface(Ci.nsIServiceManager);
+
+    let loader = Cc["@mozilla.org/moz/jsloader;1"].getService(Ci.xpcIJSModuleLoader);
+    sendAsyncMessage("Test:LoadedScripts", {
+      /* Keep only the file name for components, as the path is an absolute file
+         URL rather than a resource:// URL like for modules. */
+      components: loader.loadedComponents().map(f => f.replace(/.*\//, "")),
+      modules: loader.loadedModules(),
+      services: Object.keys(Cc).filter(c => {
+        try {
+          Cm.isServiceInstantiatedByContractID(c, Ci.nsISupports);
+          return true;
+        } catch (e) {
+          return false;
+        }
+      })
+    });
+  } + ")()", false);
+
+  let loadedList = await promise;
+  for (let scriptType in blacklist) {
+    info(scriptType);
+    for (let file of blacklist[scriptType]) {
+      ok(!loadedList[scriptType].includes(file), `${file} is not allowed`);
+    }
+    for (let file of loadedList[scriptType]) {
+      info(file);
+    }
+  }
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/base/content/test/siteIdentity/browser_bug906190.js
+++ b/browser/base/content/test/siteIdentity/browser_bug906190.js
@@ -35,16 +35,17 @@ async function doTest(parentTabSpec, chi
 
     // Wait for the script in the page to update the contents of the test div.
     let testDiv = content.document.getElementById("mctestdiv");
     await BrowserTestUtils.waitForCondition(
       () => testDiv.innerHTML == "Mixed Content Blocker disabled");
 
     // Add the link for the child tab to the page.
     let mainDiv = content.document.createElement("div");
+    // eslint-disable-next-line no-unsanitized/property
     mainDiv.innerHTML =
       '<p><a id="linkToOpenInNewTab" href="' + childTabSpec + '">Link</a></p>';
     content.document.body.appendChild(mainDiv);
 
     // Execute the test in the child tabs with the two methods to open it.
     for (let openFn of [simulateCtrlClick, simulateContextMenuOpenInTab]) {
       let promiseTabLoaded = waitForSomeTabToLoad();
       openFn(browser);
--- a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.html
+++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font.html
@@ -30,16 +30,17 @@
      !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
    is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
 
    var blockedMixedDisplay = ui &&
      !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
    is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
 
    var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http font";
+   // eslint-disable-next-line no-unsanitized/property
    document.getElementById("testDiv").innerHTML = newValue;
   }
 </script>
 </head>
 <body onload="checkLoadStates()">
   <div class="testDiv" id="testDiv">
     Testing MCB does not trigger warning/error for an http page with https css that includes http font
   </div>
--- a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.html
+++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_font2.html
@@ -31,16 +31,17 @@
    is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
 
    var blockedMixedDisplay = ui &&
      !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
    is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
 
    var newValue = "Verifying MCB does not trigger warning/error for an http page ";
     newValue += "with https css that imports another http css which includes http font";
+   // eslint-disable-next-line no-unsanitized/property
    document.getElementById("testDiv").innerHTML = newValue;
   }
 </script>
 </head>
 <body onload="checkLoadStates()">
   <div class="testDiv" id="testDiv">
     Testing MCB does not trigger warning/error for an http page with https css that imports another http css which includes http font
   </div>
--- a/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.html
+++ b/browser/base/content/test/siteIdentity/test_no_mcb_on_http_site_img.html
@@ -30,16 +30,17 @@
      !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
    is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
 
    var blockedMixedDisplay = ui &&
      !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
    is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
 
    var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http image";
+   // eslint-disable-next-line no-unsanitized/property
    document.getElementById("testDiv").innerHTML = newValue;
   }
 </script>
 </head>
 <body onload="checkLoadStates()">
   <div class="testDiv" id="testDiv">
     Testing MCB does not trigger warning/error for an http page with https css that includes http image
   </div>
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -105,16 +105,17 @@ subsuite = clipboard
 [browser_urlbar_canonize_on_autofill.js]
 [browser_urlbar_blanking.js]
 support-files =
   file_blank_but_not_blank.html
 [browser_urlbar_locationchange_urlbar_edit_dos.js]
 support-files =
   file_urlbar_edit_dos.html
 [browser_urlbar_searchsettings.js]
+[browser_urlbar_search_speculative_connect.js]
 [browser_urlbar_stop_pending.js]
 support-files =
   slow-page.sjs
 [browser_urlbar_remoteness_switch.js]
 run-if = e10s
 [browser_urlHighlight.js]
 [browser_wyciwyg_urlbarCopying.js]
 subsuite = clipboard
--- a/browser/base/content/test/urlbar/browser_bug562649.js
+++ b/browser/base/content/test/urlbar/browser_bug562649.js
@@ -1,14 +1,15 @@
 function test() {
   const URI = "data:text/plain,bug562649";
   window.browserDOMWindow.openURI(makeURI(URI),
                                   null,
                                   Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
-                                  Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+                                  Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
+                                  Services.scriptSecurityManager.getSystemPrincipal());
 
   is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI");
   is(gURLBar.value, URI, "location bar value matches test URI");
 
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   gBrowser.removeCurrentTab({ skipPermitUnload: true });
   is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs");
   is(gURLBar.value, URI, "location bar value matches test URI after switching tabs");
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-out.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-out.js
@@ -55,16 +55,18 @@ add_task(async function focus() {
   assertFooterVisible(true);
 
   // Check the Change Options link.
   let changeOptionsLink = document.getElementById("search-suggestions-change-settings");
   let prefsPromise = BrowserTestUtils.waitForLocationChange(gBrowser, "about:preferences#general-search");
   changeOptionsLink.click();
   await prefsPromise;
   Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+  // The preferences page does fancy stuff with focus, ensure to unload it.
+  await BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:blank");
 });
 
 add_task(async function new_tab() {
   // Opening a new tab when the urlbar is unfocused, should focusing it and thus
   // open the popup in order to show the notification.
   setupVisibleHint();
   gURLBar.blur();
   let popupPromise = promisePopupShown(gURLBar.popup);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
@@ -0,0 +1,82 @@
+"use strict";
+
+// This test ensures that we setup a speculative network
+// connection for autoFilled values.
+
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+let gHttpServer = null;
+let gScheme = "http";
+let gHost = "localhost"; // 'localhost' by default.
+let gPort = -1;
+
+add_task(async function setup() {
+  if (!gHttpServer) {
+    gHttpServer = new HttpServer();
+    try {
+      gHttpServer.start(gPort);
+      gPort = gHttpServer.identity.primaryPort;
+      gHttpServer.identity.setPrimary(gScheme, gHost, gPort);
+    } catch (ex) {
+      info("We can't launch our http server successfully.")
+    }
+  }
+  is(gHttpServer.identity.has(gScheme, gHost, gPort), true, "make sure we have this domain listed");
+
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.autoFill", true],
+          ["browser.urlbar.speculativeConnection.enabled", true],
+          // In mochitest this number is 0 by default but we have to turn it on.
+          ["network.http.speculative-parallel-limit", 6],
+          // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
+          // networking problem.
+          ["network.dns.disableIPv6", true]],
+  });
+
+  await PlacesTestUtils.addVisits([{
+    uri: `${gScheme}://${gHost}:${gPort}`,
+    title: "test visit for speculative connection",
+    transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+  }]);
+
+  // Bug 764062 - we can't get port number from autocomplete result, so we have to mock
+  // this function to add it manually.
+  let oldSpeculativeConnect = gURLBar.popup.maybeSetupSpeculativeConnect.bind(gURLBar.popup);
+  gURLBar.popup.maybeSetupSpeculativeConnect = (uriString) => {
+    info(`Original uri is ${uriString}`);
+    let newUriString = uriString.substr(0, uriString.length - 1) +
+                       ":" + gPort + "/";
+    info(`New uri is ${newUriString}`);
+    oldSpeculativeConnect(newUriString);
+  };
+
+  registerCleanupFunction(async function() {
+    await PlacesTestUtils.clearHistory();
+    gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
+    gHttpServer.identity.remove(gScheme, gHost, gPort);
+    gHttpServer.stop(() => {
+      gHttpServer = null;
+    });
+  });
+});
+
+add_task(async function autofill_tests() {
+  const test = {
+    search: gHost.substr(0, 2),
+    autofilledValue: `${gHost}/`
+  };
+
+  info(`Searching for '${test.search}'`);
+  await promiseAutocompleteResultPopup(test.search, window, true);
+  is(gURLBar.inputField.value, test.autofilledValue,
+     `Autofilled value is as expected for search '${test.search}'`);
+
+  await BrowserTestUtils.waitForCondition(() => {
+    if (gHttpServer) {
+      is(gHttpServer.connectionNumber, 1,
+         `${gHttpServer.connectionNumber} speculative connection has been setup.`)
+      return gHttpServer.connectionNumber == 1;
+    }
+    return false;
+  }, "Waiting for connection setup");
+});
+
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
@@ -86,16 +86,33 @@ var gTests = [
     });
     await expectObserverCalled("getUserMedia:response:allow");
     await expectObserverCalled("recording-device-events");
     Assert.deepEqual((await getMediaCaptureState()), {screen: "Screen"},
                      "expected screen to be shared");
 
     await indicator;
     await checkSharingUI({screen: "Screen"});
+
+    // we always show prompt for screen sharing.
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    await promiseRequestDevice(false, true, null, "screen");
+    await promise;
+    await expectObserverCalled("getUserMedia:request");
+
+    is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+       "webRTC-shareScreen-notification-icon", "anchored to device icon");
+    checkDeviceSelectors(false, false, true);
+
+    await promiseMessage(permissionError, () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    await expectObserverCalled("getUserMedia:response:deny");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
     await closeStream();
   }
 },
 
 {
   desc: "getUserMedia window only",
   run: async function checkWindowOnly() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
--- a/browser/base/content/test/webrtc/get_user_media.html
+++ b/browser/base/content/test/webrtc/get_user_media.html
@@ -13,16 +13,17 @@ try {
   dump("audio: " + audioDevice + "\nvideo: " + videoDevice + "\n");
   useFakeStreams = false;
 } catch (e) {
   dump("TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n");
   useFakeStreams = true;
 }
 
 function message(m) {
+  // eslint-disable-next-line no-unsanitized/property
   document.getElementById("message").innerHTML = m;
   window.parent.postMessage(m, "*");
 }
 
 var gStreams = [];
 
 function requestDevice(aAudio, aVideo, aShare, aBadDevice = false) {
   var opts = {video: aVideo, audio: aAudio};
--- a/browser/base/content/test/webrtc/get_user_media_in_frame.html
+++ b/browser/base/content/test/webrtc/get_user_media_in_frame.html
@@ -13,16 +13,17 @@ try {
   dump("audio: " + audioDevice + "\nvideo: " + videoDevice + "\n");
   useFakeStreams = false;
 } catch (e) {
   dump("TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n");
   useFakeStreams = true;
 }
 
 function message(m) {
+  // eslint-disable-next-line no-unsanitized/property
   document.getElementById("message").innerHTML = m;
   window.parent.postMessage(m, "*");
 }
 
 var gStreams = [];
 
 function requestDevice(aAudio, aVideo, aShare) {
   var opts = {video: aVideo, audio: aAudio};
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2109,28 +2109,60 @@ file, You can obtain one at http://mozil
               parts.push(this._bundle.GetStringFromName(type + "ResultLabel"));
             } catch (e) {}
 
             return parts.filter(str => str).join(" ");
           ]]>
         </body>
       </method>
 
+      <method name="maybeSetupSpeculativeConnect">
+        <parameter name="aUriString"/>
+        <body><![CDATA[
+          // We shouldn't leak autocomplete result in the private context.
+          if (!Services.prefs.getBoolPref("browser.urlbar.speculativeConnect.enabled") ||
+              this.input.inPrivateContext) {
+            return;
+          }
+          try {
+            let uri = makeURI(aUriString);
+            Services.io.speculativeConnect2(uri, gBrowser.contentPrincipal, null);
+          } catch (ex) {
+            // Can't setup speculative connection for this uri string for some
+            // reason, just ignore it.
+          }
+        ]]></body>
+      </method>
+
       <method name="onResultsAdded">
         <body>
           <![CDATA[
             // If nothing is selected yet, select the first result if it is a
             // pre-selected "heuristic" result.  (See UnifiedComplete.js.)
             if (this.selectedIndex == -1 && this._isFirstResultHeuristic) {
               // Don't fire DOMMenuItemActive so that screen readers still see
               // the input as being focused.
               this.richlistbox.suppressMenuItemEvent = true;
               this.input.controller.setInitiallySelectedIndex(0);
               this.richlistbox.suppressMenuItemEvent = false;
             }
+            // If this is the first time we get the result from the current
+            // search, and the result is an "autofill" result, that means it's
+            // the site that user frequently visits. Then we could speculatively
+            // connect to this site as a performance optimization.
+            if (!this.input.gotResultForCurrentQuery &&
+                this.input.mController.matchCount > 0 &&
+                this.input.mController.getStyleAt(0).includes("autofill")) {
+              let uri = this.input.mController.getFinalCompleteValueAt(0);
+              // "http" will be stripped out, but other scheme won't.
+              if (!uri.includes("://")) {
+                uri = "http://" + uri;
+              }
+              this.maybeSetupSpeculativeConnect(uri);
+            }
 
             // When a result is present the footer should always be visible.
             this.footer.collapsed = false;
 
             this.input.gotResultForCurrentQuery = true;
             this.input.maybeReplayDeferredKeyEvents();
           ]]>
         </body>
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -11,20 +11,19 @@
        orient="vertical">
 
   <broadcasterset>
     <broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
     <broadcaster id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/>
   </broadcasterset>
 
   <panelmultiview id="identity-popup-multiView"
-                  mainViewId="identity-popup-mainView"
-                  descriptionheightworkaround="true">
-    <panelview id="identity-popup-mainView" flex="1">
-
+                  mainViewId="identity-popup-mainView">
+    <panelview id="identity-popup-mainView" flex="1"
+               descriptionheightworkaround="true">
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox id="identity-popup-security-content" flex="1">
           <label class="plain">
             <label class="identity-popup-headline identity-popup-host"></label>
             <label class="identity-popup-headline identity-popup-hostless" crop="end"/>
           </label>
           <description class="identity-popup-connection-not-secure"
@@ -92,17 +91,18 @@
           <vbox id="identity-popup-permission-list"/>
           <description id="identity-popup-permission-reload-hint">&identity.permissionsReloadHint;</description>
           <description id="identity-popup-permission-empty-hint">&identity.permissionsEmpty;</description>
         </vbox>
       </hbox>
     </panelview>
 
     <!-- Security SubView -->
-    <panelview id="identity-popup-securityView">
+    <panelview id="identity-popup-securityView"
+               descriptionheightworkaround="true">
       <vbox id="identity-popup-securityView-header">
         <label class="plain">
           <label class="identity-popup-headline identity-popup-host"></label>
           <label class="identity-popup-headline identity-popup-hostless" crop="end"/>
         </label>
         <description class="identity-popup-connection-not-secure"
                      when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
         <description class="identity-popup-connection-secure"
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -40,16 +40,18 @@ XPCOMUtils.defineLazyPreferenceGetter(th
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const kSpecialWidgetPfx = "customizableui-special-";
 
 const kPrefCustomizationState        = "browser.uiCustomization.state";
 const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
 const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
 const kPrefDrawInTitlebar            = "browser.tabs.drawInTitlebar";
+const kPrefUIDensity                 = "browser.uidensity";
+const kPrefAutoTouchMode             = "browser.touchmode.auto";
 
 const kExpectedWindowURL = "chrome://browser/content/browser.xul";
 
 /**
  * The keys are the handlers that are fired when the event type (the value)
  * is fired on the subview. A widget that provides a subview has the option
  * of providing onViewShowing and onViewHiding event handlers.
  */
@@ -154,16 +156,18 @@ var gNewElementCount = 0;
 var gGroupWrapperCache = new Map();
 var gSingleWrapperCache = new WeakMap();
 var gListeners = new Set();
 
 var gUIStateBeforeReset = {
   uiCustomizationState: null,
   drawInTitlebar: null,
   currentTheme: null,
+  uiDensity: null,
+  autoTouchMode: null,
 };
 
 var gDefaultPanelPlacements = null;
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
   let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
@@ -2546,23 +2550,27 @@ var CustomizableUIInternal = {
 
     gResetting = false;
   },
 
   _resetUIState() {
     try {
       gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
       gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
+      gUIStateBeforeReset.uiDensity = Services.prefs.getIntPref(kPrefUIDensity);
+      gUIStateBeforeReset.autoTouchMode = Services.prefs.getBoolPref(kPrefAutoTouchMode);
       gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme;
     } catch (e) { }
 
     this._resetExtraToolbars();
 
     Services.prefs.clearUserPref(kPrefCustomizationState);
     Services.prefs.clearUserPref(kPrefDrawInTitlebar);
+    Services.prefs.clearUserPref(kPrefUIDensity);
+    Services.prefs.clearUserPref(kPrefAutoTouchMode);
     LightweightThemeManager.currentTheme = null;
     log.debug("State reset");
 
     // Reset placements to make restoring default placements possible.
     gPlacements = new Map();
     gDirtyAreaCache = new Set();
     gSeenWidgets = new Set();
     // Clear the saved state to ensure that defaults will be used.
@@ -2623,23 +2631,27 @@ var CustomizableUIInternal = {
         gUIStateBeforeReset.drawInTitlebar == null) {
       return;
     }
     gUndoResetting = true;
 
     let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
     let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
     let currentTheme = gUIStateBeforeReset.currentTheme;
+    let uiDensity = gUIStateBeforeReset.uiDensity;
+    let autoTouchMode = gUIStateBeforeReset.autoTouchMode;
 
     // Need to clear the previous state before setting the prefs
     // because pref observers may check if there is a previous UI state.
     this._clearPreviousUIState();
 
     Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
     Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
+    Services.prefs.setIntPref(kPrefUIDensity, uiDensity);
+    Services.prefs.setBoolPref(kPrefAutoTouchMode, autoTouchMode);
     LightweightThemeManager.currentTheme = currentTheme;
     this.loadSavedState();
     // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
     // and we don't need to do anything else here:
     if (gSavedState) {
       for (let areaId of Object.keys(gSavedState.placements)) {
         let placements = gSavedState.placements[areaId];
         gPlacements.set(areaId, placements);
@@ -2804,16 +2816,26 @@ var CustomizableUIInternal = {
         if (currentPlacements[i] != defaultPlacements[i]) {
           log.debug("Found " + currentPlacements[i] + " in " + areaId + " where " +
                     defaultPlacements[i] + " was expected!");
           return false;
         }
       }
     }
 
+    if (Services.prefs.prefHasUserValue(kPrefUIDensity)) {
+      log.debug(kPrefUIDensity + " pref is non-default");
+      return false;
+    }
+
+    if (Services.prefs.prefHasUserValue(kPrefAutoTouchMode)) {
+      log.debug(kPrefAutoTouchMode + " pref is non-default");
+      return false;
+    }
+
     if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
       log.debug(kPrefDrawInTitlebar + " pref is non-default");
       return false;
     }
 
     if (LightweightThemeManager.currentTheme) {
       log.debug(LightweightThemeManager.currentTheme + " theme is non-default");
       return false;
@@ -3535,17 +3557,19 @@ this.CustomizableUI = {
    * Can the last Restore Defaults operation be undone.
    *
    * @return A boolean stating whether an undo of the
    *         Restore Defaults can be performed.
    */
   get canUndoReset() {
     return gUIStateBeforeReset.uiCustomizationState != null ||
            gUIStateBeforeReset.drawInTitlebar != null ||
-           gUIStateBeforeReset.currentTheme != null;
+           gUIStateBeforeReset.currentTheme != null ||
+           gUIStateBeforeReset.autoTouchMode != null ||
+           gUIStateBeforeReset.uiDensity != null;
   },
 
   /**
    * Get the placement of a widget. This is by far the best way to obtain
    * information about what the state of your widget is. The internals of
    * this call are cheap (no DOM necessary) and you will know where the user
    * has put your widget.
    *
@@ -4192,16 +4216,17 @@ OverflowableToolbar.prototype = {
   },
 
   _onClickChevron(aEvent) {
     if (this._chevron.open) {
       this._panel.hidePopup();
       this._chevron.open = false;
     } else if (this._panel.state != "hiding") {
       this.show();
+      this._chevron.removeAttribute("animate");
     }
   },
 
   _onPanelHiding(aEvent) {
     this._chevron.open = false;
     this._panel.removeEventListener("dragover", this);
     this._panel.removeEventListener("dragend", this);
     let doc = aEvent.target.ownerDocument;
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -326,16 +326,17 @@ const CustomizableWidgets = [
         link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
         link.setAttribute("mobile-promo-os", os);
         link.className = "text-link remotetabs-promo-link";
         return link.outerHTML;
       });
       let promoParentElt = doc.getElementById("PanelUI-remotetabs-mobile-promo");
       // Put it all together...
       let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
+      // eslint-disable-next-line no-unsanitized/property
       promoParentElt.innerHTML = contents;
       // We manually manage the "click" event to open the promo links because
       // allowing the "text-link" widget handle it has 2 problems: (1) it only
       // supports button 0 and (2) it's tricky to intercept when it does the
       // open and auto-close the panel. (1) can probably be fixed, but (2) is
       // trickier without hard-coding here the knowledge of exactly what buttons
       // it does support.
       // So we allow left and middle clicks to open the link in a new tab and
@@ -437,16 +438,18 @@ const CustomizableWidgets = [
           }
           if (paginationInfo && paginationInfo.clientId == client.id) {
             this._appendClient(client, fragment, paginationInfo.maxTabs);
           } else {
             this._appendClient(client, fragment);
           }
         }
         this._tabsList.appendChild(fragment);
+        let panelView = this._tabsList.closest("panelview");
+        panelView.panelMultiView.descriptionHeightWorkaround(panelView);
       }).catch(err => {
         Cu.reportError(err);
       }).then(() => {
         // an observer for tests.
         Services.obs.notifyObservers(null, "synced-tabs-menu:test:tabs-updated");
       });
     },
     _clearTabList() {
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -721,16 +721,17 @@ CustomizeMode.prototype = {
       return;
 
     let anchorNode = aAnchor || this.document.getElementById("customization-panelHolder");
     let messageNode = this.tipPanel.querySelector(".customization-tipPanel-contentMessage");
     if (!messageNode.childElementCount) {
       // Put the tip contents in the popup.
       let bundle = this.document.getElementById("bundle_browser");
       const kLabelClass = "customization-tipPanel-link";
+      // eslint-disable-next-line no-unsanitized/property
       messageNode.innerHTML = bundle.getFormattedString("customizeTips.tip0", [
         "<label class=\"customization-tipPanel-em\" value=\"" +
           bundle.getString("customizeTips.tip0.hint") + "\"/>",
         this.document.getElementById("bundle_brand").getString("brandShortName"),
         "<label class=\"" + kLabelClass + " text-link\" value=\"" +
         bundle.getString("customizeTips.tip0.learnMore") + "\"/>"
       ]);
 
@@ -799,16 +800,22 @@ CustomizeMode.prototype = {
       aNode = aNode.firstChild;
     }
     let panel = gPhotonStructure ? CustomizableUI.AREA_FIXED_OVERFLOW_PANEL
                                  : CustomizableUI.AREA_PANEL;
     CustomizableUI.addWidgetToArea(aNode.id, panel);
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
+
+    if (AppConstants.MOZ_PHOTON_ANIMATIONS &&
+        Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
+      let overflowButton = this.document.getElementById("nav-bar-overflow-button");
+      overflowButton.setAttribute("animate", "true");
+    }
   },
 
   removeFromArea(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
       aNode = aNode.firstChild;
     }
     CustomizableUI.removeWidgetFromArea(aNode.id);
@@ -1380,16 +1387,108 @@ CustomizeMode.prototype = {
   },
 
   getMoreThemes(aEvent) {
     aEvent.target.parentNode.parentNode.hidePopup();
     let getMoreURL = Services.urlFormatter.formatURLPref("lightweightThemes.getMoreURL");
     this.window.openUILinkIn(getMoreURL, "tab");
   },
 
+  updateUIDensity(mode) {
+    this.window.gUIDensity.update(mode);
+  },
+
+  setUIDensity(mode) {
+    let win = this.window;
+    let gUIDensity = win.gUIDensity;
+    let currentDensity = gUIDensity.getCurrentDensity();
+    let panel = win.document.getElementById("customization-uidensity-menu");
+
+    Services.prefs.setIntPref(gUIDensity.uiDensityPref, mode);
+
+    // If the user is choosing a different UI density mode while
+    // the mode is overriden to Touch, remove the override.
+    if (currentDensity.overridden) {
+      Services.prefs.setBoolPref(gUIDensity.autoTouchModePref, false);
+    }
+
+    this._onUIChange();
+    panel.hidePopup();
+  },
+
+  resetUIDensity() {
+    this.window.gUIDensity.update();
+  },
+
+  onUIDensityMenuShowing() {
+    let win = this.window;
+    let doc = win.document;
+    let gUIDensity = win.gUIDensity;
+    let currentDensity = gUIDensity.getCurrentDensity();
+
+    let normalButton = doc.getElementById("customization-uidensity-menu-button-normal");
+    normalButton.mode = gUIDensity.MODE_NORMAL;
+
+    let compactButton = doc.getElementById("customization-uidensity-menu-button-compact");
+    compactButton.mode = gUIDensity.MODE_COMPACT;
+
+    let buttons = [normalButton, compactButton];
+
+    let touchButton = doc.getElementById("customization-uidensity-menu-button-touch");
+    // Touch mode can not be enabled in OSX right now.
+    if (touchButton) {
+      touchButton.mode = gUIDensity.MODE_TOUCH;
+      buttons.push(touchButton);
+    }
+
+    // Mark the active mode button.
+    for (let button of buttons) {
+      if (button.mode == currentDensity.mode) {
+        button.setAttribute("aria-checked", "true");
+        button.setAttribute("active", "true");
+      } else {
+        button.removeAttribute("aria-checked");
+        button.removeAttribute("active");
+      }
+    }
+
+    // Add menu items for automatically switching to Touch mode in Windows Tablet Mode,
+    // which is only available in Windows 10.
+    if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+      let spacer = doc.getElementById("customization-uidensity-touch-spacer");
+      let checkbox = doc.getElementById("customization-uidensity-autotouchmode-checkbox");
+      spacer.removeAttribute("hidden");
+      checkbox.removeAttribute("hidden");
+
+      // Show a hint that the UI density was overridden automatically.
+      if (currentDensity.overridden) {
+        let sb = Services.strings.createBundle("chrome://browser/locale/uiDensity.properties");
+        touchButton.setAttribute("acceltext",
+                                 sb.GetStringFromName("uiDensity.menu-button-touch.acceltext"));
+      } else {
+        touchButton.removeAttribute("acceltext");
+      }
+
+      let autoTouchMode = Services.prefs.getBoolPref(win.gUIDensity.autoTouchModePref);
+      if (autoTouchMode) {
+        checkbox.setAttribute("checked", "true");
+      } else {
+        checkbox.removeAttribute("checked");
+      }
+    }
+  },
+
+  updateAutoTouchMode(checked) {
+    Services.prefs.setBoolPref("browser.touchmode.auto", checked);
+    // Re-render the menu items since the active mode might have
+    // change because of this.
+    this.onUIDensityMenuShowing();
+    this._onUIChange();
+  },
+
   onLWThemesMenuShowing(aEvent) {
     const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
     const RECENT_LWT_COUNT = 5;
 
     this._clearLWThemesMenu(aEvent.target);
 
     function previewTheme(aPreviewThemeEvent) {
       LightweightThemeManager.previewTheme(
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -325,19 +325,21 @@ this.PanelMultiView = class {
       this._moveOutKids(this._subViews);
 
     if (this.panelViews) {
       this._moveOutKids(this._viewStack);
       this.panelViews.clear();
     } else {
       this._clickCapturer.removeEventListener("click", this);
     }
+    this._panel.removeEventListener("mousemove", this);
     this._panel.removeEventListener("popupshowing", this);
     this._panel.removeEventListener("popupshown", this);
     this._panel.removeEventListener("popuphidden", this);
+    this.window.removeEventListener("keydown", this);
     this.node.dispatchEvent(new this.window.CustomEvent("destructed"));
     this.node = this._clickCapturer = this._viewContainer = this._mainViewContainer =
       this._subViews = this._viewStack = this.__dwu = this._panelViewCache = null;
   }
 
   /**
    * Remove any child subviews into the panelViewCache, to ensure
    * they remain usable even if this panelmultiview instance is removed
@@ -863,17 +865,33 @@ this.PanelMultiView = class {
         // height of the opposite margin, but we cannot get their actual values
         // because the panel is not visible yet. However, we know that this is
         // currently 11px on Mac, 13px on Windows, and 13px on Linux. We also
         // want an extra margin, both for visual reasons and to prevent glitches
         // due to small rounding errors. So, we just use a value that makes
         // sense for all platforms. If the arrow visuals change significantly,
         // this value will be easy to adjust.
         const EXTRA_MARGIN_PX = 20;
-        this._viewStack.style.maxHeight = (maxHeight - EXTRA_MARGIN_PX) + "px";
+        maxHeight -= EXTRA_MARGIN_PX;
+        this._viewStack.style.maxHeight = maxHeight + "px";
+
+        // When using block-in-box layout inside a scrollable frame, like in the
+        // main menu contents scroller, if we allow the contents to scroll then
+        // it will not cause its container to expand. Thus, we layout first
+        // without any scrolling (using "display: flex;"), and only if the view
+        // exceeds the available space we set the height explicitly and enable
+        // scrolling.
+        if (this._mainView.hasAttribute("blockinboxworkaround")) {
+          let mainViewHeight =
+              this._dwu.getBoundsWithoutFlushing(this._mainView).height;
+          if (mainViewHeight > maxHeight) {
+            this._mainView.style.height = maxHeight + "px";
+            this._mainView.setAttribute("exceeding", "true");
+          }
+        }
         break;
       case "popupshown":
         // Now that the main view is visible, we can check the height of the
         // description elements it contains.
         this.descriptionHeightWorkaround();
         break;
       case "popuphidden":
         this.node.removeAttribute("panelopen");
@@ -885,16 +903,22 @@ this.PanelMultiView = class {
               panelView.style.removeProperty("max-width");
             }
           }
           this.window.removeEventListener("keydown", this);
           this._panel.removeEventListener("mousemove", this);
           this._resetKeyNavigation();
           this._mainViewHeight = 0;
         }
+        // Always try to layout the panel normally when reopening it. This is
+        // also the layout that will be used in customize mode.
+        if (this._mainView.hasAttribute("blockinboxworkaround")) {
+          this._mainView.style.removeProperty("height");
+          this._mainView.removeAttribute("exceeding");
+        }
         break;
     }
   }
 
   /**
    * Allow for navigating subview buttons using the arrow keys and the Enter key.
    * The Up and Down keys can be used to navigate the list up and down and the
    * Enter, Right or Left - depending on the text direction - key can be used to
@@ -1030,39 +1054,51 @@ this.PanelMultiView = class {
       let bounds = dwu.getBoundsWithoutFlushing(button);
       return bounds.width > 0 && bounds.height > 0;
     });
   }
 
   /**
    * If the main view or a subview contains wrapping elements, the attribute
    * "descriptionheightworkaround" should be set on the view to force all the
-   * "description" or wrapping toolbarbutton elements to a fixed height.
-   * If the attribute is set and the visibility, contents, or width of any of
-   * these elements changes, this function should be called to refresh the
-   * calculated heights.
+   * wrapping "description", "label" or "toolbarbutton" elements to a fixed
+   * height. If the attribute is set and the visibility, contents, or width
+   * of any of these elements changes, this function should be called to
+   * refresh the calculated heights.
    *
    * This may trigger a synchronous layout.
    *
    * @param viewNode
    *        Indicates the node to scan for descendant elements. This is the main
    *        view if omitted.
    */
   descriptionHeightWorkaround(viewNode = this._mainView) {
-    if (!this.node.hasAttribute("descriptionheightworkaround")) {
+    if (!viewNode.hasAttribute("descriptionheightworkaround")) {
       // This view does not require the workaround.
       return;
     }
 
     // We batch DOM changes together in order to reduce synchronous layouts.
     // First we reset any change we may have made previously. The first time
     // this is called, and in the best case scenario, this has no effect.
     let items = [];
-    for (let element of viewNode.querySelectorAll(
-         "description:not([hidden]):not([value]),toolbarbutton[wrap]:not([hidden])")) {
+    // Non-hidden <label> or <description> elements that also aren't empty
+    // and also don't have a value attribute can be multiline (if their
+    // text content is long enough).
+    let isMultiline = ":not(:-moz-any([hidden],[value],:empty))";
+    let selector = [
+      "description" + isMultiline,
+      "label" + isMultiline,
+      "toolbarbutton[wrap]:not([hidden])",
+    ].join(",");
+    for (let element of viewNode.querySelectorAll(selector)) {
+      // Ignore items in hidden containers.
+      if (element.closest("[hidden]")) {
+        continue;
+      }
       // Take the label for toolbarbuttons; it only exists on those elements.
       element = element.labelElement || element;
 
       let bounds = element.getBoundingClientRect();
       let previous = this._multiLineElementsMap.get(element);
       // We don't need to (re-)apply the workaround for invisible elements or
       // on elements we've seen before and haven't changed in the meantime.
       if (!bounds.width || !bounds.height ||
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -46,16 +46,73 @@
             <toolbarbutton class="customization-lwtheme-menu-footeritem"
                            label="&customizeMode.lwthemes.menuGetMore;"
                            accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
                            tabindex="0"
                            oncommand="gCustomizeMode.getMoreThemes(event);"/>
           </hbox>
         </panel>
       </button>
+#ifdef MOZ_PHOTON_THEME
+      <button id="customization-uidensity-button"
+              label="&customizeMode.uidensity;"
+              class="customizationmode-button"
+              type="menu">
+        <panel type="arrow" id="customization-uidensity-menu"
+               onpopupshowing="gCustomizeMode.onUIDensityMenuShowing();"
+               position="topcenter bottomleft"
+               flip="none"
+               role="menu">
+          <menuitem id="customization-uidensity-menu-button-normal"
+                    class="menuitem-iconic customization-uidensity-menu-button"
+                    role="menuitemradio"
+                    label="&customizeMode.uidensity.menuNormal.label;"
+                    accesskey="&customizeMode.uidensity.menuNormal.accessKey;"
+                    tooltiptext="&customizeMode.uidensity.menuNormal.tooltip;"
+                    tabindex="0"
+                    onfocus="gCustomizeMode.updateUIDensity(this.mode);"
+                    onmouseover="gCustomizeMode.updateUIDensity(this.mode);"
+                    onblur="gCustomizeMode.resetUIDensity();"
+                    onmouseout="gCustomizeMode.resetUIDensity();"
+                    oncommand="gCustomizeMode.setUIDensity(this.mode);" />
+          <menuitem id="customization-uidensity-menu-button-compact"
+                    class="menuitem-iconic customization-uidensity-menu-button"
+                    role="menuitemradio"
+                    label="&customizeMode.uidensity.menuCompact.label;"
+                    accesskey="&customizeMode.uidensity.menuCompact.accessKey;"
+                    tooltiptext="&customizeMode.uidensity.menuCompact.tooltip;"
+                    tabindex="0"
+                    onfocus="gCustomizeMode.updateUIDensity(this.mode);"
+                    onmouseover="gCustomizeMode.updateUIDensity(this.mode);"
+                    onblur="gCustomizeMode.resetUIDensity();"
+                    onmouseout="gCustomizeMode.resetUIDensity();"
+                    oncommand="gCustomizeMode.setUIDensity(this.mode);" />
+#ifndef XP_MACOSX
+          <menuitem id="customization-uidensity-menu-button-touch"
+                    class="menuitem-iconic customization-uidensity-menu-button"
+                    role="menuitemradio"
+                    label="&customizeMode.uidensity.menuTouch.label;"
+                    accesskey="&customizeMode.uidensity.menuTouch.accessKey;"
+                    tooltiptext="&customizeMode.uidensity.menuTouch.tooltip;"
+                    tabindex="0"
+                    onfocus="gCustomizeMode.updateUIDensity(this.mode);"
+                    onmouseover="gCustomizeMode.updateUIDensity(this.mode);"
+                    onblur="gCustomizeMode.resetUIDensity();"
+                    onmouseout="gCustomizeMode.resetUIDensity();"
+                    oncommand="gCustomizeMode.setUIDensity(this.mode);">
+          </menuitem>
+          <spacer hidden="true" id="customization-uidensity-touch-spacer"/>
+          <checkbox id="customization-uidensity-autotouchmode-checkbox"
+                    hidden="true"
+                    label="&customizeMode.uidensity.autoTouchMode.checkbox.label;"
+                    oncommand="gCustomizeMode.updateAutoTouchMode(this.checked)"/>
+#endif
+        </panel>
+      </button>
+#endif
 
       <spacer id="customization-footer-spacer"/>
       <button id="customization-undo-reset-button"
               class="customizationmode-button"
               hidden="true"
               oncommand="gCustomizeMode.undoReset();"
               label="&undoCmd.label;"/>
       <button id="customization-reset-button"
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -6,17 +6,18 @@
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView"
                   viewCacheId="appMenu-viewCache">
-    <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
+    <panelview id="PanelUI-mainView" context="customizationPanelContextMenu"
+               descriptionheightworkaround="true" blockinboxworkaround="true">
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
         <vbox id="PanelUI-footer-addons"></vbox>
         <toolbarbutton class="panel-banner-item"
                        label-update-available="&updateAvailable.panelUI.label;"
@@ -104,17 +105,18 @@
         <vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
       </vbox>
       <toolbarbutton id="PanelUI-historyMore"
                      class="panel-subview-footer subviewbutton"
                      label="&appMenuHistory.showAll.label;"
                      oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
     </panelview>
 
-    <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
+    <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView"
+               descriptionheightworkaround="true">
       <label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
       <vbox class="panel-subview-body">
         <!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
         <!-- When Sync is ready to sync -->
         <vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
           <vbox id="PanelUI-remotetabs-buttons">
             <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
                            class="subviewbutton subviewbutton-iconic"
@@ -316,17 +318,18 @@
         <vbox>
           <label id="PanelUI-characterEncodingView-autodetect-label"/>
           <vbox id="PanelUI-characterEncodingView-autodetect"
                 class="PanelUI-characterEncodingView-list"/>
         </vbox>
       </vbox>
     </panelview>
 
-    <panelview id="PanelUI-panicView" flex="1">
+    <panelview id="PanelUI-panicView" flex="1"
+               descriptionheightworkaround="true">
       <vbox class="panel-subview-body">
         <hbox id="PanelUI-panic-timeframe">
           <image id="PanelUI-panic-timeframe-icon" alt=""/>
           <vbox flex="1">
             <hbox id="PanelUI-panic-header">
               <image id="PanelUI-panic-timeframe-icon-small" alt=""/>
               <description id="PanelUI-panic-mainDesc" flex="1">&panicButton.view.mainTimeframeDesc;</description>
             </hbox>
@@ -531,19 +534,19 @@
        class="cui-widget-panel"
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <photonpanelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView"
-                        descriptionheightworkaround="true"
                         viewCacheId="appMenu-viewCache">
-    <panelview id="appMenu-mainView" class="PanelUI-subView">
+    <panelview id="appMenu-mainView" class="PanelUI-subView"
+               descriptionheightworkaround="true">
       <vbox class="panel-subview-body">
         <vbox id="appMenu-addon-banners"/>
         <toolbarbutton class="panel-banner-item"
                        label-update-available="&updateAvailable.panelUI.label;"
                        label-update-manual="&updateManual.panelUI.label;"
                        label-update-restart="&updateRestart.panelUI.label2;"
                        oncommand="PanelUI._onBannerItemSelected(event)"
                        wrap="true"
@@ -661,24 +664,19 @@
                        command="Browser:OpenFile"
                        />
         <toolbarbutton id="appMenu-save-file-button"
                        class="subviewbutton"
                        label="&savePageCmd.label;"
                        key="key_savePage"
                        command="Browser:SavePage"
                        />
-        <toolbarbutton id="appMenu-page-setup-button"
-                       class="subviewbutton"
-                       label="&printSetupCmd.label;"
-                       command="cmd_pageSetup"
-                       />
         <toolbarbutton id="appMenu-print-button"
                        class="subviewbutton subviewbutton-iconic"
-                       label="&printButton.label;"
+                       label="&printCmd.label;"
                        key="printKb"
 #ifdef XP_MACOSX
                        command="cmd_print"
 #else
                        command="cmd_printPreview"
 #endif
                        />
         <toolbarseparator/>
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -67,17 +67,17 @@
   <binding id="panelview">
     <content>
       <xul:box class="panel-header" anonid="header">
         <xul:toolbarbutton anonid="back"
                            class="subviewbutton subviewbutton-iconic subviewbutton-back"
                            closemenu="none"
                            tabindex="0"
                            tooltip="&backCmd.label;"
-                           onclick="document.getBindingParent(this).panelMultiView.goBack()"/>
+                           onclick="document.getBindingParent(this).panelMultiView.goBack(); this.blur()"/>
         <xul:label xbl:inherits="value=title"/>
       </xul:box>
       <children/>
     </content>
     <implementation>
       <property name="header"
                 readonly="true"
                 onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'header');"/>
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -142,16 +142,17 @@ skip-if = os == "linux" # crashing on Li
 tags = fullscreen
 skip-if = os == "mac"
 [browser_1087303_button_preferences.js]
 [browser_1089591_still_customizable_after_reset.js]
 [browser_1096763_seen_widgets_post_reset.js]
 [browser_1161838_inserted_new_default_buttons.js]
 [browser_bootstrapped_custom_toolbar.js]
 [browser_customizemode_contextmenu_menubuttonstate.js]
+[browser_customizemode_uidensity.js]
 [browser_exit_background_customize_mode.js]
 [browser_overflow_use_subviews.js]
 [browser_panel_keyboard_navigation.js]
 [browser_panel_toggle.js]
 [browser_panelUINotifications.js]
 [browser_panelUINotifications_fullscreen.js]
 tags = fullscreen
 skip-if = os == "mac"
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_customizemode_uidensity.js
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const PREF_UI_DENSITY = "browser.uidensity";
+const PREF_AUTO_TOUCH_MODE = "browser.touchmode.auto";
+
+async function testModeButton(mode, modePref) {
+  await startCustomizing();
+
+  let win = document.getElementById("main-window");
+  let popupButton = document.getElementById("customization-uidensity-button");
+  let popup = document.getElementById("customization-uidensity-menu");
+
+  // Show the popup.
+  let popupShownPromise = popupShown(popup);
+  EventUtils.synthesizeMouseAtCenter(popupButton, {});
+  await popupShownPromise;
+
+  let button = document.getElementById("customization-uidensity-menu-button-" + mode);
+  let normalButton = document.getElementById("customization-uidensity-menu-button-normal");
+
+  is(normalButton.getAttribute("active"), "true",
+     "Normal mode button should be active by default.");
+
+  // Hover over the mode button and wait until the UI density is updated.
+  EventUtils.synthesizeMouseAtCenter(button, { type: "mouseover" });
+  await BrowserTestUtils.waitForAttribute("uidensity", win, mode);
+
+  is(win.getAttribute("uidensity"), mode,
+     `UI Density should be set to ${mode} on ${mode} button hover.`);
+
+  is(Services.prefs.getIntPref(PREF_UI_DENSITY), window.gUIDensity.MODE_NORMAL,
+     `UI Density pref should still be set to normal on ${mode} button hover.`);
+
+  // Hover the normal button again and check that the UI density reset to normal.
+  EventUtils.synthesizeMouseAtCenter(normalButton, { type: "mouseover" });
+  await BrowserTestUtils.waitForCondition(() => !win.hasAttribute("uidensity"));
+
+  ok(!win.hasAttribute("uidensity"),
+     `UI Density should be reset when no longer hovering the ${mode} button.`);
+
+  // Select the custom UI density and wait for the popup to be hidden.
+  let popupHiddenPromise = popupHidden(popup);
+  EventUtils.synthesizeMouseAtCenter(button, {});
+  await popupHiddenPromise;
+
+  // Check that the click permanently changed the UI density.
+  is(win.getAttribute("uidensity"), mode,
+     `UI Density should be set to ${mode} on ${mode} button click.`);
+  is(Services.prefs.getIntPref(PREF_UI_DENSITY), modePref,
+     `UI Density pref should be set to ${mode} when clicking the ${mode} button.`);
+
+  // Open the popup again.
+  popupShownPromise = popupShown(popup);
+  EventUtils.synthesizeMouseAtCenter(popupButton, {});
+  await popupShownPromise;
+
+  // Check that the button is still active after opening and closing the popup.
+  is(button.getAttribute("active"), "true", `${mode} mode button should be active.`);
+
+  // Hide the popup again.
+  popupHiddenPromise = popupHidden(popup);
+  EventUtils.synthesizeMouseAtCenter(popupButton, {});
+  await popupHiddenPromise;
+
+  // Check that the button is still active after re-opening customize mode.
+  await endCustomizing();
+  await startCustomizing();
+
+  popupShownPromise = popupShown(popup);
+  EventUtils.synthesizeMouseAtCenter(popupButton, {});
+  await popupShownPromise;
+
+  is(button.getAttribute("active"), "true",
+     `${mode} mode button should be active after entering and exiting customize mode.`);
+
+  // Click the normal button and check that the density is reset.
+  popupHiddenPromise = popupHidden(popup);
+  EventUtils.synthesizeMouseAtCenter(normalButton, {});
+  await popupHiddenPromise;
+
+  ok(!win.hasAttribute("uidensity"),
+     "UI Density should be reset when clicking the normal button.");
+
+  is(Services.prefs.getIntPref(PREF_UI_DENSITY), window.gUIDensity.MODE_NORMAL,
+     "UI Density pref should be set to normal.");
+
+  // Show the popup and click on the mode button again to test the
+  // reset default feature.
+  popupShownPromise = popupShown(popup);
+  EventUtils.synthesizeMouseAtCenter(popupButton, {});
+  await popupShownPromise;
+
+  popupHiddenPromise = popupHidden(popup);
+  EventUtils.synthesizeMouseAtCenter(button, {});
+  await popupHiddenPromise;
+
+  is(win.getAttribute("uidensity"), mode,
+     `UI Density should be set to ${mode} on ${mode} button click.`);
+
+  is(Services.prefs.getIntPref(PREF_UI_DENSITY), modePref,
+     `UI Density pref should be set to ${mode} when clicking the ${mode} button.`);
+
+  await gCustomizeMode.reset();
+
+  ok(!win.hasAttribute("uidensity"),
+     "UI Density should be reset when clicking the normal button.");
+
+  is(Services.prefs.getIntPref(PREF_UI_DENSITY), window.gUIDensity.MODE_NORMAL,
+     "UI Density pref should be set to normal.");
+
+  await endCustomizing();
+}
+
+add_task(async function test_compact_mode_button() {
+  if (!AppConstants.MOZ_PHOTON_THEME) {
+    ok(true, "Skipping test because Photon is not enabled.");
+    return;
+  }
+
+  await testModeButton("compact", window.gUIDensity.MODE_COMPACT);
+});
+
+add_task(async function test_touch_mode_button() {
+  if (!AppConstants.MOZ_PHOTON_THEME) {
+    ok(true, "Skipping test because Photon is not enabled.");
+    return;
+  }
+
+  // OSX doesn't get touch mode for now.
+  if (AppConstants.platform == "macosx") {
+    is(document.getElementById("customization-uidensity-menu-button-touch"), null,
+       "There's no touch option on Mac OSX");
+    return;
+  }
+
+  await testModeButton("touch", window.gUIDensity.MODE_TOUCH);
+
+  // Test the checkbox for automatic Touch Mode transition
+  // in Windows 10 Tablet Mode.
+  if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+    await startCustomizing();
+
+    let popupButton = document.getElementById("customization-uidensity-button");
+    let popup = document.getElementById("customization-uidensity-menu");
+    let popupShownPromise = popupShown(popup);
+    EventUtils.synthesizeMouseAtCenter(popupButton, {});
+    await popupShownPromise;
+
+    let checkbox = document.getElementById("customization-uidensity-autotouchmode-checkbox");
+    ok(checkbox.checked, "Checkbox should be checked by default");
+
+    // Test toggling the checkbox.
+    EventUtils.synthesizeMouseAtCenter(checkbox, {});
+    is(Services.prefs.getBoolPref(PREF_AUTO_TOUCH_MODE), false,
+       "Automatic Touch Mode is off when the checkbox is unchecked.");
+
+    EventUtils.synthesizeMouseAtCenter(checkbox, {});
+    is(Services.prefs.getBoolPref(PREF_AUTO_TOUCH_MODE), true,
+       "Automatic Touch Mode is on when the checkbox is checked.");
+
+    // Test reset to defaults.
+    EventUtils.synthesizeMouseAtCenter(checkbox, {});
+    is(Services.prefs.getBoolPref(PREF_AUTO_TOUCH_MODE), false,
+       "Automatic Touch Mode is off when the checkbox is unchecked.");
+
+    await gCustomizeMode.reset();
+    is(Services.prefs.getBoolPref(PREF_AUTO_TOUCH_MODE), true,
+       "Automatic Touch Mode is on when the checkbox is checked.");
+  }
+});
+
+add_task(async function cleanup() {
+  await endCustomizing();
+
+  Services.prefs.clearUserPref(PREF_UI_DENSITY);
+  Services.prefs.clearUserPref(PREF_AUTO_TOUCH_MODE);
+});
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -102,18 +102,17 @@
                   label="&cmd.removeFromHistory.label;"
                   accesskey="&cmd.removeFromHistory.accesskey;"/>
         <menuitem command="downloadsCmd_clearList"
                   label="&cmd.clearList2.label;"
                   accesskey="&cmd.clearList2.accesskey;"/>
       </menupopup>
 
       <panelmultiview id="downloadsPanel-multiView"
-                      mainViewId="downloadsPanel-mainView"
-                      descriptionheightworkaround="true">
+                      mainViewId="downloadsPanel-mainView">
 
         <panelview id="downloadsPanel-mainView">
           <vbox class="panel-view-body-unscrollable">
             <richlistbox id="downloadsListBox"
                          context="downloadsContextMenu"
                          onmouseover="DownloadsView.onDownloadMouseOver(event);"
                          onmouseout="DownloadsView.onDownloadMouseOut(event);"
                          oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
@@ -152,17 +151,18 @@
                         accesskey="&downloadsHistory.accesskey;"
                         flex="1"
                         oncommand="DownloadsPanel.showDownloadsHistory();"/>
               </hbox>
             </stack>
           </vbox>
         </panelview>
 
-        <panelview id="downloadsPanel-blockedSubview">
+        <panelview id="downloadsPanel-blockedSubview"
+                   descriptionheightworkaround="true">
           <vbox class="panel-view-body-unscrollable">
             <description id="downloadsPanel-blockedSubview-title"/>
             <description id="downloadsPanel-blockedSubview-details1"/>
             <description id="downloadsPanel-blockedSubview-details2"/>
           </vbox>
           <hbox id="downloadsPanel-blockedSubview-buttons"
                 class="downloadsPanelFooter"
                 align="stretch">
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -416,16 +416,19 @@ class ViewPopup extends BasePopup {
   constructor(extension, window, popupURL, browserStyle, fixedWidth, blockParser) {
     let document = window.document;
 
     // Create a temporary panel to hold the browser while it pre-loads its
     // content. This panel will never be shown, but the browser's docShell will
     // be swapped with the browser in the real panel when it's ready.
     let panel = document.createElement("panel");
     panel.setAttribute("type", "arrow");
+    if (extension.remote) {
+      panel.setAttribute("remote", "true");
+    }
     document.getElementById("mainPopupSet").appendChild(panel);
 
     super(extension, panel, popupURL, browserStyle, fixedWidth, blockParser);
 
     this.ignoreResizes = true;
 
     this.attached = false;
     this.shown = false;
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -201,16 +201,18 @@ this.browserAction = class extends Exten
             event.preventDefault();
           }
         } else {
           TelemetryStopwatch.cancel(POPUP_OPEN_MS_HISTOGRAM, this);
           // This isn't not a hack, but it seems to provide the correct behavior
           // with the fewest complications.
           event.preventDefault();
           this.emit("click", tabbrowser.selectedBrowser);
+          // Ensure we close any popups this node was in:
+          CustomizableUI.hidePanelForNode(event.target);
         }
       },
     });
 
     this.tabContext.on("tab-select", // eslint-disable-line mozilla/balanced-listeners
                        (evt, tab) => { this.updateWindow(tab.ownerGlobal); });
 
     this.widget = widget;
--- a/browser/components/extensions/ext-c-omnibox.js
+++ b/browser/components/extensions/ext-c-omnibox.js
@@ -7,23 +7,23 @@
 
 this.omnibox = class extends ExtensionAPI {
   getAPI(context) {
     return {
       omnibox: {
         onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
           let listener = (text, id) => {
             fire.asyncWithoutClone(text, suggestions => {
-              context.childManager.callParentFunctionNoReturn("omnibox_internal.addSuggestions", [
+              context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [
                 id,
                 suggestions,
               ]);
             });
           };
-          context.childManager.getParentEvent("omnibox_internal.onInputChanged").addListener(listener);
+          context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
           return () => {
-            context.childManager.getParentEvent("omnibox_internal.onInputChanged").removeListener(listener);
+            context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
           };
         }).api(),
       },
     };
   }
 };
--- a/browser/components/extensions/ext-menus.js
+++ b/browser/components/extensions/ext-menus.js
@@ -188,17 +188,17 @@ var gMenuBuilder = {
     element.appendChild(menupopup);
     return element;
   },
 
   customizeElement(element, item, contextData) {
     let label = item.title;
     if (label) {
       if (contextData.isTextSelected && label.indexOf("%s") > -1) {
-        let selection = contextData.selectionText;
+        let selection = contextData.selectionText.trim();
         // The rendering engine will truncate the title if it's longer than 64 characters.
         // But if it makes sense let's try truncate selection text only, to handle cases like
         // 'look up "%s" in MyDictionary' more elegantly.
         let maxSelectionLength = gMaxLabelLength - label.length + 2;
         if (maxSelectionLength > 4) {
           selection = selection.substring(0, maxSelectionLength - 3) + "...";
         }
         label = label.replace(/%s/g, selection);
--- a/browser/components/extensions/ext-omnibox.js
+++ b/browser/components/extensions/ext-omnibox.js
@@ -64,29 +64,28 @@ this.omnibox = class extends ExtensionAP
           let listener = (eventName, text, disposition) => {
             fire.sync(text, disposition);
           };
           extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
           return () => {
             extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
           };
         }).api(),
-      },
 
-      omnibox_internal: {
+        // Internal APIs.
         addSuggestions: (id, suggestions) => {
           try {
             ExtensionSearchHandler.addSuggestions(this.keyword, id, suggestions);
           } catch (e) {
             // Silently fail because the extension developer can not know for sure if the user
             // has already invalidated the callback when asynchronously providing suggestions.
           }
         },
 
-        onInputChanged: new EventManager(context, "omnibox_internal.onInputChanged", fire => {
+        onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
           let listener = (eventName, text, id) => {
             fire.sync(text, id);
           };
           extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
           return () => {
             extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
           };
         }).api(),
--- a/browser/components/extensions/schemas/omnibox.json
+++ b/browser/components/extensions/schemas/omnibox.json
@@ -192,57 +192,10 @@
       },
       {
         "name": "onInputCancelled",
         "type": "function",
         "description": "User has ended the keyword input session without accepting the input.",
         "parameters": []
       }
     ]
-  },
-  {
-    "namespace": "omnibox_internal",
-    "description": "The internal namespace used by the omnibox API.",
-    "defaultContexts": ["addon_parent_only"],
-    "functions": [
-      {
-        "name": "addSuggestions",
-        "type": "function",
-        "async": "callback",
-        "description": "Internal function used by omnibox.onInputChanged for adding search suggestions",
-        "parameters": [
-          {
-            "name": "id",
-            "type": "integer",
-            "description": "The ID of the callback received by onInputChangedInternal"
-          },
-          {
-            "name": "suggestResults",
-            "type": "array",
-            "description": "Array of suggest results",
-            "items": {
-              "$ref": "omnibox.SuggestResult"
-            }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": []
-          }
-        ]
-      }
-    ],
-    "events": [
-      {
-        "name": "onInputChanged",
-        "type": "function",
-        "description": "Identical to omnibox.onInputChanged except no 'suggest' callback is provided.",
-        "parameters": [
-          {
-            "type": "string",
-            "name": "text"
-          }
-        ]
-      }
-    ]
   }
-]
\ No newline at end of file
+]
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -51,17 +51,16 @@ skip-if = (os == 'win' && !debug) # bug 
 [browser_ext_commands_execute_sidebar_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
 [browser_ext_contextMenus_checkboxes.js]
 [browser_ext_contextMenus_commands.js]
 [browser_ext_contextMenus_icons.js]
-skip-if = os == 'win' && !debug # Bug 1351638
 [browser_ext_contextMenus_onclick.js]
 [browser_ext_contextMenus_radioGroups.js]
 [browser_ext_contextMenus_uninstall.js]
 [browser_ext_contextMenus_urlPatterns.js]
 [browser_ext_currentWindow.js]
 [browser_ext_devtools_inspectedWindow.js]
 [browser_ext_devtools_inspectedWindow_eval_bindings.js]
 [browser_ext_devtools_inspectedWindow_reload.js]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
@@ -63,17 +63,18 @@ async function testInArea(area) {
             sendClick({expectEvent: false, expectPopup: "a"});
           },
           () => {
             browser.test.log(`Click browser action again, expect popup "a".`);
             sendClick({expectEvent: false, expectPopup: "a"});
           },
           () => {
             browser.test.log(`Call triggerAction, expect popup "a" again. Leave popup open.`);
-            sendClick({expectEvent: false, expectPopup: "a", closePopup: false}, "trigger-action");
+            sendClick({expectEvent: false, expectPopup: "a",
+                       closePopup: false, containingPopupShouldClose: false}, "trigger-action");
           },
           () => {
             browser.test.log(`Call triggerAction again. Expect remaining popup closed.`);
             sendClick({expectEvent: false, expectPopup: null}, "trigger-action");
             browser.test.sendMessage("next-test", {waitUntilClosed: true});
           },
           () => {
             browser.test.log(`Call triggerAction again. Expect popup "a" again.`);
@@ -104,31 +105,37 @@ async function testInArea(area) {
           },
           () => {
             browser.test.log(`Call triggerAction. Expect click event.`);
             sendClick({expectEvent: true, expectPopup: null}, "trigger-action");
           },
           () => {
             browser.test.log(`Set popup to "a" and click browser action. Expect popup "a", and leave open.`);
             browser.browserAction.setPopup({popup: "/popup-a.html"});
-            sendClick({expectEvent: false, expectPopup: "a", closePopup: false});
+            sendClick({expectEvent: false, expectPopup: "a", closePopup: false,
+                       containingPopupShouldClose: false});
           },
           () => {
             browser.test.log(`Tell popup "a" to call window.close(). Expect popup closed.`);
             browser.test.sendMessage("next-test", {closePopupUsingWindow: true});
           },
         ];
 
         let expect = {};
-        sendClick = ({expectEvent, expectPopup, runNextTest, waitUntilClosed, closePopup}, message = "send-click") => {
+        sendClick = ({expectEvent, expectPopup, runNextTest, waitUntilClosed,
+                      closePopup, containingPopupShouldClose = true},
+                     message = "send-click") => {
           if (closePopup == undefined) {
             closePopup = !expectEvent;
           }
 
-          expect = {event: expectEvent, popup: expectPopup, runNextTest, waitUntilClosed, closePopup};
+          expect = {
+            event: expectEvent, popup: expectPopup, runNextTest,
+            waitUntilClosed, closePopup, containingPopupShouldClose,
+          };
           browser.test.sendMessage(message);
         };
 
         browser.runtime.onMessage.addListener(msg => {
           if (msg == "close-popup-using-window.close") {
             return;
           } else if (expect.popup) {
             browser.test.assertEq(msg, `from-popup-${expect.popup}`,
@@ -211,16 +218,24 @@ async function testInArea(area) {
         info("Waiting for panel");
         await awaitExtensionPanel(extension);
       }
 
       info("Closing for panel");
       await closeBrowserAction(extension);
     }
 
+    if (area == getCustomizableUIPanelID() && expecting.containingPopupShouldClose) {
+      let {node} = getBrowserActionWidget(extension).forWindow(window);
+      let panel = node.closest("panel");
+      info(`State of panel ${panel.id} is: ${panel.state}`);
+      ok(!["open", "showing"].includes(panel.state),
+         "Panel containing the action should be closed");
+    }
+
     info("Starting next test");
     extension.sendMessage("next-test");
   });
 
   await Promise.all([extension.startup(), extension.awaitFinish("browseraction-tests-done")]);
 
   await extension.unload();
 
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -54,16 +54,23 @@ add_task(async function() {
   gBrowser.selectedTab = tab1;
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["contextMenus"],
     },
 
     background: async function() {
+      browser.test.onMessage.addListener(msg => {
+        if (msg == "removeall") {
+          browser.contextMenus.removeAll();
+          browser.test.sendMessage("removed");
+        }
+      });
+
       // A generic onclick callback function.
       function genericOnClick(info, tab) {
         browser.test.sendMessage("onclick", {info, tab});
       }
 
       browser.contextMenus.onClicked.addListener((info, tab) => {
         browser.test.sendMessage("browser.contextMenus.onClicked", {info, tab});
       });
@@ -81,20 +88,17 @@ add_task(async function() {
           title: title,
           contexts: [context],
           id: "ext-" + context,
           onclick: genericOnClick,
         });
         if (context == "selection") {
           browser.contextMenus.update("ext-selection", {
             title: "selection is: '%s'",
-            onclick: (info, tab) => {
-              browser.contextMenus.removeAll();
-              genericOnClick(info, tab);
-            },
+            onclick: genericOnClick,
           });
         }
       }
 
       let parent = browser.contextMenus.create({
         title: "parent",
       });
       browser.contextMenus.create({
@@ -285,23 +289,89 @@ add_task(async function() {
   items = extensionMenuRoot.getElementsByAttribute("label", "selection");
   is(items.length, 0, "contextMenu item label update worked (context=selection)");
 
   await closeExtensionContextMenu(selectionItem);
 
   expectedClickInfo = {
     menuItemId: "ext-selection",
     pageUrl: PAGE,
-    selectionText: "just some text 1234567890123456789012345678901234567890123456789012345678901234567890123456789012",
+    selectionText: " just some text 1234567890123456789012345678901234567890123456789012345678901234567890123456789012",
+  };
+
+  result = await extension.awaitMessage("onclick");
+  checkClickInfo(result);
+  result = await extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
+
+  // Select a lot of text
+  await ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+    let doc = content.document;
+    let range = doc.createRange();
+    let selection = content.getSelection();
+    selection.removeAllRanges();
+    let textNode = doc.getElementById("longtext").firstChild;
+    range.setStart(textNode, 0);
+    range.setEnd(textNode, textNode.length);
+    selection.addRange(range);
+  });
+
+  // Bring up context menu again
+  extensionMenuRoot = await openExtensionContextMenu("#longtext");
+
+  // Check some menu items
+  items = extensionMenuRoot.getElementsByAttribute("label", "selection is: 'Sed ut perspiciatis unde omnis iste natus err...'");
+  is(items.length, 1, `contextMenu item for longtext selection was found (context=selection)`);
+  await closeExtensionContextMenu(items[0]);
+
+  expectedClickInfo = {
+    menuItemId: "ext-selection",
+    pageUrl: PAGE,
   };
 
   result = await extension.awaitMessage("onclick");
   checkClickInfo(result);
   result = await extension.awaitMessage("browser.contextMenus.onClicked");
   checkClickInfo(result);
+  ok(result.info.selectionText.endsWith("quo voluptas nulla pariatur?"), "long text selection worked");
+
+
+  // Select a lot of text, excercise the nsIDOMNSEditableElement code path in
+  // the Browser:GetSelection handler.
+  await ContentTask.spawn(gBrowser.selectedBrowser, { }, function(arg) {
+    let doc = content.document;
+    let node = doc.getElementById("editabletext");
+    // content.js handleContentContextMenu fails intermittently without focus.
+    node.focus();
+    node.selectionStart = 0;
+    node.selectionEnd = 844;
+  });
+
+  // Bring up context menu again
+  extensionMenuRoot = await openExtensionContextMenu("#editabletext");
+
+  // Check some menu items
+  items = extensionMenuRoot.getElementsByAttribute("label", "editable");
+  is(items.length, 1, "contextMenu item for text input element was found (context=editable)");
+  await closeExtensionContextMenu(items[0]);
+
+  expectedClickInfo = {
+    menuItemId: "ext-editable",
+    editable: true,
+    pageUrl: PAGE,
+  };
+
+  result = await extension.awaitMessage("onclick");
+  checkClickInfo(result);
+  result = await extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
+  ok(result.info.selectionText.endsWith("perferendis doloribus asperiores repellat."), "long text selection worked");
+
+  extension.sendMessage("removeall");
+  await extension.awaitMessage("removed");
 
   let contentAreaContextMenu = await openContextMenu("#img1");
   items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
   is(items.length, 0, "top level item was not found (after removeAll()");
   await closeContextMenu();
 
   await extension.unload();
   await BrowserTestUtils.removeTab(tab1);
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_commands.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_commands.js
@@ -28,16 +28,17 @@ add_task(async function() {
   function testScript() {
     window.onload = () => {
       browser.test.sendMessage("test-opened", true);
     };
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
+      "name": "contextMenus commands",
       "permissions": ["contextMenus", "activeTab", "tabs"],
       "browser_action": {
         "default_title": "Test BrowserAction",
         "default_popup": "test.html",
         "browser_style": true,
       },
       "page_action": {
         "default_title": "Test PageAction",
@@ -54,16 +55,17 @@ add_task(async function() {
       "test.html": `<!DOCTYPE html><meta charset="utf-8"><script src="test.js"></script>`,
       "test.js": testScript,
     },
   });
 
   async function testContext(id) {
     const menu = await openExtensionContextMenu();
     const items = menu.getElementsByAttribute("label", id);
+    is(items.length, 1, `exactly one menu item found`);
     await closeExtensionContextMenu(items[0]);
     return extension.awaitMessage("test-opened");
   }
 
   await extension.startup();
   await extension.awaitMessage("ready");
 
   // open a page so page action works
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -6,16 +6,17 @@ add_task(async function() {
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser,
     "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html?test=icons");
 
   let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
   const IMAGE_ARRAYBUFFER = imageBufferFromDataURI(encodedImageData);
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
+      "name": "contextMenus icons",
       "permissions": ["contextMenus"],
       "icons": {
         "18": "extension.png",
       },
     },
 
     files: {
       "extension.png": IMAGE_ARRAYBUFFER,
@@ -33,38 +34,40 @@ add_task(async function() {
       browser.contextMenus.create({
         title: "child",
       });
 
       browser.test.notifyPass("contextmenus-icons");
     },
   });
 
-  let confirmContextMenuIcon = (rootElement) => {
+  let confirmContextMenuIcon = (rootElements) => {
     let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/extension\.png$`);
-    let imageUrl = rootElement.getAttribute("image");
+    is(rootElements.length, 1, "Found exactly one menu item");
+    let imageUrl = rootElements[0].getAttribute("image");
     ok(expectedURL.test(imageUrl), "The context menu should display the extension icon next to the root element");
   };
 
   await extension.startup();
   await extension.awaitFinish("contextmenus-icons");
 
   let extensionMenu = await openExtensionContextMenu();
 
   let contextMenu = document.getElementById("contentAreaContextMenu");
-  let topLevelMenuItem = contextMenu.getElementsByAttribute("ext-type", "top-level-menu")[0];
+  let topLevelMenuItem = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
   confirmContextMenuIcon(topLevelMenuItem);
 
-  let childToDelete = extensionMenu.getElementsByAttribute("label", "child-to-delete")[0];
-  await closeExtensionContextMenu(childToDelete);
+  let childToDelete = extensionMenu.getElementsByAttribute("label", "child-to-delete");
+  is(childToDelete.length, 1, "Found exactly one child to delete");
+  await closeExtensionContextMenu(childToDelete[0]);
   await extension.awaitMessage("child-deleted");
 
   await openExtensionContextMenu();
 
   contextMenu = document.getElementById("contentAreaContextMenu");
-  topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child")[0];
+  topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child");
 
   confirmContextMenuIcon(topLevelMenuItem);
   await closeContextMenu();
 
   await extension.unload();
   await BrowserTestUtils.removeTab(tab1);
 });
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
@@ -213,16 +213,17 @@ add_task(async function test_onclick_mod
   const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
 
   await extension.startup();
   await extension.awaitMessage("ready");
 
   async function click(modifiers = {}) {
     const menu = await openContextMenu();
     const items = menu.getElementsByAttribute("label", "modify");
+    is(items.length, 1, "Got exactly one context menu item");
     await closeExtensionContextMenu(items[0], modifiers);
     return extension.awaitMessage("click");
   }
 
   const plain = await click();
   is(plain.modifiers.length, 0, "modifiers array empty with a plain click");
 
   const shift = await click({shiftKey: true});
--- a/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
@@ -150,27 +150,33 @@ add_task(async function test_unpinned() 
   });
 });
 
 add_task(async function test_url() {
   await do_test_update(function background() {
     // Create a new tab for testing update.
     browser.tabs.create({}, function(tab) {
       browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
-        // Check callback
-        browser.test.assertEq(tabId, tab.id, "Check tab id");
-        browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
         if ("url" in changeInfo) {
+          // When activity stream is enabled, about:newtab runs in the content process
+          // which causes some timing issues for onUpdated. So if we encounter
+          // about:newtab, return early and continue waiting for about:blank.
+          if (changeInfo.url === "about:newtab") {
+            return;
+          }
           browser.test.assertEq("about:blank", changeInfo.url,
                                 "Check changeInfo.url");
           browser.tabs.onUpdated.removeListener(onUpdated);
           // Remove created tab.
           browser.tabs.remove(tabId);
           browser.test.notifyPass("finish");
         }
+        // Check callback
+        browser.test.assertEq(tabId, tab.id, "Check tab id");
+        browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
       });
       browser.tabs.update(tab.id, {url: "about:blank"});
     });
   });
 });
 
 add_task(async function test_title() {
   await do_test_update(async function background() {
--- a/browser/components/extensions/test/browser/browser_ext_themes_icons.js
+++ b/browser/components/extensions/test/browser/browser_ext_themes_icons.js
@@ -135,22 +135,24 @@ async function runTestWithIcons(icons) {
     ["sidebars", "#sidebar-button", "sidebar-button"],
     ["share_page", "#social-share-button", "social-share-button"],
     ["subscribe", "#feed-button", "feed-button"],
     ["text_encoding", "#characterencoding-button", "characterencoding-button"],
     ["email_link", "#email-link-button", "email-link-button"],
     ["forget", "#panic-button", "panic-button"],
     ["pocket", "#pocket-button", "pocket-button"],
   ];
+  // We add these at the beginning because adding them at the end can end up
+  // putting them in the overflow panel, where they aren't displayed the same way.
   if (AppConstants.MOZ_PHOTON_THEME) {
-    ICON_INFO.push(["bookmark_star", "#star-button"]);
-    ICON_INFO.push(["bookmark_menu", "#bookmarks-menu-button", "bookmarks-menu-button"]);
+    ICON_INFO.unshift(["bookmark_star", "#star-button"]);
+    ICON_INFO.unshift(["bookmark_menu", "#bookmarks-menu-button", "bookmarks-menu-button"]);
   } else {
-    ICON_INFO.push(["bookmark_star", "#bookmarks-menu-button", "bookmarks-menu-button"]);
-    ICON_INFO.push(["bookmark_menu", "#bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon"]);
+    ICON_INFO.unshift(["bookmark_star", "#bookmarks-menu-button", "bookmarks-menu-button"]);
+    ICON_INFO.unshift(["bookmark_menu", "#bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon"]);
   }
 
   window.maximize();
 
   for (let button of ICON_INFO) {
     if (button[2]) {
       CustomizableUI.addWidgetToArea(button[2], CustomizableUI.AREA_NAVBAR);
     }
--- a/browser/components/extensions/test/browser/context.html
+++ b/browser/components/extensions/test/browser/context.html
@@ -1,8 +1,9 @@
+<!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8">
   </head>
   <body>
   just some text 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
   <img src="ctxmenu-image.png" id="img1">
 
@@ -15,11 +16,25 @@
       <img src="ctxmenu-image.png" id="img-wrapped-in-link">
     </a>
   </p>
 
   <p>
     <input type="text" id="edit-me"><br>
     <input type="password" id="password">
   </p>
-  <iframe id="frame" src="context_frame.html"/>
+  <iframe id="frame" src="context_frame.html"></iframe>
+  <p id="longtext">Sed ut perspiciatis unde omnis iste natus error sit
+  voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque
+  ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta
+  sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut
+  odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem
+  sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit
+  amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
+  incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad
+  minima veniam, quis nostrum exercitationem ullam corporis suscipit
+  laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum
+  iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+  consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
+
+  <input id="editabletext" type="text" value="At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat." />
   </body>
 </html>
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -315,28 +315,31 @@ async function openExtensionContextMenu(
   let contextMenu = await openContextMenu(selector);
   let topLevelMenu = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
 
   // Return null if the extension only has one item and therefore no extension menu.
   if (topLevelMenu.length == 0) {
     return null;
   }
 
-  let extensionMenu = topLevelMenu[0].childNodes[0];
+  let extensionMenu = topLevelMenu[0];
   let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(extensionMenu, {});
   await popupShownPromise;
   return extensionMenu;
 }
 
 async function closeExtensionContextMenu(itemToSelect, modifiers = {}) {
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
   EventUtils.synthesizeMouseAtCenter(itemToSelect, modifiers);
-  return popupHiddenPromise;
+  await popupHiddenPromise;
+
+  // Bug 1351638: parent menu fails to close intermittently, make sure it does.
+  contentAreaContextMenu.hidePopup();
 }
 
 async function openChromeContextMenu(menuId, target, win = window) {
   const node = win.document.querySelector(target);
   const menu = win.document.getElementById(menuId);
   const shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(node, {type: "contextmenu"}, win);
   await shown;
--- a/browser/components/migration/tests/unit/test_automigration.js
+++ b/browser/components/migration/tests/unit/test_automigration.js
@@ -661,17 +661,20 @@ add_task(async function checkUndoVisitsS
                "2 example.org visits should have persisted (out of 4).");
   Assert.equal(await visitsForURL("http://www.unrelated.org/"), 1,
                "1 unrelated.org visits should have persisted as it's not involved in the import.");
   await PlacesTestUtils.clearHistory();
 });
 
 add_task(async function checkHistoryRemovalCompletion() {
   AutoMigrate._errorMap = {bookmarks: 0, visits: 0, logins: 0};
-  await AutoMigrate._removeSomeVisits([{url: "http://www.example.com/", limit: -1}]);
+  await AutoMigrate._removeSomeVisits([{url: "http://www.example.com/",
+                                        first: 0,
+                                        last: PlacesUtils.toPRTime(new Date()),
+                                        limit: -1}]);
   ok(true, "Removing visits should complete even if removing some visits failed.");
   Assert.equal(AutoMigrate._errorMap.visits, 1, "Should have logged the error for visits.");
 
   // Unfortunately there's not a reliable way to make removing bookmarks be
   // unhappy unless the DB is messed up (e.g. contains children but has
   // parents removed already).
   await AutoMigrate._removeUnchangedBookmarks([
     {guid: PlacesUtils.bookmarks, lastModified: new Date(0), parentGuid: 0},
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -338,17 +338,18 @@ nsBrowserContentHandler.prototype = {
       }
     } catch (e) {
       Components.utils.reportError(e);
     }
 
     try {
       while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
         let uri = resolveURIInternal(cmdLine, uriparam);
-        handURIToExistingBrowser(uri, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine);
+        handURIToExistingBrowser(uri, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, false,
+                                 Services.scriptSecurityManager.getSystemPrincipal());
         cmdLine.preventDefault = true;
       }
     } catch (e) {
       Components.utils.reportError(e);
     }
 
     var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
     if (chromeParam) {
@@ -386,17 +387,18 @@ nsBrowserContentHandler.prototype = {
     }
     if (cmdLine.handleFlag("silent", false))
       cmdLine.preventDefault = true;
 
     try {
       var privateWindowParam = cmdLine.handleFlagWithParam("private-window", false);
       if (privateWindowParam) {
         let resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
-        handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, true);
+        handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, true,
+                                 Services.scriptSecurityManager.getSystemPrincipal());
         cmdLine.preventDefault = true;
       }
     } catch (e) {
       if (e.result != Components.results.NS_ERROR_INVALID_ARG) {
         throw e;
       }
       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
       if (cmdLine.handleFlag("private-window", false)) {
@@ -602,18 +604,18 @@ nsBrowserContentHandler.prototype = {
       if (!webNavInfo.isTypeSupported(contentType, null)) {
         throw NS_ERROR_WONT_HANDLE_CONTENT;
       }
     } catch (e) {
       throw NS_ERROR_WONT_HANDLE_CONTENT;
     }
 
     request.QueryInterface(nsIChannel);
-    handURIToExistingBrowser(request.URI,
-      nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, null);
+    handURIToExistingBrowser(request.URI, nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, null, false,
+                             request.loadInfo.triggeringPrincipal);
     request.cancel(NS_BINDING_ABORTED);
   },
 
   /* nsICommandLineValidator */
   validate: function bch_validate(cmdLine) {
     // Other handlers may use osint so only handle the osint flag if the url
     // flag is also present and the command line is valid.
     var osintFlagIdx = cmdLine.findFlag("osint", false);
@@ -637,17 +639,17 @@ nsBrowserContentHandler.prototype = {
         throw NS_ERROR_ABORT;
       }
       cmdLine.handleFlag("osint", false)
     }
   },
 };
 var gBrowserContentHandler = new nsBrowserContentHandler();
 
-function handURIToExistingBrowser(uri, location, cmdLine, forcePrivate) {
+function handURIToExistingBrowser(uri, location, cmdLine, forcePrivate, triggeringPrincipal) {
   if (!shouldLoadURI(uri))
     return;
 
   // Unless using a private window is forced, open external links in private
   // windows only if we're in perma-private mode.
   var allowPrivate = forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
   var navWin = RecentWindow.getMostRecentBrowserWindow({private: allowPrivate});
   if (!navWin) {
@@ -662,17 +664,17 @@ function handURIToExistingBrowser(uri, l
 
   var navNav = navWin.QueryInterface(nsIInterfaceRequestor)
                      .getInterface(nsIWebNavigation);
   var rootItem = navNav.QueryInterface(nsIDocShellTreeItem).rootTreeItem;
   var rootWin = rootItem.QueryInterface(nsIInterfaceRequestor)
                         .getInterface(nsIDOMWindow);
   var bwin = rootWin.QueryInterface(nsIDOMChromeWindow).browserDOMWindow;
   bwin.openURI(uri, null, location,
-               nsIBrowserDOMWindow.OPEN_EXTERNAL);
+               nsIBrowserDOMWindow.OPEN_EXTERNAL, triggeringPrincipal);
 }
 
 function nsDefaultCommandLineHandler() {
 }
 
 nsDefaultCommandLineHandler.prototype = {
   classID: Components.ID("{47cd0651-b1be-4a0f-b5c4-10e5a573ef71}"),
 
@@ -737,17 +739,18 @@ nsDefaultCommandLineHandler.prototype = 
     }
 
     if (urilist.length) {
       if (cmdLine.state != nsICommandLine.STATE_INITIAL_LAUNCH &&
           urilist.length == 1) {
         // Try to find an existing window and load our URI into the
         // current tab, new tab, or new window as prefs determine.
         try {
-          handURIToExistingBrowser(urilist[0], nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, cmdLine);
+          handURIToExistingBrowser(urilist[0], nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, cmdLine, false,
+                                   Services.scriptSecurityManager.getSystemPrincipal());
           return;
         } catch (e) {
         }
       }
 
       var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
       if (URLlist.length) {
         openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -10,17 +10,16 @@ const Cu = Components.utils;
 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/AsyncPrefs.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils", "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
-XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-service;1", "nsIAlertsService");
 XPCOMUtils.defineLazyGetter(this, "WeaveService", () =>
   Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject
 );
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
 
 // lazy module getters
 
@@ -255,16 +254,17 @@ function BrowserGlue() {
   XPCOMUtils.defineLazyGetter(this, "_sanitizer",
     function() {
       let sanitizerScope = {};
       Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", sanitizerScope);
       return sanitizerScope.Sanitizer;
     });
 
   XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts", "resource://gre/modules/FxAccounts.jsm");
+  XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-service;1", "nsIAlertsService");
 
   this._init();
 }
 
 /*
  * OS X has the concept of zero-window sessions and therefore ignores the
  * browser-lastwindow-close-* topics.
  */
@@ -361,16 +361,19 @@ BrowserGlue.prototype = {
         this._setSyncAutoconnectDelay();
         break;
       case "fxaccounts:onverified":
         this._showSyncStartedDoorhanger();
         break;
       case "fxaccounts:device_connected":
         this._onDeviceConnected(data);
         break;
+      case "fxaccounts:verify_login":
+        this._onVerifyLoginNotification(JSON.parse(data));
+        break;
       case "fxaccounts:device_disconnected":
         data = JSON.parse(data);
         if (data.isLocalDevice) {
           this._onDeviceDisconnected();
         }
         break;
       case "weave:engine:clients:display-uris":
         this._onDisplaySyncURIs(subject);
@@ -420,16 +423,20 @@ BrowserGlue.prototype = {
         } else if (data == "smart-bookmarks-init") {
           this.ensurePlacesDefaultQueriesInitialized().then(() => {
             Services.obs.notifyObservers(null, "test-smart-bookmarks-done");
           });
         } else if (data == "mock-fxaccounts") {
           Object.defineProperty(this, "fxAccounts", {
             value: subject.wrappedJSObject
           });
+        } else if (data == "mock-alerts-service") {
+          Object.defineProperty(this, "AlertsService", {
+            value: subject.wrappedJSObject
+          });
         }
         break;
       case "initial-migration-will-import-default-bookmarks":
         this._migrationImportsDefaultBookmarks = true;
         break;
       case "initial-migration-did-import-default-bookmarks":
         this._initPlaces(true);
         break;
@@ -518,16 +525,17 @@ BrowserGlue.prototype = {
     os.addObserver(this, "quit-application-granted");
     if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
       os.addObserver(this, "browser-lastwindow-close-requested");
       os.addObserver(this, "browser-lastwindow-close-granted");
     }
     os.addObserver(this, "weave:service:ready");
     os.addObserver(this, "fxaccounts:onverified");
     os.addObserver(this, "fxaccounts:device_connected");
+    os.addObserver(this, "fxaccounts:verify_login");
     os.addObserver(this, "fxaccounts:device_disconnected");
     os.addObserver(this, "weave:engine:clients:display-uris");
     os.addObserver(this, "session-save");
     os.addObserver(this, "places-init-complete");
     this._isPlacesInitObserver = true;
     os.addObserver(this, "places-database-locked");
     this._isPlacesLockedObserver = true;
     os.addObserver(this, "distribution-customization-complete");
@@ -564,16 +572,17 @@ BrowserGlue.prototype = {
     os.removeObserver(this, "restart-in-safe-mode");
     if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
       os.removeObserver(this, "browser-lastwindow-close-requested");
       os.removeObserver(this, "browser-lastwindow-close-granted");
     }
     os.removeObserver(this, "weave:service:ready");
     os.removeObserver(this, "fxaccounts:onverified");
     os.removeObserver(this, "fxaccounts:device_connected");
+    os.removeObserver(this, "fxaccounts:verify_login");
     os.removeObserver(this, "fxaccounts:device_disconnected");
     os.removeObserver(this, "weave:engine:clients:display-uris");
     os.removeObserver(this, "session-save");
     if (this._bookmarksBackupIdleTime) {
       this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
       delete this._bookmarksBackupIdleTime;
     }
     if (this._mediaTelemetryIdleObserver) {
@@ -1440,17 +1449,17 @@ BrowserGlue.prototype = {
         return;
       let win = RecentWindow.getMostRecentBrowserWindow();
       win.openUILinkIn(data, "tab");
     }
 
     try {
       // This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot
       // be displayed per the idl.
-      AlertsService.showAlertNotification(null, title, text,
+      this.AlertsService.showAlertNotification(null, title, text,
                                           true, url, clickCallback);
     } catch (e) {
       Cu.reportError(e);
     }
   },
 
   /**
    * Initialize Places
@@ -1706,17 +1715,17 @@ BrowserGlue.prototype = {
     let body = bundle.formatStringFromName("syncStartNotification.body2",
                                             [productName], 1);
 
     let clickCallback = (subject, topic, data) => {
       if (topic != "alertclickcallback")
         return;
       this._openPreferences("sync", { origin: "doorhanger" });
     }
-    AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
+    this.AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
   },
 
   // eslint-disable-next-line complexity
   _migrateUI: function BG__migrateUI() {
     const UI_VERSION = 49;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
 
     let currentUIVersion;
@@ -2293,22 +2302,50 @@ BrowserGlue.prototype = {
         }
       }
 
       // Specify an icon because on Windows no icon is shown at the moment
       let imageURL;
       if (AppConstants.platform == "win") {
         imageURL = "chrome://branding/content/icon64.png";
       }
-      AlertsService.showAlertNotification(imageURL, title, body, true, null, clickCallback);
+      this.AlertsService.showAlertNotification(imageURL, title, body, true, null, clickCallback);
     } catch (ex) {
       Cu.reportError("Error displaying tab(s) received by Sync: " + ex);
     }
   },
 
+  async _onVerifyLoginNotification({body, title, url}) {
+    let tab;
+    let imageURL;
+    if (AppConstants.platform == "win") {
+      imageURL = "chrome://branding/content/icon64.png";
+    }
+    let win = RecentWindow.getMostRecentBrowserWindow({private: false});
+    if (!win) {
+      win = await this._openURLInNewWindow(url);
+      let tabs = win.gBrowser.tabs;
+      tab = tabs[tabs.length - 1];
+    } else {
+      tab = win.gBrowser.addTab(url);
+    }
+    tab.setAttribute("attention", true);
+    let clickCallback = (subject, topic, data) => {
+      if (topic != "alertclickcallback")
+        return;
+      win.gBrowser.selectedTab = tab;
+    };
+
+    try {
+      this.AlertsService.showAlertNotification(imageURL, title, body, true, null, clickCallback);
+    } catch (ex) {
+      Cu.reportError("Error notifying of a verify login event: " + ex);
+    }
+  },
+
   _onDeviceConnected(deviceName) {
     let accountsBundle = Services.strings.createBundle(
       "chrome://browser/locale/accounts.properties"
     );
     let title = accountsBundle.GetStringFromName("deviceConnectedTitle");
     let body = accountsBundle.formatStringFromName("deviceConnectedBody" +
                                                    (deviceName ? "" : ".noDeviceName"),
                                                    [deviceName], 1);
@@ -2321,33 +2358,33 @@ BrowserGlue.prototype = {
       if (!win) {
         this._openURLInNewWindow(url);
       } else {
         win.gBrowser.addTab(url);
       }
     };
 
     try {
-      AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
+      this.AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
     } catch (ex) {
       Cu.reportError("Error notifying of a new Sync device: " + ex);
     }
   },
 
   _onDeviceDisconnected() {
     let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
     let title = bundle.GetStringFromName("deviceDisconnectedNotification.title");
     let body = bundle.GetStringFromName("deviceDisconnectedNotification.body");
 
     let clickCallback = (subject, topic, data) => {
       if (topic != "alertclickcallback")
         return;
       this._openPreferences("sync", { origin: "devDisconnectedAlert"});
     }
-    AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
+    this.AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
   },
 
   _handleFlashHang() {
     ++this._flashHangCount;
     if (this._flashHangCount < 2) {
       return;
     }
     // protected mode only applies to win32
--- a/browser/components/originattributes/test/browser/browser_sharedworker.js
+++ b/browser/components/originattributes/test/browser/browser_sharedworker.js
@@ -7,16 +7,17 @@ const TEST_PATH = TEST_DOMAIN + "browser
 const TEST_PAGE = TEST_PATH + "file_sharedworker.html";
 
 async function getResultFromSharedworker(aBrowser) {
   let response = await ContentTask.spawn(aBrowser, null, async function() {
     let worker = new content.SharedWorker("file_sharedworker.js", "isolationSharedWorkerTest");
 
     let result = await new Promise(resolve => {
       worker.port.onmessage = function(e) {
+        // eslint-disable-next-line no-unsanitized/property
         content.document.getElementById("display").innerHTML = e.data;
         resolve(e.data);
       };
     });
 
     return result;
   });
 
--- a/browser/components/originattributes/test/browser/file_broadcastChannel.html
+++ b/browser/components/originattributes/test/browser/file_broadcastChannel.html
@@ -5,12 +5,13 @@
   <title>Page broadcast channel creator for first party isolation</title>
 </head>
 <body>
   <div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
   <iframe id="iframe" src="file_broadcastChanneliFrame.html"></iframe>>
 <script type="text/javascript">
 let bc = new BroadcastChannel("testBroadcastChannel");
 bc.onmessage = function(e) {
+  // eslint-disable-next-line no-unsanitized/property
   document.getElementById("display").innerHTML = e.data;
 };
 </script>
 </body>
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -1128,25 +1128,25 @@ this.PlacesUIUtils = {
     // This is the list of the left pane queries.
     let queries = {
       "PlacesRoot": { title: "" },
       "History": { title: this.getString("OrganizerQueryHistory") },
       "Downloads": { title: this.getString("OrganizerQueryDownloads") },
       "Tags": { title: this.getString("OrganizerQueryTags") },
       "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") },
       "BookmarksToolbar":
-        { title: null,
+        { title: "",
           concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"),
           concreteId: PlacesUtils.toolbarFolderId },
       "BookmarksMenu":
-        { title: null,
+        { title: "",
           concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
           concreteId: PlacesUtils.bookmarksMenuFolderId },
       "UnfiledBookmarks":
-        { title: null,
+        { title: "",
           concreteTitle: PlacesUtils.getString("OtherBookmarksFolderTitle"),
           concreteId: PlacesUtils.unfiledBookmarksFolderId },
     };
     // All queries but PlacesRoot.
     const EXPECTED_QUERY_COUNT = 7;
 
     // Removes an item and associated annotations, ignoring eventual errors.
     function safeRemoveItem(aItemId) {
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -506,17 +506,20 @@ var gEditItemOverlay = {
     // Optimize the trivial cases (which are actually the most common).
     if (inputTags.length == 0 && aCurrentTags.length == 0)
       return { newTags: [], removedTags: [] };
     if (inputTags.length == 0)
       return { newTags: [], removedTags: aCurrentTags };
     if (aCurrentTags.length == 0)
       return { newTags: inputTags, removedTags: [] };
 
-    let removedTags = aCurrentTags.filter(t => !inputTags.includes(t));
+    // Do not remove tags that may be reinserted with a different
+    // case, since the tagging service may handle those more efficiently.
+    let lcInputTags = inputTags.map(t => t.toLowerCase());
+    let removedTags = aCurrentTags.filter(t => !lcInputTags.includes(t.toLowerCase()));
     let newTags = inputTags.filter(t => !aCurrentTags.includes(t));
     return { removedTags, newTags };
   },
 
   // Adds and removes tags for one or more uris.
   async _setTagsFromTagsInputField(aCurrentTags, aURIs) {
     let { removedTags, newTags } = this._getTagsChanges(aCurrentTags);
     if (removedTags.length + newTags.length == 0)
@@ -532,24 +535,24 @@ var gEditItemOverlay = {
       }
 
       PlacesUtils.transactionManager.doTransaction(
         new PlacesAggregatedTransaction("Update tags", txns));
       return true;
     }
 
     let setTags = async function() {
+      if (removedTags.length > 0) {
+        await PlacesTransactions.Untag({ urls: aURIs, tags: removedTags })
+                                .transact();
+      }
       if (newTags.length > 0) {
         await PlacesTransactions.Tag({ urls: aURIs, tags: newTags })
                                 .transact();
       }
-      if (removedTags.length > 0) {
-        await PlacesTransactions.Untag({ urls: aURIs, tags: removedTags })
-                          .transact();
-      }
     };
 
     // Only in the library info-pane it's safe (and necessary) to batch these.
     // TODO bug 1093030: cleanup this mess when the bookmarksProperties dialog
     // and star UI code don't "run a batch in the background".
     if (window.document.documentElement.id == "places")
       PlacesTransactions.batch(setTags).catch(Components.utils.reportError);
     else
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js
@@ -1,25 +1,31 @@
 "use strict"
 
 const TEST_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser/keyword_form.html";
 
+function closeHandler(dialogWin) {
+  let savedItemId = dialogWin.gEditItemOverlay.itemId;
+  return PlacesTestUtils.waitForNotification("onItemRemoved",
+                                             itemId => itemId === savedItemId);
+}
+
 add_task(async function() {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: TEST_URL,
   }, async function(browser) {
     // We must wait for the context menu code to build metadata.
     await openContextMenuForContentSelector(browser, '#form1 > input[name="search"]');
 
     await withBookmarksDialog(true, AddKeywordForSearchField, async function(dialogWin) {
       let acceptBtn = dialogWin.document.documentElement.getButton("accept");
       ok(acceptBtn.disabled, "Accept button is disabled");
 
-      let promiseKeywordNotification = promiseBookmarksNotification(
+      let promiseKeywordNotification = PlacesTestUtils.waitForNotification(
         "onItemChanged", (itemId, prop, isAnno, val) => prop == "keyword" && val == "kw");
 
       fillBookmarkTextField("editBMPanel_keywordField", "kw", dialogWin);
 
       ok(!acceptBtn.disabled, "Accept button is enabled");
 
       // The dialog is instant apply.
       await promiseKeywordNotification;
@@ -38,17 +44,17 @@ add_task(async function() {
       info("Check the charset has been saved");
       let charset = await PlacesUtils.getCharsetForURI(NetUtil.newURI(TEST_URL));
       is(charset, "windows-1252", "charset is correct");
 
       // Now check getShortcutOrURI.
       let data = await getShortcutOrURIAndPostData("kw test");
       is(getPostDataString(data.postData), "accenti=\u00E0\u00E8\u00EC\u00F2\u00F9&search=test", "getShortcutOrURI POST data is correct");
       is(data.url, TEST_URL, "getShortcutOrURI URL is correct");
-    });
+    }, closeHandler);
   });
 });
 
 add_task(async function reopen_same_field() {
   await PlacesUtils.keywords.insert({
     url: TEST_URL,
     keyword: "kw",
     postData: "accenti%3D%E0%E8%EC%F2%F9&search%3D%25s"
@@ -59,23 +65,23 @@ add_task(async function reopen_same_fiel
   // Reopening on the same input field should show the existing keyword.
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: TEST_URL,
   }, async function(browser) {
     // We must wait for the context menu code to build metadata.
     await openContextMenuForContentSelector(browser, '#form1 > input[name="search"]');
 
-    await withBookmarksDialog(true, AddKeywordForSearchField, function(dialogWin) {
+    await withBookmarksDialog(true, AddKeywordForSearchField, async function(dialogWin) {
       let acceptBtn = dialogWin.document.documentElement.getButton("accept");
       ok(acceptBtn.disabled, "Accept button is disabled");
 
       let elt = dialogWin.document.getElementById("editBMPanel_keywordField");
-      is(elt.value, "kw");
-    });
+      await BrowserTestUtils.waitForCondition(() => elt.value == "kw", "Keyword should be the previous value");
+    }, closeHandler);
   });
 });
 
 add_task(async function open_other_field() {
   await PlacesUtils.keywords.insert({
     url: TEST_URL,
     keyword: "kw2",
     postData: "search%3D%25s"
@@ -93,17 +99,17 @@ add_task(async function open_other_field
     await openContextMenuForContentSelector(browser, '#form2 > input[name="search"]');
 
     await withBookmarksDialog(true, AddKeywordForSearchField, function(dialogWin) {
       let acceptBtn = dialogWin.document.documentElement.getButton("accept");
       ok(acceptBtn.disabled, "Accept button is disabled");
 
       let elt = dialogWin.document.getElementById("editBMPanel_keywordField");
       is(elt.value, "");
-    });
+    }, closeHandler);
   });
 });
 
 function getPostDataString(stream) {
   let sis = Cc["@mozilla.org/scriptableinputstream;1"]
               .createInstance(Ci.nsIScriptableInputStream);
   sis.init(stream);
   return sis.read(stream.available()).split("\n").pop();
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js
@@ -26,34 +26,34 @@ add_task(async function() {
   let fooTag = tagsContainer.getChild(0);
   let tagNode = fooTag;
   tree.selectNode(fooTag);
   Assert.equal(tagNode.title, "tag1", "tagNode title is correct");
 
   Assert.ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
             "'placesCmd_show:info' on current selected node is enabled");
 
-  let promiseTitleResetNotification = promiseBookmarksNotification(
+  let promiseTitleResetNotification = PlacesTestUtils.waitForNotification(
       "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag1");
 
   await withBookmarksDialog(
     true,
     function openDialog() {
       tree.controller.doCommand("placesCmd_show:info");
     },
     async function test(dialogWin) {
       // Check that the dialog is not read-only.
       Assert.ok(!dialogWin.gEditItemOverlay.readOnly, "Dialog should not be read-only");
 
       // Check that name picker is not read only
       let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
       Assert.ok(!namepicker.readOnly, "Name field should not be read-only");
       Assert.equal(namepicker.value, "tag1", "Node title is correct");
 
-      let promiseTitleChangeNotification = promiseBookmarksNotification(
+      let promiseTitleChangeNotification = PlacesTestUtils.waitForNotification(
           "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag2");
 
       fillBookmarkTextField("editBMPanel_namePicker", "tag2", dialogWin);
 
       await promiseTitleChangeNotification;
 
       Assert.equal(namepicker.value, "tag2", "Node title has been properly edited");
 
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
@@ -29,14 +29,14 @@ add_task(async function() {
            "Node title is correct");
         // Blur the field and ensure root's name has not been changed.
         namepicker.blur();
         is(namepicker.value,
            PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId),
            "Root title is correct");
         // Check the shortcut's title.
         let bookmark = await PlacesUtils.bookmarks.fetch(tree.selectedNode.bookmarkGuid);
-        is(bookmark.title, null,
+        is(bookmark.title, "",
            "Shortcut title is null");
       }
     );
   });
 });
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -393,28 +393,26 @@ function open_properties_dialog() {
        "We have a places node selected: " + tree.selectedNode.title);
 
     // Wait for the Properties dialog.
     function windowObserver(aSubject, aTopic, aData) {
       if (aTopic != "domwindowopened")
         return;
       ww.unregisterNotification(windowObserver);
       let observerWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
-      waitForFocus(() => {
-        // Windows has been loaded, execute our test now.
-        executeSoon(function() {
-          // Ensure overlay is loaded
-          ok(observerWindow.gEditItemOverlay.initialized, "EditItemOverlay is initialized");
-          gCurrentTest.window = observerWindow;
-          try {
-            gCurrentTest.run();
-          } catch (ex) {
-            ok(false, "An error occured during test run: " + ex.message);
-          }
-        });
+      waitForFocus(async () => {
+        // Ensure overlay is loaded
+        await BrowserTestUtils.waitForCondition(
+          () => observerWindow.gEditItemOverlay.initialized, "EditItemOverlay is initialized");
+        gCurrentTest.window = observerWindow;
+        try {
+          gCurrentTest.run();
+        } catch (ex) {
+          ok(false, "An error occured during test run: " + ex.message);
+        }
       }, observerWindow);
     }
     ww.registerNotification(windowObserver);
 
     var command = null;
     switch (gCurrentTest.action) {
       case ACTION_EDIT:
         command = "placesCmd_show:info";
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -266,20 +266,23 @@ function isToolbarVisible(aToolbar) {
 
 /**
  * Executes a task after opening the bookmarks dialog, then cancels the dialog.
  *
  * @param autoCancel
  *        whether to automatically cancel the dialog at the end of the task
  * @param openFn
  *        generator function causing the dialog to open
- * @param task
+ * @param taskFn
  *        the task to execute once the dialog is open
+ * @param closeFn
+ *        A function to be used to wait for pending work when the dialog is
+ *        closing. It is passed the dialog window handle and should return a promise.
  */
-var withBookmarksDialog = async function(autoCancel, openFn, taskFn) {
+var withBookmarksDialog = async function(autoCancel, openFn, taskFn, closeFn) {
   let closed = false;
   let dialogPromise = new Promise(resolve => {
     Services.ww.registerNotification(function winObserver(subject, topic, data) {
       if (topic == "domwindowopened") {
         let win = subject.QueryInterface(Ci.nsIDOMWindow);
         win.addEventListener("load", function() {
           ok(win.location.href.startsWith("chrome://browser/content/places/bookmarkProperties"),
              "The bookmark properties dialog is open");
@@ -312,25 +315,34 @@ var withBookmarksDialog = async function
   let elt = doc.querySelector("textbox:not([collapsed=true])");
   if (elt) {
     info("waiting for focus on the first textfield");
     await waitForCondition(() => doc.activeElement == elt.inputField,
                            "The first non collapsed textbox should have been focused");
   }
 
   info("withBookmarksDialog: executing the task");
+
+  let closePromise = () => Promise.resolve();
+  if (closeFn) {
+    closePromise = closeFn(dialogWin);
+  }
+
   try {
     await taskFn(dialogWin);
   } finally {
     if (!closed) {
       if (!autoCancel) {
         ok(false, "The test should have closed the dialog!");
       }
       info("withBookmarksDialog: canceling the dialog");
+
       doc.documentElement.cancelDialog();
+
+      await closePromise;
     }
   }
 };
 
 /**
  * Opens the contextual menu on the element pointed by the given selector.
  *
  * @param selector
--- a/browser/components/places/tests/browser/pageopeningwindow.html
+++ b/browser/components/places/tests/browser/pageopeningwindow.html
@@ -1,9 +1,11 @@
 <meta charset="UTF-8">
-Hi, I was opened via a <script>document.write(location.search ?
+Hi, I was opened via a <script>
+// eslint-disable-next-line no-unsanitized/method
+document.write(location.search ?
   "popup call from the opened window... uh oh, that shouldn't happen!" :
   "bookmarklet, and I will open a new window myself.")</script><br>
 <script>
   if (!location.search) {
     open(location.href + "?donotopen=true", "_blank");
   }
 </script>
--- a/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
+++ b/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
@@ -23,27 +23,26 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <vbox id="editBookmarkPanelContent"/>
 
   <script type="application/javascript">
   <![CDATA[
-
     function runTest() {
       SimpleTest.waitForExplicitFinish();
-
       (async function() {
         let testTag = "foo";
         let testTagUpper = "Foo";
         let testURI = Services.io.newURI("http://www.example.com/");
 
         // Add a bookmark.
         let bm = await PlacesUtils.bookmarks.insert({
           parentGuid: PlacesUtils.bookmarks.toolbarGuid,
@@ -55,24 +54,33 @@
 
         // Init panel
         ok(gEditItemOverlay, "gEditItemOverlay is in context");
         let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
         gEditItemOverlay.initPanel({ node });
 
         // add a tag
         document.getElementById("editBMPanel_tagsField").value = testTag;
+        let promiseNotification = PlacesTestUtils.waitForNotification(
+          "onItemChanged", (id, property) => property == "tags");
         gEditItemOverlay.onTagsFieldChange();
+        await promiseNotification;
 
         // test that the tag has been added in the backend
         is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTag, "tags match");
 
         // change the tag
         document.getElementById("editBMPanel_tagsField").value = testTagUpper;
+        // The old sync API doesn't notify a tags change, and fixing it would be
+        // quite complex, so we just wait for a title change until tags are
+        // refactored.
+        promiseNotification = PlacesTestUtils.waitForNotification(
+          "onItemChanged", (id, property) => property == "title");
         gEditItemOverlay.onTagsFieldChange();
+        await promiseNotification;
 
         // test that the tag has been added in the backend
         is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTagUpper, "tags match");
 
         // Cleanup.
         PlacesUtils.tagging.untagURI(testURI, [testTag]);
         await PlacesUtils.bookmarks.remove(bm.guid);
       })().then(() => SimpleTest.finish());
--- a/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
+++ b/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
@@ -22,55 +22,49 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="Bug 631374 - Editing tags in the selector scrolls up the listbox"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <vbox id="editBookmarkPanelContent"/>
 
   <script type="application/javascript">
   <![CDATA[
-
      /**
       * This test checks that editing tags doesn't scroll the tags selector
       * listbox to wrong positions.
       */
 
     function runTest() {
       SimpleTest.waitForExplicitFinish();
-
       (async function() {
-        let bs = PlacesUtils.bookmarks;
-
+        await PlacesUtils.bookmarks.eraseEverything();
         let tags = ["a", "b", "c", "d", "e", "f", "g",
                     "h", "i", "l", "m", "n", "o", "p"];
 
         // Add a bookmark and tag it.
         let uri1 = Services.io.newURI("http://www1.mozilla.org/");
-        let bm1 = await bs.insert({
-          parentGuid: bs.toolbarGuid,
-          index: bs.DEFAULT_INDEX,
-          type: bs.TYPE_BOOKMARK,
+        let bm1 = await PlacesUtils.bookmarks.insert({
+          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
           title: "mozilla",
           url: uri1.spec
         });
         PlacesUtils.tagging.tagURI(uri1, tags);
 
         // Add a second bookmark so that tags won't disappear when unchecked.
         let uri2 = Services.io.newURI("http://www2.mozilla.org/");
-        let bm2 = await bs.insert({
-          parentGuid: bs.toolbarGuid,
-          index: bs.DEFAULT_INDEX,
-          type: bs.TYPE_BOOKMARK,
+        let bm2 = await PlacesUtils.bookmarks.insert({
+          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
           title: "mozilla",
           url: uri2.spec
         });
         PlacesUtils.tagging.tagURI(uri2, tags);
 
         // Init panel.
         ok(gEditItemOverlay, "gEditItemOverlay is in context");
         let node1 = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm1);
@@ -88,49 +82,58 @@
 
           tagsSelector.ensureElementIsVisible(listItem);
           let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
 
           ok(listItem.checked, "Item is checked " + i);
           let selectedTag = listItem.label;
 
           // Uncheck the tag.
+          let promiseNotification = PlacesTestUtils.waitForNotification(
+            "onItemChanged", (id, property) => property == "tags");
           listItem.checked = false;
+          await promiseNotification;
           is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
              "Scroll position did not change");
 
           // The listbox is rebuilt, so we have to get the new element.
           let newItem = tagsSelector.selectedItem;
           isnot(newItem, null, "Valid new listItem found");
           ok(!newItem.checked, "New listItem is unchecked " + i);
           is(newItem.label, selectedTag, "Correct tag is still selected");
 
           // Check the tag.
+          promiseNotification = PlacesTestUtils.waitForNotification(
+            "onItemChanged", (id, property) => property == "tags");
           newItem.checked = true;
+          await promiseNotification;
           is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
              "Scroll position did not change");
         }
 
         // Remove the second bookmark, then nuke some of the tags.
-        await bs.remove(bm2.guid);
+        await PlacesUtils.bookmarks.remove(bm2);
 
         // Doing this backwords tests more interesting paths.
         for (let i = tags.length - 1; i >= 0 ; i -= 2) {
           tagsSelector.selectedIndex = i;
           let listItem = tagsSelector.selectedItem;
           isnot(listItem, null, "Valid listItem found");
 
           tagsSelector.ensureElementIsVisible(listItem);
           let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()];
 
           ok(listItem.checked, "Item is checked " + i);
           let selectedTag = listItem.label;
 
           // Uncheck the tag.
+          let promiseNotification = PlacesTestUtils.waitForNotification(
+            "onItemChanged", (id, property) => property == "tags");
           listItem.checked = false;
+          await promiseNotification;
 
           // Ensure the first visible tag is still visible in the list.
           let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
           let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() -1;
           let expectedTagIndex = tags.indexOf(firstVisibleTag);
           ok(expectedTagIndex >= firstVisibleIndex &&
              expectedTagIndex <= lastVisibleIndex,
              "Scroll position is correct");
@@ -140,31 +143,29 @@
           isnot(newItem, null, "Valid new listItem found");
           ok(newItem.checked, "New listItem is checked " + i);
           is(tagsSelector.selectedItem.label,
              tags[Math.min(i + 1, tags.length - 2)],
              "The next tag is now selected");
         }
 
         // Cleanup.
-        await bs.remove(bm1.guid);
-      })().then(SimpleTest.finish).catch(alert);
+        await PlacesUtils.bookmarks.remove(bm1);
+      })().catch(ex => ok(false, "test failed: " + ex)).then(SimpleTest.finish);
     }
 
     function openTagSelector() {
       // Wait for the tags selector to be open.
       let promise = new Promise(resolve => {
         let row = document.getElementById("editBMPanel_tagsSelectorRow");
         row.addEventListener("DOMAttrModified", function onAttrModified() {
           row.removeEventListener("DOMAttrModified", onAttrModified);
           resolve();
         });
       });
-
       // Open the tags selector.
       document.getElementById("editBMPanel_tagsSelectorExpander").doCommand();
-
       return promise;
     }
   ]]>
   </script>
 
 </window>
--- a/browser/components/preferences/in-content-new/findInPage.js
+++ b/browser/components/preferences/in-content-new/findInPage.js
@@ -262,16 +262,17 @@ var gSearchResultsPane = {
 
         let strings = this.strings;
 
         document.getElementById("sorry-message").textContent = AppConstants.platform == "win" ?
           strings.getFormattedString("searchResults.sorryMessageWin", [this.query]) :
           strings.getFormattedString("searchResults.sorryMessageUnix", [this.query]);
         let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "preferences";
         let brandName = document.getElementById("bundleBrand").getString("brandShortName");
+        // eslint-disable-next-line no-unsanitized/property
         document.getElementById("need-help").innerHTML =
           strings.getFormattedString("searchResults.needHelp2", [helpUrl, brandName]);
       } else {
         // Creating tooltips for all the instances found
         this.listSearchTooltips.forEach((anchorNode) => this.createSearchTooltip(anchorNode, this.query));
       }
     } else {
       this.searchResultsCategory.hidden = true;
@@ -407,50 +408,53 @@ var gSearchResultsPane = {
    * @param String query
    *    Word or words that are being searched for
    */
   createSearchTooltip(anchorNode, query) {
     let searchTooltip = anchorNode.ownerDocument.createElement("span");
     searchTooltip.setAttribute("class", "search-tooltip");
     searchTooltip.textContent = query;
 
-    anchorNode.setAttribute("data-has-tooltip", "true");
+    // Set tooltipNode property to track corresponded tooltip node.
+    anchorNode.tooltipNode = searchTooltip;
     anchorNode.parentElement.classList.add("search-tooltip-parent");
     anchorNode.parentElement.appendChild(searchTooltip);
 
     this.calculateTooltipPosition(anchorNode);
   },
 
   calculateTooltipPosition(anchorNode) {
-    let searchTooltip = anchorNode.parentElement.querySelector(":scope > .search-tooltip");
-
+    let searchTooltip = anchorNode.tooltipNode;
     // In order to get the up-to-date position of each of the nodes that we're
     // putting tooltips on, we have to flush layout intentionally, and that
     // this is the result of a XUL limitation (bug 1363730).
     let anchorRect = anchorNode.getBoundingClientRect();
     let tooltipRect = searchTooltip.getBoundingClientRect();
     let parentRect = anchorNode.parentElement.getBoundingClientRect();
 
-    let offSet = (anchorRect.width / 2) - (tooltipRect.width / 2);
+    let offSetLeft = (anchorRect.width / 2) - (tooltipRect.width / 2);
     let relativeOffset = anchorRect.left - parentRect.left;
-    offSet += relativeOffset > 0 ? relativeOffset : 0;
+    offSetLeft += relativeOffset > 0 ? relativeOffset : 0;
+    // 20.5 is reserved for tooltip position
+    let offSetTop = anchorRect.top - parentRect.top - 20.5;
 
-    searchTooltip.style.setProperty("left", `${offSet}px`);
+    searchTooltip.style.setProperty("left", `${offSetLeft}px`);
+    searchTooltip.style.setProperty("top", `${offSetTop}px`);
   },
 
   /**
    * Remove all search tooltips that were created.
    */
   removeAllSearchTooltips() {
     let searchTooltips = Array.from(document.querySelectorAll(".search-tooltip"));
     for (let searchTooltip of searchTooltips) {
       searchTooltip.parentElement.classList.remove("search-tooltip-parent");
       searchTooltip.remove();
     }
-    this.listSearchTooltips.forEach((anchorNode) => anchorNode.removeAttribute("data-has-tooltip"));
+    this.listSearchTooltips.forEach((anchorNode) => anchorNode.tooltipNode.remove());
     this.listSearchTooltips.clear();
   },
 
   /**
    * Remove all indicators on menuitem.
    */
   removeAllSearchMenuitemIndicators() {
     this.listSearchMenuitemIndicators.forEach((node) => node.removeAttribute("indicator"));
--- a/browser/components/preferences/in-content-new/subdialogs.js
+++ b/browser/components/preferences/in-content-new/subdialogs.js
@@ -333,26 +333,22 @@ SubDialog.prototype = {
     }
 
     this._trapFocus();
 
     // Search within main document and highlight matched keyword.
     gSearchResultsPane.searchWithinNode(this._titleElement, gSearchResultsPane.query);
 
     // Search within sub-dialog document and highlight matched keyword.
-    let subDialogsChildren = this._frame.contentDocument
-      .querySelectorAll(":scope > *:not([data-hidden-from-search])");
-
-    for (let i = 0; i < subDialogsChildren.length; i++) {
-      gSearchResultsPane.searchWithinNode(subDialogsChildren[i], gSearchResultsPane.query);
-    }
+    gSearchResultsPane.searchWithinNode(this._frame.contentDocument.firstElementChild,
+      gSearchResultsPane.query);
 
     // Creating tooltips for all the instances found
     for (let node of gSearchResultsPane.listSearchTooltips) {
-      if (!node.getAttribute("data-has-tooltip")) {
+      if (!node.tooltipNode) {
         gSearchResultsPane.createSearchTooltip(node, gSearchResultsPane.query);
       }
     }
   },
 
   _onResize(mutations) {
     let frame = this._frame;
     // The width and height styles are needed for the initial
--- a/browser/components/resistfingerprinting/test/browser/file_navigator.html
+++ b/browser/components/resistfingerprinting/test/browser/file_navigator.html
@@ -18,16 +18,17 @@
     result["vendor"] = navigator.vendor;
     result["vendorSub"] = navigator.vendorSub;
     result["mimeTypesLength"] = navigator.mimeTypes.length;
     result["pluginsLength"] = navigator.plugins.length;
     result["oscpu"] = navigator.oscpu;
     result["buildID"] = navigator.buildID;
     result["hardwareConcurrency"] = navigator.hardwareConcurrency;
 
+    // eslint-disable-next-line no-unsanitized/property
     document.getElementById("result").innerHTML = JSON.stringify(result);
   }
 </script>
 </head>
 <body onload="collect();">
 <p id="result"></p>
 </body>
 </html>
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3593,19 +3593,17 @@ var SessionStoreInternal = {
     } else {
       tabbrowser.showTab(tab);
     }
 
     if (!!tabData.muted != browser.audioMuted) {
       tab.toggleMuteAudio(tabData.muteReason);
     }
 
-    if (tabData.mediaBlocked) {
-      browser.blockMedia();
-    } else {
+    if (!tabData.mediaBlocked) {
       browser.resumeMedia();
     }
 
     if (tabData.lastAccessed) {
       tab.updateLastAccessed(tabData.lastAccessed);
     }
 
     if ("attributes" in tabData) {
--- a/browser/components/sessionstore/test/browser_459906.js
+++ b/browser/components/sessionstore/test/browser_459906.js
@@ -15,16 +15,17 @@ function test() {
   let tab = BrowserTestUtils.addTab(gBrowser, testURL);
   tab.linkedBrowser.addEventListener("load", function(aEvent) {
     // wait for all frames to load completely
     if (frameCount++ < 2)
       return;
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
 
     let iframes = tab.linkedBrowser.contentWindow.frames;
+    // eslint-disable-next-line no-unsanitized/property
     iframes[1].document.body.innerHTML = uniqueValue;
 
     frameCount = 0;
     let tab2 = gBrowser.duplicateTab(tab);
     tab2.linkedBrowser.addEventListener("load", function(eventTab2) {
       // wait for all frames to load (and reload!) completely
       if (frameCount++ < 2)
         return;
--- a/browser/components/syncedtabs/SyncedTabsDeckView.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckView.js
@@ -70,16 +70,17 @@ SyncedTabsDeckView.prototype = {
       let link = this._doc.createElement("a");
       link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
       link.className = `${os}-link text-link`;
       link.setAttribute("href", "#");
       return link.outerHTML;
     });
     // Put it all together...
     let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
+    // eslint-disable-next-line no-unsanitized/property
     this.container.querySelector(".device-promo").innerHTML = contents;
   },
 
   destroy() {
     this._tabListComponent.uninit();
     this.container.remove();
   },
 
--- a/browser/components/uitour/UITour-lib.js
+++ b/browser/components/uitour/UITour-lib.js
@@ -476,16 +476,23 @@ if (typeof Mozilla == "undefined") {
    *                                                        without user interaction.
    * @property {Boolean} defaultBrowser - Whether the application is the default browser. Since Fx40.
    * @property {String} defaultUpdateChannel - Update channel e.g. 'release', 'beta', 'aurora',
    *                                           'nightly', 'default'
    *                                           (self-built or automated testing builds)
    * @property {String} distribution - Contains the distributionId property. This value will be
    *                                   "default" in most cases but can differ for repack or
    *                                   funnelcake builds. Since Fx48
+   * @property {Number} profileCreatedWeeksAgo - The number of weeks since the profile was created,
+   *                                             starting from 0 for profiles dating less than
+   *                                             seven days old. Since Fx56.
+   * @property {Number} profileResetWeeksAgo - The number of weeks since the profile was last reset,
+   *                                           starting from 0 for profiles reset less than seven
+   *                                           days ago. If the profile has never been reset it
+   *                                           returns null. Since Fx56.
    * @property {String} version - Version string e.g. "48.0a2"
    * @since 35
    */
 
   /**
    * @summary Search service configuration.
    *
    * @description From version 34 through 42 inclusive, a string was returned for the 'selectedSearchEngine'
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -24,16 +24,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
+  "resource://gre/modules/ProfileAge.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
   "resource:///modules/ReaderParent.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure", "browser.photon.structure.enabled");
 
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL      = "browser.uitour.loglevel";
 const PREF_SEENPAGEIDS    = "browser.uitour.seenPageIDs";
@@ -1478,53 +1480,17 @@ this.UITour = {
     aPanel.hidden = true;
     aPanel.clientWidth; // flush
     aPanel.hidden = false;
   },
 
   getConfiguration(aMessageManager, aWindow, aConfiguration, aCallbackID) {
     switch (aConfiguration) {
       case "appinfo":
-        let props = ["defaultUpdateChannel", "version"];
-        let appinfo = {};
-        props.forEach(property => appinfo[property] = Services.appinfo[property]);
-
-        // Identifier of the partner repack, as stored in preference "distribution.id"
-        // and included in Firefox and other update pings. Note this is not the same as
-        // Services.appinfo.distributionID (value of MOZ_DISTRIBUTION_ID is set at build time).
-        let distribution =
-          Services.prefs.getDefaultBranch("distribution.").getCharPref("id", "default");
-        appinfo["distribution"] = distribution;
-
-        let isDefaultBrowser = null;
-        try {
-          let shell = aWindow.getShellService();
-          if (shell) {
-            isDefaultBrowser = shell.isDefaultBrowser(false);
-          }
-        } catch (e) {}
-        appinfo["defaultBrowser"] = isDefaultBrowser;
-
-        let canSetDefaultBrowserInBackground = true;
-        if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2") ||
-            AppConstants.isPlatformAndVersionAtLeast("macosx", "10.10")) {
-          canSetDefaultBrowserInBackground = false;
-        } else if (AppConstants.platform == "linux") {
-          // The ShellService may not exist on some versions of Linux.
-          try {
-            aWindow.getShellService();
-          } catch (e) {
-            canSetDefaultBrowserInBackground = null;
-          }
-        }
-
-        appinfo["canSetDefaultBrowserInBackground"] =
-          canSetDefaultBrowserInBackground;
-
-        this.sendPageCallback(aMessageManager, aCallbackID, appinfo);
+        this.getAppInfo(aMessageManager, aWindow, aCallbackID);
         break;
       case "availableTargets":
         this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
         break;
       case "search":
       case "selectedSearchEngine":
         Services.search.init(rv => {
           let data;
@@ -1571,16 +1537,74 @@ this.UITour = {
         } catch (e) {}
         break;
       default:
         log.error("setConfiguration: Unknown configuration requested: " + aConfiguration);
         break;
     }
   },
 
+  getAppInfo(aMessageManager, aWindow, aCallbackID) {
+    (async() => {
+      let props = ["defaultUpdateChannel", "version"];
+      let appinfo = {};
+      props.forEach(property => appinfo[property] = Services.appinfo[property]);
+
+      // Identifier of the partner repack, as stored in preference "distribution.id"
+      // and included in Firefox and other update pings. Note this is not the same as
+      // Services.appinfo.distributionID (value of MOZ_DISTRIBUTION_ID is set at build time).
+      let distribution =
+          Services.prefs.getDefaultBranch("distribution.").getCharPref("id", "default");
+      appinfo["distribution"] = distribution;
+
+      let isDefaultBrowser = null;
+      try {
+        let shell = aWindow.getShellService();
+        if (shell) {
+          isDefaultBrowser = shell.isDefaultBrowser(false);
+        }
+      } catch (e) {}
+      appinfo["defaultBrowser"] = isDefaultBrowser;
+
+      let canSetDefaultBrowserInBackground = true;
+      if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2") ||
+          AppConstants.isPlatformAndVersionAtLeast("macosx", "10.10")) {
+        canSetDefaultBrowserInBackground = false;
+      } else if (AppConstants.platform == "linux") {
+        // The ShellService may not exist on some versions of Linux.
+        try {
+          aWindow.getShellService();
+        } catch (e) {
+          canSetDefaultBrowserInBackground = null;
+        }
+      }
+
+      appinfo["canSetDefaultBrowserInBackground"] =
+        canSetDefaultBrowserInBackground;
+
+      // Expose Profile creation and last reset dates in weeks.
+      const ONE_WEEK = 7 * 24 * 60 * 60 * 1000;
+      let profileAge = new ProfileAge(null, null);
+      let createdDate = await profileAge.created;
+      let resetDate = await profileAge.reset;
+      let createdWeeksAgo = Math.floor((Date.now() - createdDate) / ONE_WEEK);
+      let resetWeeksAgo = null;
+      if (resetDate) {
+        resetWeeksAgo = Math.floor((Date.now() - resetDate) / ONE_WEEK);
+      }
+      appinfo["profileCreatedWeeksAgo"] = createdWeeksAgo;
+      appinfo["profileResetWeeksAgo"] = resetWeeksAgo;
+
+      this.sendPageCallback(aMessageManager, aCallbackID, appinfo);
+    })().catch(err => {
+      log.error(err);
+      this.sendPageCallback(aMessageManager, aCallbackID, {});
+    })
+  },
+
   getAvailableTargets(aMessageManager, aChromeWindow, aCallbackID) {
     (async () => {
       let window = aChromeWindow;
       let data = this.availableTargetsCache.get(window);
       if (data) {
         log.debug("getAvailableTargets: Using cached targets list", data.targets.join(","));
         this.sendPageCallback(aMessageManager, aCallbackID, data);
         return;
--- a/browser/components/uitour/test/browser_UITour.js
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -3,16 +3,18 @@
 
 "use strict";
 
 var gTestTab;
 var gContentAPI;
 var gContentWindow;
 
 Components.utils.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
+Components.utils.import("resource://gre/modules/ProfileAge.jsm", this);
+
 
 function test() {
   UITourTest();
 }
 
 var tests = [
   function test_untrusted_host(done) {
     loadUITourTestPage(function() {
@@ -298,16 +300,31 @@ var tests = [
       gContentAPI.getConfiguration("appinfo", (result2) => {
         ok(typeof(result2.distribution) !== "undefined", "Check distribution isn't undefined.");
         is(result2.distribution, testDistributionID, "Should have the distribution as set in preference.");
 
         done();
       });
     });
   },
+  function test_getConfigurationProfileAge(done) {
+    gContentAPI.getConfiguration("appinfo", (result) => {
+      ok(typeof(result.profileCreatedWeeksAgo) === "number", "profileCreatedWeeksAgo should be number.");
+      ok(result.profileResetWeeksAgo === null, "profileResetWeeksAgo should be null.");
+
+      // Set profile reset date to 15 days ago.
+      let profileAccessor = new ProfileAge();
+      profileAccessor.recordProfileReset(Date.now() - (15 * 24 * 60 * 60 * 1000));
+      gContentAPI.getConfiguration("appinfo", (result2) => {
+        ok(typeof(result2.profileResetWeeksAgo) === "number", "profileResetWeeksAgo should be number.");
+        is(result2.profileResetWeeksAgo, 2, "profileResetWeeksAgo should be 2.");
+        done();
+      });
+    });
+  },
   function test_addToolbarButton(done) {
     let placement = CustomizableUI.getPlacementOfWidget("panic-button");
     is(placement, null, "default UI has panic button in the palette");
 
     gContentAPI.getConfiguration("availableTargets", (data) => {
       let available = (data.targets.indexOf("forget") != -1);
       ok(!available, "Forget button should not be available by default");
 
--- a/browser/config/mozconfigs/win32/common-opt
+++ b/browser/config/mozconfigs/win32/common-opt
@@ -1,10 +1,14 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
+# TODO remove once configure defaults to stylo once stylo enabled
+# on all platforms.
+ac_add_options --enable-stylo=build
+
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-jemalloc
 
 if [ -f /c/builds/gapi.data ]; then
   _gapi_keyfile=c:/builds/gapi.data
 else
--- a/browser/config/mozconfigs/win32/debug
+++ b/browser/config/mozconfigs/win32/debug
@@ -1,12 +1,16 @@
 . "$topsrcdir/build/mozconfig.win-common"
 MOZ_AUTOMATION_L10N_CHECK=0
 . "$topsrcdir/browser/config/mozconfigs/common"
 
+# TODO remove once configure defaults to stylo once stylo enabled
+# on all platforms.
+ac_add_options --enable-stylo=build
+
 ac_add_options --enable-debug
 ac_add_options --enable-dmd
 ac_add_options --enable-profiling  # needed for --enable-dmd to work on Windows
 ac_add_options --enable-verify-mar
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
--- a/browser/config/mozconfigs/win64/common-opt
+++ b/browser/config/mozconfigs/win64/common-opt
@@ -1,10 +1,14 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
+# TODO remove once configure defaults to stylo once stylo enabled
+# on all platforms.
+ac_add_options --enable-stylo=build
+
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-jemalloc
 if [ -f /c/builds/gapi.data ]; then
   _gapi_keyfile=c:/builds/gapi.data
 else
   _gapi_keyfile=e:/builds/gapi.data
--- a/browser/config/mozconfigs/win64/debug
+++ b/browser/config/mozconfigs/win64/debug
@@ -1,15 +1,19 @@
 . "$topsrcdir/build/mozconfig.win-common"
 MOZ_AUTOMATION_L10N_CHECK=0
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --target=x86_64-pc-mingw32
 ac_add_options --host=x86_64-pc-mingw32
 
+# TODO remove once configure defaults to stylo once stylo enabled
+# on all platforms.
+ac_add_options --enable-stylo=build
+
 ac_add_options --enable-debug
 ac_add_options --enable-dmd
 ac_add_options --enable-profiling  # needed for --enable-dmd to work on Windows
 ac_add_options --enable-verify-mar
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
--- a/browser/config/tooltool-manifests/linux32/releng.manifest
+++ b/browser/config/tooltool-manifests/linux32/releng.manifest
@@ -19,23 +19,23 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 146886764,
     "digest": "e03eeebd4acc593369d5635a059f55a6beed2d2fb839a8c196ccc735a246620d3285a15c17ab34fa8bcf9dd57dd25f735d4ef3eb2fc3be672bbde62342823f1e",
     "algorithm": "sha512",
     "filename": "rustc.tar.xz",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   },
   {
     "version": "clang + llvm 3.9.0, built from SVN r290136",
     "size": 166261192,
     "digest": "52f3fc23f0f5c98050f8b0ac7c92a6752d067582a16f712a5a58074be98975d594f9e36249fc2be7f1cc2ca6d509c663faaf2bea66f949243cc1f41651638ba6",
     "algorithm": "sha512",
     "filename": "clang.tar.xz",
     "unpack": true
--- a/browser/config/tooltool-manifests/linux64/base-toolchains.manifest
+++ b/browser/config/tooltool-manifests/linux64/base-toolchains.manifest
@@ -19,23 +19,23 @@
     "version": "rustc 1.17.0 (56124baa9 2017-04-24) repack",
     "size": 121834488,
     "digest": "8ce3d979c169af6c6e2bf393aa2bd4825371e87d42ebdfd7d7f06c0e3e69c0d68a3dcd39d9a85bcaa9fe5c6d6fe86e881fc8413d74c2a240204f2bafca781b9d",
     "algorithm": "sha512",
     "filename": "rustc.tar.xz",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   },
   {
     "version": "clang + llvm 3.9.0, built from SVN r290136",
     "size": 166261192,
     "digest": "52f3fc23f0f5c98050f8b0ac7c92a6752d067582a16f712a5a58074be98975d594f9e36249fc2be7f1cc2ca6d509c663faaf2bea66f949243cc1f41651638ba6",
     "algorithm": "sha512",
     "filename": "clang.tar.xz",
     "unpack": true
--- a/browser/config/tooltool-manifests/linux64/clang.manifest
+++ b/browser/config/tooltool-manifests/linux64/clang.manifest
@@ -19,17 +19,17 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 146886764,
     "digest": "e03eeebd4acc593369d5635a059f55a6beed2d2fb839a8c196ccc735a246620d3285a15c17ab34fa8bcf9dd57dd25f735d4ef3eb2fc3be672bbde62342823f1e",
     "algorithm": "sha512",
     "filename": "rustc.tar.xz",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   }
 ]
--- a/browser/config/tooltool-manifests/linux64/clang.manifest.centos6
+++ b/browser/config/tooltool-manifests/linux64/clang.manifest.centos6
@@ -19,17 +19,17 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 146886764,
     "digest": "e03eeebd4acc593369d5635a059f55a6beed2d2fb839a8c196ccc735a246620d3285a15c17ab34fa8bcf9dd57dd25f735d4ef3eb2fc3be672bbde62342823f1e",
     "algorithm": "sha512",
     "filename": "rustc.tar.xz",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   }
 ]
new file mode 100644
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/fuzzing.manifest
@@ -0,0 +1,35 @@
+[
+  {
+    "version": "gcc 4.9.4 + PR64905",
+    "size": 101297752,
+    "digest": "42aa2e3fdd232b5e390472a788e7f7db71a1fee4221e260b6cb58c9a1d73e6cdd10afcbac137f7844290169cd6b561b424ecc92b159e9726b0ad5de3f478a8be",
+    "algorithm": "sha512",
+    "filename": "gcc.tar.xz",
+    "unpack": true
+  },
+  {
+    "version": "clang 4.0.1 gecko build for linux",
+    "size": 215309284,
+    "visibility": "public",
+    "digest": "8f6d386ca1d4606526dd24f366b1dbc1914c6c6d7f54c69c2a2ca0e7cfabe641c1168952d606295feffa9f38ad687084de5efb1e80be3ed2f431ac91de80039b",
+    "algorithm": "sha512",
+    "filename": "clang.tar.xz",
+    "unpack": true
+  },
+  {
+    "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
+    "size": 146886764,
+    "digest": "e03eeebd4acc593369d5635a059f55a6beed2d2fb839a8c196ccc735a246620d3285a15c17ab34fa8bcf9dd57dd25f735d4ef3eb2fc3be672bbde62342823f1e",
+    "algorithm": "sha512",
+    "filename": "rustc.tar.xz",
+    "unpack": true
+  },
+  {
+    "size": 12072532,
+    "digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+    "algorithm": "sha512",
+    "filename": "gtk3.tar.xz",
+    "setup": "setup.sh",
+    "unpack": true
+  }
+]
--- a/browser/config/tooltool-manifests/linux64/hazard.manifest
+++ b/browser/config/tooltool-manifests/linux64/hazard.manifest
@@ -27,23 +27,23 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 146886764,
     "digest": "e03eeebd4acc593369d5635a059f55a6beed2d2fb839a8c196ccc735a246620d3285a15c17ab34fa8bcf9dd57dd25f735d4ef3eb2fc3be672bbde62342823f1e",
     "algorithm": "sha512",
     "filename": "rustc.tar.xz",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   },
   {
     "version": "clang + llvm 3.9.0, built from SVN r290136",
     "size": 166261192,
     "digest": "52f3fc23f0f5c98050f8b0ac7c92a6752d067582a16f712a5a58074be98975d594f9e36249fc2be7f1cc2ca6d509c663faaf2bea66f949243cc1f41651638ba6",
     "algorithm": "sha512",
     "filename": "clang.tar.xz",
     "unpack": true
--- a/browser/config/tooltool-manifests/linux64/releng.manifest
+++ b/browser/config/tooltool-manifests/linux64/releng.manifest
@@ -19,23 +19,23 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 146886764,
     "digest": "e03eeebd4acc593369d5635a059f55a6beed2d2fb839a8c196ccc735a246620d3285a15c17ab34fa8bcf9dd57dd25f735d4ef3eb2fc3be672bbde62342823f1e",
     "algorithm": "sha512",
     "filename": "rustc.tar.xz",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   },
   {
     "version": "clang + llvm 3.9.0, built from SVN r290136",
     "size": 166261192,
     "digest": "52f3fc23f0f5c98050f8b0ac7c92a6752d067582a16f712a5a58074be98975d594f9e36249fc2be7f1cc2ca6d509c663faaf2bea66f949243cc1f41651638ba6",
     "algorithm": "sha512",
     "filename": "clang.tar.xz",
     "unpack": true
--- a/browser/config/tooltool-manifests/macosx64/clang.manifest
+++ b/browser/config/tooltool-manifests/macosx64/clang.manifest
@@ -11,23 +11,23 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 97110232,
     "digest": "7df456b9f86a0f11b81700b6bbb9451137beb5a4035fe2330dc507c804fe8230573636720dba60308eee5305b7c04bc041c025ca0ca98bef54f98f98b48dd8d7",
     "algorithm": "sha512",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.bz2",
     "unpack": true,
-    "digest": "6e14c9a19ee4178b105ee2d995c647888a162056c61d2bd5d1d9dce0326af5ea45cc7d2f28d0b9a499ab2aa909ed534e6c545c6d27319bdd598b6dbceaca2e22",
-    "size": 1669459
+    "digest": "7ace40311de3d466f20476ce27c24f25208e8dd80151cbff2e72bab75038dee429312703f13a832c9671bb42dc22a00801c5eb5a9e4d807f2723c16e33167d1b",
+    "size": 1658526
   },
   {
     "version": "cctools port from commit hash 8e9c3f2506b51",
     "size": 2233376,
     "digest": "d632ef587f0253f016aa5323999a3d9576284c04e66b5243a5780af9a55f474ac91ad8dee5bd86a6ee4e2593e2b345e2fd0aa4e8838b3686f84c5c5ac5c9c418",
     "algorithm": "sha512",
     "filename": "cctools.tar.bz2",
     "unpack": true
--- a/browser/config/tooltool-manifests/macosx64/cross-clang.manifest
+++ b/browser/config/tooltool-manifests/macosx64/cross-clang.manifest
@@ -27,17 +27,17 @@
     "size": 30823112,
     "visibility": "internal",
     "digest": "0c58e06a3ea8f4641c991a7406fc8733c574f0b4aa773bce0feaa5468d2b8440fa33cea056e16fad2b8a7ef4409ca7228113eb12adc87c3e115129d8a3b3b565",
     "algorithm": "sha512",
     "unpack": true,
     "filename": "MacOSX10.10.sdk.tar.xz"
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   }
 ]
--- a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
@@ -19,23 +19,23 @@
     "size": 35215976,
     "visibility": "internal",
     "digest": "8be736545ddab25ebded188458ce974d5c9a7e29f3c50d2ebfbcb878f6aff853dd2ff5a3528bdefc64396a10101a1b50fd2fe52000140df33643cebe1ea759da",
     "algorithm": "sha512",
     "unpack": true,
     "filename": "MacOSX10.7.sdk.tar.bz2"
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.xz",
     "unpack": true,
-    "digest": "da67d4a872f8d23ef0707e02a823e5a644f1988e605d2c46c787641c8c37ae4521ab7d83b1dc4c0cc52d203be6313c7f9580830845536fa7c2a521af9443f69a",
-    "size": 2192144
+    "digest": "7bbe53cda3f608826dc6a0266e648cd2ea09c88ef93edf87d582b41185b72740667e2c7bf6e9b2b911fb2915cbfb985e528598c7b3153092ad27d5e937342df7",
+    "size": 2191380
   },
   {
     "version": "https://github.com/mozilla/libdmg-hfsplus rev ba04b00435a0853f1499d751617177828ee8ec00",
     "size": 57060,
     "visibility": "public",
     "digest": "0c96f0d3ace71c4110abec6f7ead013600b0a73c89465d840276090d849279e555d977fb2aa6bbabb7891d7191fc8cc8a4e8242be888114be52346b77a512fcc",
     "algorithm": "sha512",
     "unpack": true,
--- a/browser/config/tooltool-manifests/macosx64/releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/releng.manifest
@@ -19,17 +19,17 @@
     "version": "cctools port from commit hash 8e9c3f2506b51",
     "size": 2233376,
     "digest": "d632ef587f0253f016aa5323999a3d9576284c04e66b5243a5780af9a55f474ac91ad8dee5bd86a6ee4e2593e2b345e2fd0aa4e8838b3686f84c5c5ac5c9c418",
     "algorithm": "sha512",
     "filename": "cctools.tar.bz2",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.bz2",
     "unpack": true,
-    "digest": "6e14c9a19ee4178b105ee2d995c647888a162056c61d2bd5d1d9dce0326af5ea45cc7d2f28d0b9a499ab2aa909ed534e6c545c6d27319bdd598b6dbceaca2e22",
-    "size": 1669459
+    "digest": "7ace40311de3d466f20476ce27c24f25208e8dd80151cbff2e72bab75038dee429312703f13a832c9671bb42dc22a00801c5eb5a9e4d807f2723c16e33167d1b",
+    "size": 1658526
   }
 ]
--- a/browser/config/tooltool-manifests/win32/build-clang-cl.manifest
+++ b/browser/config/tooltool-manifests/win32/build-clang-cl.manifest
@@ -9,23 +9,23 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 90954208,
     "digest": "6ee6ab72521c1f476fd15a3f2787f3046a1648f34cbc43380c2ba3915c2207b29c942fefe4b56426f5bafe47937c6a8074201d61a79d02ad366dace41d5cb717",
     "algorithm": "sha512",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.bz2",
     "unpack": true,
-    "digest": "ce3a59557d91e3a874663f64576d184556053430d997b1581874073d067e92e342002bf7c8ee2f056ea86bdbafb9872a81e98d592fc990d40ad43e0539df00ad",
-    "size": 1878161
+    "digest": "7a40983b93536aaa9d6953777612b7dcc170b1acab91b377f4f9e697a316b586d477435a93ac32d086f685d52bfe3ea47e99f96e0374c224c3c11c702dbd9ab6",
+    "size": 1879437
   },
   {
     "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
     "size": 326656969,
     "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
     "algorithm": "sha512",
     "filename": "vs2015u3.zip",
     "unpack": true
--- a/browser/config/tooltool-manifests/win32/clang.manifest
+++ b/browser/config/tooltool-manifests/win32/clang.manifest
@@ -9,23 +9,23 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 90954208,
     "digest": "6ee6ab72521c1f476fd15a3f2787f3046a1648f34cbc43380c2ba3915c2207b29c942fefe4b56426f5bafe47937c6a8074201d61a79d02ad366dace41d5cb717",
     "algorithm": "sha512",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.bz2",
     "unpack": true,
-    "digest": "ce3a59557d91e3a874663f64576d184556053430d997b1581874073d067e92e342002bf7c8ee2f056ea86bdbafb9872a81e98d592fc990d40ad43e0539df00ad",
-    "size": 1878161
+    "digest": "7a40983b93536aaa9d6953777612b7dcc170b1acab91b377f4f9e697a316b586d477435a93ac32d086f685d52bfe3ea47e99f96e0374c224c3c11c702dbd9ab6",
+    "size": 1879437
   },
   {
     "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
     "size": 326656969,
     "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
     "algorithm": "sha512",
     "filename": "vs2015u3.zip",
     "unpack": true
--- a/browser/config/tooltool-manifests/win32/releng.manifest
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -9,23 +9,23 @@
     "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
     "size": 90954208,
     "digest": "6ee6ab72521c1f476fd15a3f2787f3046a1648f34cbc43380c2ba3915c2207b29c942fefe4b56426f5bafe47937c6a8074201d61a79d02ad366dace41d5cb717",
     "algorithm": "sha512",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.bz2",
     "unpack": true,
-    "digest": "ce3a59557d91e3a874663f64576d184556053430d997b1581874073d067e92e342002bf7c8ee2f056ea86bdbafb9872a81e98d592fc990d40ad43e0539df00ad",
-    "size": 1878161
+    "digest": "7a40983b93536aaa9d6953777612b7dcc170b1acab91b377f4f9e697a316b586d477435a93ac32d086f685d52bfe3ea47e99f96e0374c224c3c11c702dbd9ab6",
+    "size": 1879437
   },
   {
     "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
     "size": 326656969,
     "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
     "algorithm": "sha512",
     "filename": "vs2015u3.zip",
     "unpack": true
--- a/browser/config/tooltool-manifests/win64/clang.manifest
+++ b/browser/config/tooltool-manifests/win64/clang.manifest
@@ -10,23 +10,23 @@
     "size": 98336380,
     "digest": "92091d92ce135ee52486c31ae670735dd140ab5b1389f14582c4d9b14cbb393f7180399b9232564a3eb96443b568323070a3c1329deb07b145b28476e8271175",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.bz2",
     "unpack": true,
-    "digest": "ce3a59557d91e3a874663f64576d184556053430d997b1581874073d067e92e342002bf7c8ee2f056ea86bdbafb9872a81e98d592fc990d40ad43e0539df00ad",
-    "size": 1878161
+    "digest": "7a40983b93536aaa9d6953777612b7dcc170b1acab91b377f4f9e697a316b586d477435a93ac32d086f685d52bfe3ea47e99f96e0374c224c3c11c702dbd9ab6",
+    "size": 1879437
   },
   {
     "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
     "size": 326656969,
     "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
     "algorithm": "sha512",
     "filename": "vs2015u3.zip",
     "unpack": true
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -10,23 +10,23 @@
     "size": 98336380,
     "digest": "92091d92ce135ee52486c31ae670735dd140ab5b1389f14582c4d9b14cbb393f7180399b9232564a3eb96443b568323070a3c1329deb07b145b28476e8271175",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
-    "version": "sccache rev 9155425cfc038d6a60deb50816055f4e93b93ad1",
+    "version": "sccache rev 69334a26ba65fc88e3934271a2ce6781c51b445e",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "sccache2.tar.bz2",
     "unpack": true,
-    "digest": "ce3a59557d91e3a874663f64576d184556053430d997b1581874073d067e92e342002bf7c8ee2f056ea86bdbafb9872a81e98d592fc990d40ad43e0539df00ad",
-    "size": 1878161
+    "digest": "7a40983b93536aaa9d6953777612b7dcc170b1acab91b377f4f9e697a316b586d477435a93ac32d086f685d52bfe3ea47e99f96e0374c224c3c11c702dbd9ab6",
+    "size": 1879437
   },
   {
     "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
     "size": 326656969,
     "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
     "algorithm": "sha512",
     "filename": "vs2015u3.zip",
     "unpack": true
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -493,24 +493,42 @@ var FormAutofillContent = {
     if (!field || !(field instanceof Ci.nsIDOMHTMLInputElement)) {
       return;
     }
 
     formFillController.markAsAutofillField(field);
   },
 
   _previewProfile(doc) {
-    let selectedIndex = ProfileAutocomplete._getSelectedIndex(doc.ownerGlobal);
+    let docWin = doc.ownerGlobal;
+    let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin);
     let lastAutoCompleteResult = ProfileAutocomplete.getProfileAutoCompleteResult();
+    let focusedInput = formFillController.focusedInput;
+    let mm = this._messageManagerFromWindow(docWin);
 
     if (selectedIndex === -1 ||
+        !focusedInput ||
         !lastAutoCompleteResult ||
         lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
+      mm.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {});
+
       ProfileAutocomplete._clearProfilePreview();
     } else {
+      let focusedInputDetails = this.getInputDetails(focusedInput);
+      let profile = JSON.parse(lastAutoCompleteResult.getCommentAt(selectedIndex));
+      let allFieldNames = FormAutofillContent.getAllFieldNames(focusedInput);
+      let profileFields = allFieldNames.filter(fieldName => !!profile[fieldName]);
+
+      let focusedCategory = FormAutofillUtils.getCategoryFromFieldName(focusedInputDetails.fieldName);
+      let categories = FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
+      mm.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {
+        focusedCategory,
+        categories,
+      });
+
       ProfileAutocomplete._previewSelectedProfile(selectedIndex);
     }
   },
 
   _messageManagerFromWindow(win) {
     return win.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIWebNavigation)
               .QueryInterface(Ci.nsIDocShell)
--- a/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
+++ b/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
@@ -46,16 +46,39 @@ const CONTENT = {
       accessKey: "C",
       callbackState: "open-pref",
     },
     options: {
       persistWhileVisible: true,
       popupIconURL: "chrome://formautofill/content/icon-address-save.svg",
     },
   },
+  update: {
+    notificationId: "autofill-address",
+    message: GetStringFromName("updateAddressMessage"),
+    anchor: {
+      id: "autofill-address-notification-icon",
+      URL: "chrome://formautofill/content/formfill-anchor.svg",
+      tooltiptext: GetStringFromName("openAutofillMessagePanel"),
+    },
+    mainAction: {
+      label: GetStringFromName("updateAddressLabel"),
+      accessKey: "U",
+      callbackState: "update",
+    },
+    secondaryActions: [{
+      label: GetStringFromName("createAddressLabel"),
+      accessKey: "C",
+      callbackState: "create",
+    }],
+    options: {
+      persistWhileVisible: true,
+      popupIconURL: "chrome://formautofill/content/icon-address-update.svg",
+    },
+  },
 };
 
 let FormAutofillDoorhanger = {
   /**
    * Generate the main action and secondary actions from content parameters and
    * promise resolve.
    *
    * @private
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -275,17 +275,35 @@ FormAutofillParent.prototype = {
     this._updateStatus();
   },
 
   _onFormSubmit(data, target) {
     let {address} = data;
 
     if (address.guid) {
       if (!this.profileStorage.addresses.mergeIfPossible(address.guid, address.record)) {
-        // TODO: Show update doorhanger(bug 1303513) and set probe(bug 990200)
+        FormAutofillDoorhanger.show(target, "update").then((state) => {
+          let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
+          switch (state) {
+            case "create":
+              if (!changedGUIDs.length) {
+                changedGUIDs.push(this.profileStorage.addresses.add(address.record));
+              }
+              break;
+            case "update":
+              if (!changedGUIDs.length) {
+                this.profileStorage.addresses.update(address.guid, address.record);
+                changedGUIDs.push(address.guid);
+              } else {
+                this.profileStorage.addresses.remove(address.guid);
+              }
+              break;
+          }
+          changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
+        });
         return;
       }
       this.profileStorage.addresses.notifyUsed(address.guid);
     } else {
       let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
       if (!changedGUIDs.length) {
         changedGUIDs.push(this.profileStorage.addresses.add(address.record));
       }
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -40,25 +40,29 @@ this.FormAutofillUtils = {
   isAddressField(fieldName) {
     return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
   },
 
   isCreditCardField(fieldName) {
     return this._fieldNameInfo[fieldName] == "creditCard";
   },
 
+  getCategoryFromFieldName(fieldName) {
+    return this._fieldNameInfo[fieldName];
+  },
+
   getCategoriesFromFieldNames(fieldNames) {
     let categories = new Set();
     for (let fieldName of fieldNames) {
-      let info = this._fieldNameInfo[fieldName];
+      let info = this.getCategoryFromFieldName(fieldName);
       if (info) {
         categories.add(info);
       }
     }
-    return categories;
+    return Array.from(categories);
   },
 
   getAddressSeparator() {
     // The separator should be based on the L10N address format, and using a
     // white space is a temporary solution.
     return " ";
   },
 
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -37,16 +37,17 @@ this.ProfileAutoCompleteResult = functio
   this._popupLabels = this._generateLabels(this._focusedFieldName,
                                            this._allFieldNames,
                                            this._matchingProfiles);
   // Add an empty result entry for footer. Its content will come from
   // the footer binding, so don't assign any value to it.
   this._popupLabels.push({
     primary: "",
     secondary: "",
+    categories: FormAutofillUtils.getCategoriesFromFieldNames(allFieldNames),
   });
 };
 
 ProfileAutoCompleteResult.prototype = {
 
   // The user's query string
   searchString: "",
 
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -53,17 +53,17 @@
       <method name="_onOverflow">
         <body></body>
       </method>
 
       <method name="_onUnderflow">
         <body></body>
       </method>
 
-      <method name="_adjustProfileItemLayout">
+      <method name="_adjustAutofillItemLayout">
         <body>
         <![CDATA[
           let outerBoxRect = this.parentNode.getBoundingClientRect();
 
           // Make item fit in popup as XUL box could not constrain
           // item's width
           this._itemBox.style.width = outerBoxRect.width + "px";
           // Use two-lines layout when width is smaller than 150px
@@ -119,72 +119,147 @@
           let {AutoCompletePopup} = Cu.import("resource://gre/modules/AutoCompletePopup.jsm", {});
 
           AutoCompletePopup.sendMessageToBrowser("FormAutofill:PreviewProfile");
 
           return val;
         ]]></setter>
       </property>
 
-
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
-          this._adjustProfileItemLayout();
+          this._adjustAutofillItemLayout();
           this.setAttribute("formautofillattached", "true");
 
           let {primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
 
           this._label.textContent = primary;
           this._comment.textContent = secondary;
         ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
   <binding id="autocomplete-profile-listitem-footer" extends="chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-base">
     <xbl:content xmlns="http://www.w3.org/1999/xhtml">
       <div anonid="autofill-footer" class="autofill-item-box autofill-footer">
+        <div anonid="autofill-warning" class="autofill-footer-row autofill-warning">
+        </div>
+        <div anonid="autofill-option-button" class="autofill-footer-row autofill-option-button">
+        </div>
       </div>
     </xbl:content>
 
     <handlers>
       <handler event="click" button="0"><![CDATA[
         window.openPreferences("panePrivacy", {origin: "autofillFooter"});
       ]]></handler>
     </handlers>
 
     <implementation implements="nsIDOMXULSelectControlItemElement">
       <constructor>
         <![CDATA[
           this._itemBox = document.getAnonymousElementByAttribute(
             this, "anonid", "autofill-footer"
           );
+          this._optionButton = document.getAnonymousElementByAttribute(
+            this, "anonid", "autofill-option-button"
+          );
+          this._warningTextBox = document.getAnonymousElementByAttribute(
+            this, "anonid", "autofill-warning"
+          );
+
+          this.allFieldCategories = JSON.parse(this.getAttribute("ac-value")).categories;
+
+          /**
+           * Update the text on the footer.
+           *
+           * @private
+           * @param {string|string[]} categories
+           *        A list of categories that used to generate the message.
+           * @param {boolean} hasExtraCategories
+           *        Used to determine if it has the extra categories other than the focued category. If
+           *        the value is true, we show "Also fill ...", otherwise, show "Fill ..." only.
+           */
+          this._updateText = (categories = this.allFieldCategories, hasExtraCategories = true) => {
+            let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2";
+            let sep = this._stringBundle.GetStringFromName("fieldNameSeparator");
+            let categoriesText = categories.map(this._stringBundle.GetStringFromName).join(sep);
+
+            this._warningTextBox.textContent = this._stringBundle.formatStringFromName(warningTextTmplKey,
+              [categoriesText], 1);
+            this.parentNode.parentNode.adjustHeight();
+          };
+
+          /**
+           * A handler for updating warning message once selectedIndex has been changed.
+           *
+           * There're three different states of warning message:
+           * 1. None of addresses were selected: We show all the categories in the form.
+           * 2. An address was selested: Show the additional categories that will also be filled.
+           * 3. An address was selected, but the focused category is the same as the only all categories: Only show
+           * the exact category that we're going to fill in.
+           *
+           * @private
+           * @param {string} focusedCategory
+           *        The category that the focused input's field belongs to.
+           * @param {string[]} categories
+           *        The categories of all the fields contained in the selected address.
+           */
+          this._updateWarningMsgHandler = ({data: {focusedCategory, categories}} = {data: {}}) => {
+            let hasSelectedAddress = focusedCategory && categories;
+            // If the length of categories is 1, that means all the fillable fields are in the same
+            // category. We will change the way to inform user according to this flag.
+            let hasExtraCategories = hasSelectedAddress && categories.length > 1;
+            if (!hasSelectedAddress) {
+              this._updateText();
+              return;
+            }
+
+            let showCategories = hasExtraCategories ?
+                                 categories.filter(category => category != focusedCategory) :
+                                 [focusedCategory];
+            this._updateText(showCategories, hasExtraCategories);
+          };
 
           this._adjustAcItem();
+          this._updateText();
         ]]>
       </constructor>
 
+      <method name="_onCollapse">
+        <body>
+        <![CDATA[
+          /* global messageManager */
+
+          messageManager.removeMessageListener("FormAutofill:UpdateWarningMessage", this._updateWarningMsgHandler);
+        ]]>
+        </body>
+      </method>
+
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
           /* global Cu */
-          this._adjustProfileItemLayout();
+          this._adjustAutofillItemLayout();
           this.setAttribute("formautofillattached", "true");
 
           let {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
-          let footerTextBundleKey = AppConstants.platform == "macosx" ?
+          let buttonTextBundleKey = AppConstants.platform == "macosx" ?
             "autocompleteFooterOptionOSX" : "autocompleteFooterOption";
           // If the popup shows up with small layout, we should use short string to
           // have a better fit in the box.
           if (this._itemBox.getAttribute("size") == "small") {
-            footerTextBundleKey += "Short";
+            buttonTextBundleKey += "Short";
           }
-          let footerText = this._stringBundle.GetStringFromName(footerTextBundleKey);
-          this._itemBox.textContent = footerText;
+          let buttonText = this._stringBundle.GetStringFromName(buttonTextBundleKey);
+          this._optionButton.textContent = buttonText;
+
+          messageManager.addMessageListener("FormAutofill:UpdateWarningMessage", this._updateWarningMsgHandler);
         ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
 </bindings>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/icon-address-update.svg
@@ -0,0 +1,6 @@
+<!-- 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" viewBox="0 0 32 32">
+  <path fill="#999899" d="M22 13.7H9.4c-.6 0-1.2.5-1.2 1.2 0 .6.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM6.1 26.6V5.5c0-.8.7-1.5 1.5-1.5h16c.9 0 1.5.6 1.5 1.5V16h2V3.8c0-1-.7-1.8-1.8-1.8H5.9c-1 0-1.8.8-1.8 1.8v24.5c0 1 .8 1.7 1.8 1.7h9.3v-2H7.6c-.8 0-1.5-.6-1.5-1.4zm9.8-7.5H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2h6.5c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM22 7.8H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zm-5.7 16l4.4-4.3c.2-.2.5-.3.8-.3s.6.1.8.3l4.4 4.3c.5.5.3.8-.3.8h-2.6v4.7c0 .4-.4.8-.8.8h-3c-.4 0-.8-.4-.8-.8v-4.7h-2.5c-.7 0-.8-.4-.4-.8z"/>
+</svg>
--- a/browser/extensions/formautofill/locale/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locale/en-US/formautofill.properties
@@ -5,13 +5,29 @@
 preferenceGroupTitle = Form Autofill
 enableProfileAutofill = Enable Profile Autofill
 savedProfiles = Saved Profiles…
 saveAddressesMessage = Firefox now saves addresses so you can fill out forms faster.
 viewAutofillOptionsLink = View Form Autofill Options
 changeAutofillOptions = Change Form Autofill Options
 viewAutofillOptionsLinkOSX = View Form Autofill Preferences
 changeAutofillOptionsOSX = Change Form Autofill Preferences
+updateAddressMessage = Would you like to update your address with this new information?
+createAddressLabel = Create New Address
+updateAddressLabel = Update Address
 openAutofillMessagePanel = Open Form Autofill message panel
 autocompleteFooterOption = Form Autofill Options
 autocompleteFooterOptionShort = Options
 autocompleteFooterOptionOSX = Form Autofill Preferences
 autocompleteFooterOptionOSXShort = Preferences
+address = address
+name = name
+organization = company
+tel = phone
+email = email
+# LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories.
+fieldNameSeparator = ,\u0020
+# LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
+# text that is displayed for informing users what categories are about to be filled.
+# "%S" will be replaced with a list generated from the pre-defined categories.
+# The text would be e.g. Also fill company, phone, email
+phishingWarningMessage = Also fill %S
+phishingWarningMessage2 = Fill %S
--- a/browser/extensions/formautofill/skin/linux/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/linux/autocomplete-item.css
@@ -8,11 +8,15 @@
 .autofill-item-box > .profile-item-col > .profile-label {
   font-size: .84em;
 }
 
 .autofill-item-box > .profile-item-col > .profile-comment {
   font-size: .7em;
 }
 
-.autofill-footer {
+.autofill-footer > .autofill-warning {
+  font-size: .7em;
+}
+
+.autofill-footer > .autofill-option-button {
   font-size: .77em;
 }
--- a/browser/extensions/formautofill/skin/osx/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/osx/autocomplete-item.css
@@ -7,8 +7,12 @@
 
 .autofill-item-box > .profile-item-col > .profile-label {
   font-size: 1.09em;
 }
 
 .autofill-item-box > .profile-item-col > .profile-comment {
   font-size: .9em;
 }
+
+.autofill-footer > .autofill-warning {
+  font-size: .9em;
+}
--- a/browser/extensions/formautofill/skin/shared/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/shared/autocomplete-item.css
@@ -5,17 +5,17 @@
 @namespace url("http://www.w3.org/1999/xhtml");
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 
 xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
   background-color: #F2F2F2;
 }
 
-xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-footer {
+xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-item-box > .autofill-option-button {
   background-color: #DCDCDE;
 }
 
 .autofill-item-box {
   --item-padding-vertical: 6px;
   --item-padding-horizontal: 10px;
   --col-spacer: 7px;
   --item-width: calc(50% - (var(--col-spacer) / 2));
@@ -24,17 +24,19 @@ xul|richlistitem[originaltype="autofill-
 
 .autofill-item-box[size="small"] {
   --item-padding-vertical: 7px;
   --col-spacer: 0px;
   --row-spacer: 3px;
   --item-width: 100%;
 }
 
-.autofill-footer {
+.autofill-footer,
+.autofill-footer[size="small"] {
+  --item-width: 100%;
   --item-padding-vertical: 0;
   --item-padding-horizontal: 0;
 }
 
 .autofill-item-box {
   box-sizing: border-box;
   margin: 0;
   border-bottom: 1px solid rgba(38,38,38,.15);
@@ -76,12 +78,30 @@ xul|richlistitem[originaltype="autofill-
 }
 
 .autofill-item-box[size="small"] > .profile-comment-col {
   margin-top: var(--row-spacer);
   text-align: start;
 }
 
 .autofill-footer {
+  flex-direction: column;
+}
+
+.autofill-footer > .autofill-footer-row {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: var(--item-width);
+}
+
+.autofill-footer > .autofill-warning {
+  padding: 2.5px 0;
+  color: #989898;
+  text-align: center;
+  background-color: rgba(248,232,28,.2);
+  border-bottom: 1px solid rgba(38,38,38,.15);
+}
+
+.autofill-footer > .autofill-option-button {
   height: 41px;
   background-color: #EDEDED;
-  justify-content: center;
 }
--- a/browser/extensions/formautofill/skin/windows/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/windows/autocomplete-item.css
@@ -5,17 +5,21 @@
 @namespace url("http://www.w3.org/1999/xhtml");
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 
 .autofill-item-box > .profile-item-col > .profile-comment {
   font-size: .83em;
 }
 
-.autofill-footer {
+.autofill-footer > .autofill-warning {
+  font-size: .83em;
+}
+
+.autofill-footer > .autofill-option-button {
   font-size: .91em;
 }
 
 @media (-moz-windows-default-theme: 0) {
   xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
     background-color: Highlight;
   }
 }
--- a/browser/extensions/formautofill/test/browser/browser.ini
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -6,8 +6,9 @@ support-files =
 
 [browser_autocomplete_footer.js]
 [browser_check_installed.js]
 [browser_editProfileDialog.js]
 [browser_first_time_use_doorhanger.js]
 [browser_privacyPreferences.js]
 [browser_manageProfilesDialog.js]
 [browser_submission_in_private_mode.js]
+[browser_update_doorhanger.js]
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
@@ -1,60 +1,87 @@
 "use strict";
 
 const URL = BASE_URL + "autocomplete_basic.html";
 const PRIVACY_PREF_URL = "about:preferences#privacy";
 
+async function expectWarningText(browser, expectedText) {
+  const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
+  const warningBox = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._warningTextBox;
+
+  await BrowserTestUtils.waitForCondition(() => {
+    return warningBox.textContent == expectedText;
+  }, `Waiting for expected warning text: ${expectedText}, Got ${warningBox.textContent}`);
+  ok(true, `Got expected warning text: ${expectedText}`);
+}
+
 add_task(async function setup_storage() {
   await saveAddress(TEST_ADDRESS_1);
   await saveAddress(TEST_ADDRESS_2);
   await saveAddress(TEST_ADDRESS_3);
 });
 
 add_task(async function test_click_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
     const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.getElementById("organization").focus();
-    });
-    await sleep(2000);
-    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await expectPopupOpen(browser);
-
+    await openPopupOn(browser, "#organization");
     // Click on the footer
-    const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
+    const optionButton = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._optionButton;
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
-    await EventUtils.synthesizeMouseAtCenter(listItemElems[listItemElems.length - 1], {});
+    await EventUtils.synthesizeMouseAtCenter(optionButton, {});
     await BrowserTestUtils.removeTab(await prefTabPromise);
     ok(true, "Tab: preferences#privacy was successfully opened by clicking on the footer");
 
     // Ensure the popup is closed before entering the next test.
     await ContentTask.spawn(browser, {}, async function() {
       content.document.getElementById("organization").blur();
     });
     await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
   });
 });
 
 add_task(async function test_press_enter_on_footer() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
-    const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
+    const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
-    await ContentTask.spawn(browser, {}, async function() {
-      const input = content.document.getElementById("organization");
-      input.focus();
-    });
-    await sleep(2000);
-    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await expectPopupOpen(browser);
-
+    await openPopupOn(browser, "#organization");
     // Navigate to the footer and press enter.
     const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
     const prefTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, PRIVACY_PREF_URL);
     for (let i = 0; i < listItemElems.length; i++) {
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     }
     await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
     await BrowserTestUtils.removeTab(await prefTabPromise);
     ok(true, "Tab: preferences#privacy was successfully opened by pressing enter on the footer");
+
+    // Ensure the popup is closed before entering the next test.
+    await ContentTask.spawn(browser, {}, async function() {
+      content.document.getElementById("organization").blur();
+    });
+    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
   });
 });
+
+add_task(async function test_phishing_warning() {
+  await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
+    const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
+
+    await openPopupOn(browser, "#street-address");
+    const warningBox = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._warningTextBox;
+    ok(warningBox, "Got phishing warning box");
+    await expectWarningText(browser, "Also fill company, address, phone, email");
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await expectWarningText(browser, "Also fill company, phone, email");
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await expectWarningText(browser, "Fill address");
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+    await expectWarningText(browser, "Also fill company, address, phone, email");
+
+    // Ensure the popup is closed before entering the next test.
+    await ContentTask.spawn(browser, {}, async function() {
+      content.document.getElementById("street-address").blur();
+    });
+    await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js
@@ -0,0 +1,120 @@
+"use strict";
+
+const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
+
+add_task(async function test_update_address() {
+  await saveAddress(TEST_ADDRESS_1);
+  let addresses = await getAddresses();
+  is(addresses.length, 1, "1 address in storage");
+
+  await BrowserTestUtils.withNewTab({gBrowser, url: FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        content.document.querySelector("form #organization").focus();
+      });
+
+      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+      await expectPopupOpen(browser);
+      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+      await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.