Merge latest green b2g-inbound changeset and mozilla-central
authorEd Morley <emorley@mozilla.com>
Wed, 29 Jan 2014 10:33:43 +0000
changeset 181727 b28e216c8d07f8e383a24cac46a18bbd279a995b
parent 181726 e0d2a1828aed98f9a3b098468565523d80d26216 (current diff)
parent 181699 cb10967d98f2cfea9d3b9644cda3b6eb83573d54 (diff)
child 181742 0fd024e3ef803e2d42a04db762f5acb0f6d0f2c2
child 181790 9bca7390eb1f03026d13fb916d4e1e2cfd5759f6
child 181818 4b7f81db2646db7ed1b3147e4b2a358bf67ca68e
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge latest green b2g-inbound changeset and mozilla-central
--- a/accessible/src/base/AccGroupInfo.cpp
+++ b/accessible/src/base/AccGroupInfo.cpp
@@ -103,17 +103,17 @@ AccGroupInfo::AccGroupInfo(Accessible* a
 
     mSetSize++;
   }
 
   if (mParent)
     return;
 
   roles::Role parentRole = parent->Role();
-  if (IsConceptualParent(aRole, parentRole))
+  if (ShouldReportRelations(aRole, parentRole))
     mParent = parent;
 
   // ARIA tree and list can be arranged by using ARIA groups to organize levels.
   if (parentRole != roles::GROUPING)
     return;
 
   // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a
   // parent. In other words the parent of the tree item will be a group and
@@ -166,19 +166,19 @@ AccGroupInfo::FirstItemOf(Accessible* aC
     item = item->FirstChild();
     if (item) {
       AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
       if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
         return item;
     }
   }
 
-  // Otherwise it can be a direct child.
+  // Otherwise, it can be a direct child if the container is a list or tree.
   item = aContainer->FirstChild();
-  if (IsConceptualParent(BaseRole(item->Role()), containerRole))
+  if (ShouldReportRelations(item->Role(), containerRole))
     return item;
 
   return nullptr;
 }
 
 Accessible*
 AccGroupInfo::NextItemTo(Accessible* aItem)
 {
@@ -196,34 +196,26 @@ AccGroupInfo::NextItemTo(Accessible* aIt
     Accessible* nextItem = parent->GetChildAt(idx);
     AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo();
     if (nextGroupInfo &&
         nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
       return nextItem;
     }
   }
 
-  NS_NOTREACHED("Item in the midle of the group but there's no next item!");
+  NS_NOTREACHED("Item in the middle of the group but there's no next item!");
   return nullptr;
 }
 
 bool
-AccGroupInfo::IsConceptualParent(role aRole, role aParentRole)
+AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole)
 {
+  // We only want to report hierarchy-based node relations for items in tree or
+  // list form.  ARIA level/owns relations are always reported.
   if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM)
     return true;
-  if ((aParentRole == roles::TABLE || aParentRole == roles::TREE_TABLE) &&
-      aRole == roles::ROW)
+  if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW)
     return true;
   if (aParentRole == roles::LIST && aRole == roles::LISTITEM)
     return true;
-  if (aParentRole == roles::COMBOBOX_LIST && aRole == roles::COMBOBOX_OPTION)
-    return true;
-  if (aParentRole == roles::LISTBOX && aRole == roles::OPTION)
-    return true;
-  if (aParentRole == roles::PAGETABLIST && aRole == roles::PAGETAB)
-    return true;
-  if ((aParentRole == roles::POPUP_MENU || aParentRole == roles::MENUPOPUP) &&
-      aRole == roles::MENUITEM)
-    return true;
 
   return false;
 }
--- a/accessible/src/base/AccGroupInfo.h
+++ b/accessible/src/base/AccGroupInfo.h
@@ -86,20 +86,20 @@ private:
 
     if (aRole == mozilla::a11y::roles::CHECK_RICH_OPTION)
       return mozilla::a11y::roles::RICH_OPTION;
 
     return aRole;
   }
 
   /**
-   * Return true if the given parent role is conceptual parent of the given
-   * role.
+   * Return true if the given parent and child roles should have their node
+   * relations reported.
    */
-  static bool IsConceptualParent(a11y::role aRole, a11y::role aParentRole);
+  static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole);
 
   uint32_t mPosInSet;
   uint32_t mSetSize;
   Accessible* mParent;
 };
 
 } // namespace mozilla
 } // namespace a11y
--- a/accessible/tests/mochitest/relations.js
+++ b/accessible/tests/mochitest/relations.js
@@ -92,16 +92,71 @@ function testRelation(aIdentifier, aRelT
     for (var idx = 0; idx < targets.length && relatedAcc != targets[idx]; idx++);
 
     if (idx == targets.length)
       ok(false, "There is unexpected target" + prettyName(relatedAcc) + "of" + relDescr);
   }
 }
 
 /**
+ * Test that the given accessible relations don't exist.
+ *
+ * @param aIdentifier           [in] identifier to get an accessible, may be ID
+ *                              attribute or DOM element or accessible object
+ * @param aRelType              [in] relation type (see constants above)
+ * @param aUnrelatedIdentifiers [in] identifier or array of identifiers of
+ *                              accessibles that shouldn't exist for this
+ *                              relation.
+ */
+function testAbsentRelation(aIdentifier, aRelType, aUnrelatedIdentifiers)
+{
+  var relation = getRelationByType(aIdentifier, aRelType);
+
+  var relDescr = getRelationErrorMsg(aIdentifier, aRelType);
+  var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true);
+
+  if (!aUnrelatedIdentifiers) {
+    ok(false, "No identifiers given for unrelated accessibles.");
+    return;
+  }
+
+  if (!relation || !relation.targetsCount) {
+    ok(true, "No relations exist.");
+    return;
+  }
+
+  var relatedIds = (aUnrelatedIdentifiers instanceof Array) ?
+    aUnrelatedIdentifiers : [aUnrelatedIdentifiers];
+
+  var targets = [];
+  for (var idx = 0; idx < relatedIds.length; idx++)
+    targets.push(getAccessible(relatedIds[idx]));
+
+  if (targets.length != relatedIds.length)
+    return;
+
+  var actualTargets = relation.getTargets();
+
+  // Any found targets that match given accessibles should be called out.
+  for (var idx = 0; idx < targets.length; idx++) {
+    var notFound = true;
+    var enumerate = actualTargets.enumerate();
+    while (enumerate.hasMoreElements()) {
+      var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible);
+      if (targets[idx] == relatedAcc) {
+        notFound = false;
+        break;
+      }
+    }
+
+    ok(notFound, prettyName(relatedIds[idx]) + " is a target of " + relDescr);
+  }
+}
+
+/**
  * Return related accessible for the given relation type.
  *
  * @param aIdentifier  [in] identifier to get an accessible, may be ID attribute
  *                     or DOM element or accessible object
  * @param aRelType     [in] relation type (see constants above)
  */
 function getRelationByType(aIdentifier, aRelType)
 {
--- a/accessible/tests/mochitest/relations/test_general.html
+++ b/accessible/tests/mochitest/relations/test_general.html
@@ -79,16 +79,28 @@
       testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
       testRelation("treeitem6", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem7", RELATION_NODE_CHILD_OF, "treeitem6");
       testRelation("tree2_ti1", RELATION_NODE_CHILD_OF, "tree2");
       testRelation("tree2_ti1a", RELATION_NODE_CHILD_OF, "tree2_ti1");
       testRelation("tree2_ti1b", RELATION_NODE_CHILD_OF, "tree2_ti1");
 
+      // 'node child of' relation for row role in grid.
+      // Relation for row associated using aria-owns should exist.
+      testRelation("simplegrid-ownrow", RELATION_NODE_CHILD_OF, "simplegrid");
+      // Relation for row associated using aria-level should exist.
+      testRelation("simplegrid-row3", RELATION_NODE_CHILD_OF,
+                   "simplegrid-row2");
+      // Relations for hierarchical children elements shouldn't exist.
+      testAbsentRelation("simplegrid-row1", RELATION_NODE_CHILD_OF,
+                         "simplegrid");
+      testAbsentRelation("simplegrid-row2", RELATION_NODE_CHILD_OF,
+                         "simplegrid");
+
       // 'node child of' relation for row role of treegrid
       testRelation("treegridrow1", RELATION_NODE_CHILD_OF, "treegrid");
       testRelation("treegridrow2", RELATION_NODE_CHILD_OF, "treegrid");
       testRelation("treegridrow3", RELATION_NODE_CHILD_OF, "treegridrow2");
 
       // 'node child of' relation for lists organized by groups
       testRelation("listitem1", RELATION_NODE_CHILD_OF, "list");
       testRelation("listitem1.1", RELATION_NODE_CHILD_OF, "listitem1");
@@ -113,16 +125,24 @@
       testRelation("tree2", RELATION_NODE_PARENT_OF, "tree2_ti1"); // group role
       testRelation("tree2_ti1", RELATION_NODE_PARENT_OF,
                    ["tree2_ti1a", "tree2_ti1b"]); // group role
 
       testRelation("treegridrow2", RELATION_NODE_PARENT_OF, "treegridrow3");
       testRelation("treegrid", RELATION_NODE_PARENT_OF,
                    ["treegridrow1", "treegridrow2"]);
 
+      // 'node parent of' relation on ARIA grid.
+      // Should only have relation to child added through aria-owns.
+      testRelation("simplegrid", RELATION_NODE_PARENT_OF, "simplegrid-ownrow");
+      // 'node parent of' relation on ARIA grid's row.
+      // Should only have relation to child through aria-level.
+      testRelation("simplegrid-row2", RELATION_NODE_PARENT_OF,
+                   "simplegrid-row3");
+
       // 'node parent of' relation on ARIA list structured by groups
       testRelation("list", RELATION_NODE_PARENT_OF,
                    "listitem1");
       testRelation("listitem1", RELATION_NODE_PARENT_OF,
                    [ "listitem1.1", "listitem1.2" ]);
 
       // aria-atomic
       testRelation(getNode("atomic").firstChild, RELATION_MEMBER_OF, "atomic");
@@ -285,16 +305,32 @@
     <div role="treeitem" id="treeitem4" aria-level="1">Green</div>
     <div role="treeitem" id="treeitem5" aria-level="2">Light green</div>
     <div role="treeitem" id="treeitem6" aria-level="1">Green2</div>
     <div role="group">
       <div role="treeitem" id="treeitem7">Super light green</div>
     </div>
   </div>
 
+  <div aria-owns="simplegrid-ownrow" role="grid" id="simplegrid">
+    <div role="row" id="simplegrid-row1" aria-level="1">
+      <div role="gridcell">cell 1,1</div>
+      <div role="gridcell">cell 1,2</div>
+    </div>
+    <div role="row" id="simplegrid-row2" aria-level="1">
+      <div role="gridcell">cell 2,1</div>
+      <div role="gridcell">cell 2,2</div>
+    </div>
+    <div role="row" id="simplegrid-row3" aria-level="2">
+      <div role="gridcell">cell 3,1</div>
+      <div role="gridcell">cell 3,2</div>
+    </div>
+  </div>
+  <div role="row" id="simplegrid-ownrow"></div>
+
   <ul role="tree" id="tree2">
     <li role="treeitem" id="tree2_ti1">Item 1
       <ul role="group">
         <li role="treeitem" id="tree2_ti1a">Item 1A</li>
         <li role="treeitem" id="tree2_ti1b">Item 1B</li>
       </ul>
     </li>
   </ul>
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -1,6 +1,7 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/Math.jsm
@@ -0,0 +1,11 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["Math"];
+
+this.Math = {
+  square: function (x) { return x * x; }
+};
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+  head.js
+  Math.jsm
+  math.js
+  data.json
+  invalid.json
+[browser_sdk_loader_sdk_modules.js]
+[browser_sdk_loader_sdk_gui_modules.js]
+[browser_sdk_loader_jsm_modules.js]
+[browser_sdk_loader_js_modules.js]
+[browser_sdk_loader_json.js]
+[browser_sdk_loader_chrome.js]
+[browser_sdk_loader_chrome_in_sdk.js]
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser_sdk_loader_chrome.js
@@ -0,0 +1,24 @@
+function test () {
+  let loader = makeLoader();
+  let module = Module("./main", gTestPath);
+  let require = Require(loader, module);
+
+  const { Ci, Cc, Cu, components } = require("chrome");
+
+  let { generateUUID } = Cc["@mozilla.org/uuid-generator;1"]
+                         .getService(Ci.nsIUUIDGenerator);
+  ok(isUUID(generateUUID()), "chrome.Cc and chrome.Ci works");
+  
+  let { ID: parseUUID } = components;
+  let uuidString = "00001111-2222-3333-4444-555566667777";
+  let parsed = parseUUID(uuidString);
+  is(parsed, "{" + uuidString + "}", "chrome.components works");
+
+  const { defer } = Cu.import("resource://gre/modules/Promise.jsm").Promise;
+  let { promise, resolve } = defer();
+  resolve(5);
+  promise.then(val => {
+    is(val, 5, "chrome.Cu works");
+    finish();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser_sdk_loader_chrome_in_sdk.js
@@ -0,0 +1,19 @@
+function test () {
+  let loader = makeLoader();
+  let module = Module("./main", gTestPath);
+  let require = Require(loader, module);
+
+  // sdk/util/uuid uses Cc, Ci, components
+  const { uuid } = require("sdk/util/uuid");
+
+  ok(isUUID(uuid()), "chrome.Cc and chrome.Ci works in SDK includes");
+
+  let uuidString = '00001111-2222-3333-4444-555566667777';
+  let parsed = uuid(uuidString);
+  is(parsed, '{' + uuidString + '}', "chrome.components works in SDK includes");
+
+  // sdk/base64 uses Cu
+  const { encode } = require("sdk/base64");
+  is(encode("hello"), "aGVsbG8=", "chrome.Cu works in SDK includes");
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser_sdk_loader_js_modules.js
@@ -0,0 +1,33 @@
+function test () {
+  let loader = makeLoader();
+  let module = Module("./main", gTestPath);
+  let require = Require(loader, module);
+
+  try {
+    let Model = require("./cant-find-me");
+    ok(false, "requiring a JS module that doesn't exist should throw");
+  }
+  catch (e) {
+    ok(e, "requiring a JS module that doesn't exist should throw");
+  }
+
+
+  /*
+   * Relative resource:// URI of JS
+   */
+
+  let { square } = require("./math");
+  is(square(5), 25, "loads relative URI of JS");
+
+  /*
+   * Absolute resource:// URI of JS
+   */
+
+  let { has } = require("resource://gre/modules/commonjs/sdk/util/array");
+  let testArray = ['rock', 'paper', 'scissors'];
+
+  ok(has(testArray, 'rock'), "loads absolute resource:// URI of JS");
+  ok(!has(testArray, 'dragon'), "loads absolute resource:// URI of JS");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser_sdk_loader_jsm_modules.js
@@ -0,0 +1,31 @@
+function test () {
+  let loader = makeLoader();
+  let module = Module("./main", gTestPath);
+  let require = Require(loader, module);
+
+  try {
+    let Model = require("resource://gre/modules/BlinkTag.jsm");
+    ok(false, "requiring a JS module that doesn't exist should throw");
+  }
+  catch (e) {
+    ok(e, "requiring a JS module that doesn't exist should throw");
+  }
+
+  /*
+   * Relative resource:// URI of JSM
+   */
+
+  let { square } = require("./Math.jsm").Math;
+  is(square(5), 25, "loads relative URI of JSM");
+
+  /*
+   * Absolute resource:// URI of JSM
+   */
+  let { defer } = require("resource://gre/modules/Promise.jsm").Promise;
+  let { resolve, promise } = defer();
+  resolve(5);
+  promise.then(val => {
+    is(val, 5, "loads absolute resource:// URI of JSM");
+  }).then(finish);
+
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser_sdk_loader_json.js
@@ -0,0 +1,23 @@
+function test () {
+  let loader = makeLoader();
+  let module = Module("./main", gTestPath);
+  let require = Require(loader, module);
+
+  /*
+   * Relative resource:// URI of json
+   */
+
+  let data = require("./data.json");
+  is(data.title, "jetpack mochitests", "loads relative JSON");
+  is(data.dependencies.underscore, "1.0.0", "loads relative JSON");
+
+  try {
+    let data = require("./invalid.json");
+    ok(false, "parsing an invalid JSON should throw");
+  }
+  catch (e) {
+    ok(e, "parsing an invalid JSON should throw");
+  }
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser_sdk_loader_sdk_gui_modules.js
@@ -0,0 +1,18 @@
+function test () {
+  // Load `constructor` as global since tabs uses `traits`
+  // that use this module
+  let loader = makeLoader({ globals: constructor });
+  let module = Module("./main", "scratchpad://");
+  let require = Require(loader, module);
+
+  let tabs = require("sdk/tabs");
+
+  tabs.open({
+    url: "about:blank",
+    onReady: function (tab) {
+      is(tab.url, "about:blank", "correct uri for tab");
+      is(tabs.activeTab, tab, "correctly active tab");
+      tab.close(finish);
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/browser_sdk_loader_sdk_modules.js
@@ -0,0 +1,22 @@
+function test () {
+  let loader = makeLoader();
+  let module = Module("./main", "scratchpad://");
+  let require = Require(loader, module);
+
+  let { has } = require("sdk/util/array");
+  let { extend } = require("sdk/util/object");
+  let testArray = [1, 'hello', 2, 'hi'];
+
+  ok(has(testArray, 1), 'loader loading simple array utils');
+  ok(has(testArray, 'hello'), 'loader loading simple array utils');
+  ok(!has(testArray, 4), 'loader loading simple array utils');
+
+  let testObj1 = { strings: 6, prop: [] };
+  let testObj2 = { name: 'Tosin Abasi', strings: 8 };
+  let extended = extend(testObj1, testObj2);
+
+  is(extended.name, 'Tosin Abasi', 'loader loading simple object utils');
+  is(extended.strings, 8, 'loader loading simple object utils');
+  is(extended.prop, testObj1.prop, 'loader loading simple object utils');
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/data.json
@@ -0,0 +1,6 @@
+{
+  "title": "jetpack mochitests",
+  "dependencies": {
+    "underscore": "1.0.0"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/head.js
@@ -0,0 +1,79 @@
+/* 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/. */
+
+const { utils: Cu } = Components;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const LoaderModule = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}).Loader;
+const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+let {
+  Loader, main, Module, Require, unload
+} = LoaderModule;
+
+let CURRENT_DIR = gTestPath.replace(/\/[^\/]*\.js$/,'/');
+let loaders = [];
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+
+registerCleanupFunction(() => {
+  info("finish() was called, cleaning up...");
+  loaders.forEach(unload);
+  Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+});
+
+function makePaths (root) {
+  return {
+    './': CURRENT_DIR,
+    '': 'resource://gre/modules/commonjs/'
+  };
+}
+
+function makeLoader (options) {
+  let { paths, globals } = options || {};
+
+  // We have to have `console` as a global, otherwise
+  // many SDK modules will fail
+  // bug 961252
+  let globalDefaults = {
+    console: console
+  };
+
+  let loader = Loader({
+    paths: paths || makePaths(),
+    globals: extend({}, globalDefaults, globals) || null,
+    modules: {
+      // Needed because sdk/ modules reference utilities in
+      // `toolkit/loader`, until Bug 961194 is completed
+      'toolkit/loader': LoaderModule
+    },
+    // We need rootURI due to `sdk/self` (or are using native loader)
+    // which overloads with pseudo modules
+    // bug 961245
+    rootURI: CURRENT_DIR,
+    // We also need metadata dummy object
+    // bug 961245
+    metadata: {}
+  });
+
+  loaders.push(loader);
+  return loader;
+}
+
+function isUUID (string) {
+  return /^\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}$/.test(string);
+}
+
+function extend (...objs) {
+  if (objs.length === 0 || objs.length === 1)
+    return objs[0] || {};
+
+  for (let i = objs.length; i > 1; i--) {
+    for (var prop in objs[i - 1])
+      objs[0][prop] = objs[i - 1][prop];
+  }
+  return objs[0];
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/invalid.json
@@ -0,0 +1,1 @@
+this isnt json
new file mode 100644
--- /dev/null
+++ b/addon-sdk/test/math.js
@@ -0,0 +1,7 @@
+/* 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";
+
+exports.square = function square (x) { return x * x; }
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -16,16 +16,26 @@ function log(msg) {
 function error(msg) {
   console.log("Firefox Account Error: " + msg + "\n");
 };
 
 let wrapper = {
   iframe: null,
 
   init: function () {
+    let weave = Cc["@mozilla.org/weave/service;1"]
+                  .getService(Ci.nsISupports)
+                  .wrappedJSObject;
+
+    // Don't show about:accounts with FxA disabled.
+    if (!weave.fxAccountsEnabled) {
+      document.body.remove();
+      return;
+    }
+
     let iframe = document.getElementById("remote");
     this.iframe = iframe;
     iframe.addEventListener("load", this);
 
     try {
       iframe.src = fxAccounts.getAccountsURI();
     } catch (e) {
       error("Couldn't init Firefox Account wrapper: " + e.message);
@@ -48,16 +58,21 @@ let wrapper = {
    * onLogin handler receives user credentials from the jelly after a
    * sucessful login and stores it in the fxaccounts service
    *
    * @param accountData the user's account data and credentials
    */
   onLogin: function (accountData) {
     log("Received: 'login'. Data:" + JSON.stringify(accountData));
 
+    if (accountData.customizeSync) {
+      Services.prefs.setBoolPref("services.sync.needsCustomization", true);
+      delete accountData.customizeSync;
+    }
+
     fxAccounts.setSignedInUser(accountData).then(
       () => {
         this.injectData("message", { status: "login" });
         // until we sort out a better UX, just leave the jelly page in place.
         // If the account email is not yet verified, it will tell the user to
         // go check their email, but then it will *not* change state after
         // the verification completes (the browser will begin syncing, but
         // won't notify the user). If the email has already been verified,
@@ -128,17 +143,17 @@ let wrapper = {
     this.iframe.contentWindow.postMessage(data, authUrl);
   },
 };
 
 
 // Button onclick handlers
 function handleOldSync() {
   // we just want to navigate the current tab to the new location...
-  window.location = "https://services.mozilla.com/legacysync";
+  window.location = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
 }
 
 function getStarted() {
   hide("intro");
   hide("stage");
   show("remote");
 }
 
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -4,56 +4,154 @@
 
 XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
   return Cu.import("resource://gre/modules/FxAccountsCommon.js", {});
 });
 
 let gFxAccounts = {
 
   _initialized: false,
+  _originalLabel: null,
+  _inCustomizationMode: false,
+
+  get weave() {
+    delete this.weave;
+    return this.weave = Cc["@mozilla.org/weave/service;1"]
+                          .getService(Ci.nsISupports)
+                          .wrappedJSObject;
+  },
 
   get topics() {
     delete this.topics;
     return this.topics = [
-      FxAccountsCommon.ONVERIFIED_NOTIFICATION
+      FxAccountsCommon.ONLOGIN_NOTIFICATION,
+      FxAccountsCommon.ONVERIFIED_NOTIFICATION,
+      FxAccountsCommon.ONLOGOUT_NOTIFICATION
     ];
   },
 
+  get button() {
+    delete this.button;
+    return this.button = document.getElementById("PanelUI-fxa-status");
+  },
+
+  get syncNeedsCustomization() {
+    try {
+      return Services.prefs.getBoolPref("services.sync.needsCustomization");
+    } catch (e) {
+      return false;
+    }
+  },
+
   init: function () {
     if (this._initialized) {
       return;
     }
 
     for (let topic of this.topics) {
       Services.obs.addObserver(this, topic, false);
     }
 
+    gNavToolbox.addEventListener("customizationstarting", this);
+    gNavToolbox.addEventListener("customizationending", this);
+
+    // Save the button's original label so that
+    // we can restore it if overridden later.
+    this._originalLabel = this.button.getAttribute("label");
     this._initialized = true;
+
+    this.updateUI();
   },
 
   uninit: function () {
     if (!this._initialized) {
       return;
     }
 
     for (let topic of this.topics) {
       Services.obs.removeObserver(this, topic);
     }
 
     this._initialized = false;
   },
 
   observe: function (subject, topic) {
-    this.showDoorhanger();
+    if (topic != FxAccountsCommon.ONVERIFIED_NOTIFICATION) {
+      this.updateUI();
+    } else if (!this.syncNeedsCustomization) {
+      this.showSyncStartedDoorhanger();
+    }
   },
 
-  showDoorhanger: function () {
-    let panel = document.getElementById("sync-popup");
+  handleEvent: function (event) {
+    this._inCustomizationMode = event.type == "customizationstarting";
+    this.updateUI();
+  },
+
+  showDoorhanger: function (id) {
+    let panel = document.getElementById(id);
     let anchor = document.getElementById("PanelUI-menu-button");
 
     let iconAnchor =
       document.getAnonymousElementByAttribute(anchor, "class",
                                               "toolbarbutton-icon");
 
     panel.hidden = false;
     panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
+  },
+
+  showSyncStartedDoorhanger: function () {
+    this.showDoorhanger("sync-start-panel");
+  },
+
+  showSyncFailedDoorhanger: function () {
+    this.showDoorhanger("sync-error-panel");
+  },
+
+  updateUI: function () {
+    // Bail out if FxA is disabled.
+    if (!this.weave.fxAccountsEnabled) {
+      return;
+    }
+
+    // FxA is enabled, show the widget.
+    this.button.removeAttribute("hidden");
+
+    // Make sure the button is disabled in customization mode.
+    if (this._inCustomizationMode) {
+      this.button.setAttribute("disabled", "true");
+    } else {
+      this.button.removeAttribute("disabled");
+    }
+
+    // If the user is signed into their Firefox account and we are not
+    // currently in customization mode, show their email address.
+    fxAccounts.getSignedInUser().then(userData => {
+      if (userData && !this._inCustomizationMode) {
+        this.button.setAttribute("signedin", "true");
+        this.button.setAttribute("label", userData.email);
+        this.button.setAttribute("tooltiptext", userData.email);
+      } else {
+        this.button.removeAttribute("signedin");
+        this.button.setAttribute("label", this._originalLabel);
+        this.button.removeAttribute("tooltiptext");
+      }
+    });
+  },
+
+  toggle: function (event) {
+    if (event.originalTarget.hasAttribute("signedin")) {
+      this.openPreferences();
+    } else {
+      this.openSignInPage();
+    }
+
+    PanelUI.hide();
+  },
+
+  openPreferences: function () {
+    openPreferences("paneSync");
+  },
+
+  openSignInPage: function () {
+    switchToTabHavingURI("about:accounts", true);
   }
 };
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -378,23 +378,60 @@
         <button class="ctrlTab-preview" flex="1"/>
       </hbox>
       <hbox pack="center">
         <button id="ctrlTab-showAll" class="ctrlTab-preview" noicon="true"/>
       </hbox>
     </panel>
 
     <!-- Sync Panel -->
-    <panel id="sync-popup" type="arrow" hidden="true" noautofocus="true"
-           level="top" onclick="this.hidePopup();">
-      <hbox id="sync-popup-outer">
-        <image id="sync-popup-icon"/>
-        <vbox id="sync-popup-inner">
-          <description id="sync-popup-desc-syncing" value="&syncPanel.descriptionSyncing;"/>
-          <description id="sync-popup-desc-prefs">&syncPanel.descriptionPrefs;</description>
+    <panel id="sync-start-panel" class="sync-panel" type="arrow" hidden="true"
+           noautofocus="true" level="top" onclick="this.hidePopup();">
+      <hbox class="sync-panel-outer">
+        <image class="sync-panel-icon"/>
+        <vbox class="sync-panel-inner">
+          <description id="sync-start-panel-title"
+                       value="&syncStartPanel.title;"/>
+          <description id="sync-start-panel-subtitle">
+#ifdef XP_UNIX
+            &syncStartPanel.subTitleUnix;
+#else
+            &syncStartPanel.subTitle;
+#endif
+          </description>
+        </vbox>
+      </hbox>
+    </panel>
+
+    <!-- Sync Error Panel -->
+    <panel id="sync-error-panel" class="sync-panel" type="arrow" hidden="true"
+           noautofocus="true" level="top" onclick="this.hidePopup();">
+      <hbox class="sync-panel-outer">
+        <image class="sync-panel-icon"/>
+        <vbox class="sync-panel-inner">
+          <description id="sync-error-panel-title"
+                       value="&syncErrorPanel.title;"/>
+          <description id="sync-error-panel-subtitle"
+                       value="&syncErrorPanel.subTitle;"/>
+          <hbox class="sync-panel-button-box">
+            <button class="sync-panel-button"
+#ifdef XP_UNIX
+                    label="&syncErrorPanel.prefButtonUnix.label;"
+                    accesskey="&syncErrorPanel.prefButtonUnix.accesskey;"
+#else
+                    label="&syncErrorPanel.prefButton.label;"
+                    accesskey="&syncErrorPanel.prefButton.accesskey;"
+#endif
+                    onclick="gFxAccounts.openPreferences();"/>
+            <spacer flex="1"/>
+            <button class="sync-panel-button"
+                    label="&syncErrorPanel.signInButton.label;"
+                    accesskey="&syncErrorPanel.signInButton.accesskey;"
+                    onclick="gFxAccounts.openSignInPage();"/>
+          </hbox>
         </vbox>
       </hbox>
     </panel>
 
     <!-- Bookmarks and history tooltip -->
     <tooltip id="bhTooltip"/>
 
     <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/customize.css
@@ -0,0 +1,28 @@
+/* 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/. */
+
+:root {
+  font-size: 80%;
+}
+
+#sync-customize-pane {
+  -moz-padding-start: 74px;
+  background: top left url(chrome://browser/skin/sync-128.png) no-repeat;
+  background-size: 64px;
+}
+
+#sync-customize-title {
+  -moz-margin-start: 0;
+  padding-bottom: 0.5em;
+  font-weight: bold;
+}
+
+#sync-customize-subtitle {
+  font-size: 90%;
+}
+
+checkbox {
+  margin: 0;
+  padding: 0.5em 0 0;
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/customize.js
@@ -0,0 +1,9 @@
+/* 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";
+
+addEventListener("dialogaccept", function () {
+  window.arguments[0].accepted = true;
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/customize.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/customize.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % syncCustomizeDTD SYSTEM "chrome://browser/locale/syncCustomize.dtd">
+%syncCustomizeDTD;
+]>
+<dialog id="sync-customize"
+        windowtype="Sync:Customize"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        title="&syncCustomize.dialog.title;"
+        buttonlabelaccept="&syncCustomize.acceptButton.label;"
+        buttons="accept">
+
+  <prefpane id="sync-customize-pane">
+    <preferences>
+      <preference id="engine.bookmarks" name="services.sync.engine.bookmarks" type="bool"/>
+      <preference id="engine.history"   name="services.sync.engine.history"   type="bool"/>
+      <preference id="engine.tabs"      name="services.sync.engine.tabs"      type="bool"/>
+      <preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
+      <preference id="engine.addons"    name="services.sync.engine.addons"    type="bool"/>
+      <preference id="engine.prefs"     name="services.sync.engine.prefs"     type="bool"/>
+    </preferences>
+
+    <label id="sync-customize-title" value="&syncCustomize.title;"/>
+    <description id="sync-customize-subtitle"
+#ifdef XP_UNIX
+                 value="&syncCustomize.subTitleUnix;"
+#else
+                 value="&syncCustomize.subTitle;"
+#endif
+                 />
+
+    <checkbox label="&engine.bookmarks.label;"
+              accesskey="&engine.bookmarks.accesskey;"
+              preference="engine.bookmarks"/>
+    <checkbox label="&engine.history.label;"
+              accesskey="&engine.history.accesskey;"
+              preference="engine.history"/>
+    <checkbox label="&engine.tabs.label;"
+              accesskey="&engine.tabs.accesskey;"
+              preference="engine.tabs"/>
+    <checkbox label="&engine.passwords.label;"
+              accesskey="&engine.passwords.accesskey;"
+              preference="engine.passwords"/>
+    <checkbox label="&engine.addons.label;"
+              accesskey="&engine.addons.accesskey;"
+              preference="engine.addons"/>
+    <checkbox label="&engine.prefs.label;"
+              accesskey="&engine.prefs.accesskey;"
+              preference="engine.prefs"/>
+  </prefpane>
+
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/customize.js" />
+
+</dialog>
--- a/browser/base/content/sync/utils.js
+++ b/browser/base/content/sync/utils.js
@@ -9,16 +9,23 @@ const PERMISSIONS_RWUSR = 0x180;
 
 // Weave should always exist before before this file gets included.
 let gSyncUtils = {
   get bundle() {
     delete this.bundle;
     return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
   },
 
+  get fxAccountsEnabled() {
+    let service = Components.classes["@mozilla.org/weave/service;1"]
+                            .getService(Components.interfaces.nsISupports)
+                            .wrappedJSObject;
+    return service.fxAccountsEnabled;
+  },
+
   // opens in a new window if we're in a modal prefwindow world, in a new tab otherwise
   _openLink: function (url) {
     let thisDocEl = document.documentElement,
         openerDocEl = window.opener && window.opener.document.documentElement;
     if (thisDocEl.id == "accountSetup" && window.opener &&
         openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
       openUILinkIn(url, "window");
     else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
@@ -66,21 +73,23 @@ let gSyncUtils = {
       this.openChange("UpdatePassphrase");
   },
 
   resetPassword: function () {
     this._openLink(Weave.Service.pwResetURL);
   },
 
   openToS: function () {
-    this._openLink(Weave.Svc.Prefs.get("termsURL"));
+    let root = this.fxAccountsEnabled ? "fxa." : "";
+    this._openLink(Weave.Svc.Prefs.get(root + "termsURL"));
   },
 
   openPrivacyPolicy: function () {
-    this._openLink(Weave.Svc.Prefs.get("privacyURL"));
+    let root = this.fxAccountsEnabled ? "fxa." : "";
+    this._openLink(Weave.Svc.Prefs.get(root + "privacyURL"));
   },
 
   openFirstSyncProgressPage: function () {
     this._openLink("about:sync-progress");
   },
 
   /**
    * Prepare an invisible iframe with the passphrase backup document.
--- a/browser/base/content/test/general/browser_aboutAccounts.js
+++ b/browser/base/content/test/general/browser_aboutAccounts.js
@@ -4,25 +4,27 @@
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 
 registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
+  Services.prefs.clearUserPref("identity.fxaccounts.enabled");
   Services.prefs.clearUserPref("identity.fxaccounts.remote.uri");
 });
 
 let gTests = [
 
 {
   desc: "Test the remote commands",
   setup: function ()
   {
+    Services.prefs.setBoolPref("identity.fxaccounts.enabled", true);
     Services.prefs.setCharPref("identity.fxaccounts.remote.uri",
                                "https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
   },
   run: function ()
   {
     let deferred = Promise.defer();
 
     let results = 0;
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -96,16 +96,19 @@ browser.jar:
         content/browser/sync/genericChange.js         (content/sync/genericChange.js)
         content/browser/sync/key.xhtml                (content/sync/key.xhtml)
         content/browser/sync/notification.xml         (content/sync/notification.xml)
         content/browser/sync/quota.xul                (content/sync/quota.xul)
         content/browser/sync/quota.js                 (content/sync/quota.js)
         content/browser/sync/utils.js                 (content/sync/utils.js)
         content/browser/sync/progress.js              (content/sync/progress.js)
         content/browser/sync/progress.xhtml           (content/sync/progress.xhtml)
+*       content/browser/sync/customize.xul            (content/sync/customize.xul)
+        content/browser/sync/customize.js             (content/sync/customize.js)
+        content/browser/sync/customize.css            (content/sync/customize.css)
 #endif
         content/browser/safeMode.css                  (content/safeMode.css)
         content/browser/safeMode.js                   (content/safeMode.js)
         content/browser/safeMode.xul                  (content/safeMode.xul)
 *       content/browser/sanitize.js                   (content/sanitize.js)
 *       content/browser/sanitize.xul                  (content/sanitize.xul)
 *       content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -10,35 +10,39 @@
        noautofocus="true">
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
     <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
-        <!-- The parentNode is used so that the footer is presented as the anchor
-             instead of just the button being the anchor. -->
-        <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
-                       exitLabel="&appMenuCustomizeExit.label;"
-                       oncommand="gCustomizeMode.toggle();"/>
-        <toolbarseparator/>
-        <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
-                       tooltiptext="&helpMenu.label;"
-                       oncommand="PanelUI.showHelpView(this.parentNode);"/>
-        <toolbarseparator/>
-        <toolbarbutton id="PanelUI-quit"
+        <toolbarbutton id="PanelUI-fxa-status" label="&fxaSignIn.label;"
+                       oncommand="gFxAccounts.toggle(event);"
+                       hidden="true"/>
+
+        <hbox id="PanelUI-footer-inner">
+          <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
+                         exitLabel="&appMenuCustomizeExit.label;"
+                         oncommand="gCustomizeMode.toggle();"/>
+          <toolbarseparator/>
+          <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
+                         tooltiptext="&helpMenu.label;"
+                         oncommand="PanelUI.showHelpView(this.parentNode);"/>
+          <toolbarseparator/>
+          <toolbarbutton id="PanelUI-quit"
 #ifdef XP_WIN
-                       label="&quitApplicationCmdWin.label;"
-                       tooltiptext="&quitApplicationCmdWin.label;"
+                         label="&quitApplicationCmdWin.label;"
+                         tooltiptext="&quitApplicationCmdWin.label;"
 #else
-                       label="&quitApplicationCmd.label;"
-                       tooltiptext="&quitApplicationCmd.label;"
+                         label="&quitApplicationCmd.label;"
+                         tooltiptext="&quitApplicationCmd.label;"
 #endif
-                       command="cmd_quitApplication"/>
+                         command="cmd_quitApplication"/>
+        </hbox>
       </footer>
     </panelview>
 
     <panelview id="PanelUI-history" flex="1">
       <label value="&appMenuHistory.label;" class="panel-subview-header"/>
       <toolbarbutton id="appMenuViewHistorySidebar"
                      label="&appMenuHistory.viewSidebar.label;"
                      type="checkbox"
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -108,14 +108,14 @@
   </binding>
 
   <binding id="download-toolbarbutton"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children />
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
-                 xbl:inherits="value=label,accesskey,crop"/>
+                 xbl:inherits="value=label,accesskey,crop,wrap"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
-                 xbl:inherits="xbl:text=label,accesskey"/>
+                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
     </content>
   </binding>
 </bindings>
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -3,16 +3,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://services-sync/main.js");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 const PAGE_NO_ACCOUNT = 0;
 const PAGE_HAS_ACCOUNT = 1;
 const PAGE_NEEDS_UPDATE = 2;
+const PAGE_PLEASE_WAIT = 3;
+const FXA_PAGE_LOGGED_OUT = 4;
+const FXA_PAGE_LOGGED_IN = 5;
+
+// Indexes into the "login status" deck.
+// We are in a successful verified state - everything should work!
+const FXA_LOGIN_VERIFIED = 0;
+// We have logged in to an unverified account.
+const FXA_LOGIN_UNVERIFIED = 1;
+// We are logged in locally, but the server rejected our credentials.
+const FXA_LOGIN_FAILED = 2;
 
 let gSyncPane = {
   _stringBundle: null,
   prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
               "engine.tabs", "engine.history"],
 
   get page() {
     return document.getElementById("weavePrefsDeck").selectedIndex;
@@ -39,16 +50,20 @@ let gSyncPane = {
                                 .getService(Components.interfaces.nsISupports)
                                 .wrappedJSObject;
 
     if (xps.ready) {
       this._init();
       return;
     }
 
+    // it may take some time before we can determine what provider to use
+    // and the state of that provider, so show the "please wait" page.
+    this.page = PAGE_PLEASE_WAIT;
+
     let onUnload = function () {
       window.removeEventListener("unload", onUnload, false);
       try {
         Services.obs.removeObserver(onReady, "weave:service:ready");
       } catch (e) {}
     };
 
     let onReady = function () {
@@ -83,26 +98,69 @@ let gSyncPane = {
     }, false);
 
     this._stringBundle =
       Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
     this.updateWeavePrefs();
   },
 
   updateWeavePrefs: function () {
-    if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
-        Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
+    let service = Components.classes["@mozilla.org/weave/service;1"]
+                  .getService(Components.interfaces.nsISupports)
+                  .wrappedJSObject;
+    // service.fxAccountsEnabled is false iff sync is already configured for
+    // the legacy provider.
+    if (service.fxAccountsEnabled) {
+      // determine the fxa status...
+      this.page = PAGE_PLEASE_WAIT;
+      Components.utils.import("resource://gre/modules/FxAccounts.jsm");
+      fxAccounts.getSignedInUser().then(data => {
+        if (!data) {
+          this.page = FXA_PAGE_LOGGED_OUT;
+          return;
+        }
+        this.page = FXA_PAGE_LOGGED_IN;
+        // We are logged in locally, but maybe we are in a state where the
+        // server rejected our credentials (eg, password changed on the server)
+        let fxaLoginStatus = document.getElementById("fxaLoginStatus");
+        let enginesListDisabled;
+        // Not Verfied implies login error state, so check that first.
+        if (!data.verified) {
+          fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
+          enginesListDisabled = true;
+        // So we think we are logged in, so login problems are next.
+        } else if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
+          fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
+          enginesListDisabled = true;
+        // Else we must be golden!
+        } else {
+          fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
+          enginesListDisabled = false;
+        }
+        document.getElementById("fxaEmailAddress1").textContent = data.email;
+        document.getElementById("fxaEmailAddress2").textContent = data.email;
+        document.getElementById("fxaEmailAddress3").textContent = data.email;
+        document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName;
+        let enginesList = document.getElementById("fxaSyncEnginesList")
+        enginesList.disabled = enginesListDisabled;
+        // *sigh* - disabling the <richlistbox> draws each item as if it is disabled,
+        // but doesn't disable the checkboxes.
+        for (let checkbox of enginesList.querySelectorAll("checkbox")) {
+          checkbox.disabled = enginesListDisabled;
+        }
+      });
+    // If fxAccountEnabled is false and we are in a "not configured" state,
+    // then fxAccounts is probably fully disabled rather than just unconfigured,
+    // so handle this case.  This block can be removed once we remove support
+    // for fxAccounts being disabled.
+    } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
+               Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
-      let service = Components.classes["@mozilla.org/weave/service;1"]
-                    .getService(Components.interfaces.nsISupports)
-                    .wrappedJSObject;
-      // no concept of "pair" in an fxAccounts world.
-      if (service.fxAccountsEnabled) {
-        document.getElementById("pairDevice").hidden = true;
-      }
+    // else: sync was previously configured for the legacy provider, so we
+    // make the "old" panels available.
     } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
                Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
       this.needsUpdate();
     } else {
       this.page = PAGE_HAS_ACCOUNT;
       document.getElementById("accountName").value = Weave.Service.identity.account;
       document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
       document.getElementById("tosPP").hidden = this._usingCustomServer;
@@ -158,32 +216,79 @@ let gSyncPane = {
    *          "reset" -- reset sync
    */
   openSetup: function (wizardType) {
     let service = Components.classes["@mozilla.org/weave/service;1"]
                   .getService(Components.interfaces.nsISupports)
                   .wrappedJSObject;
 
     if (service.fxAccountsEnabled) {
-      let win = Services.wm.getMostRecentWindow("navigator:browser");
-      win.switchToTabHavingURI("about:accounts", true);
-      // seeing as we are doing this in a tab we close the prefs dialog.
-      window.close();
+      this.openContentInBrowser("about:accounts");
     } else {
       let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
       if (win) {
         win.focus();
       } else {
         window.openDialog("chrome://browser/content/sync/setup.xul",
                           "weaveSetup", "centerscreen,chrome,resizable=no",
                           wizardType);
       }
     }
   },
 
+  openContentInBrowser: function(url) {
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    if (!win) {
+      // no window to use, so use _openLink to create a new one.  We don't
+      // always use that as it prefers to open a new window rather than use
+      // an existing one.
+      gSyncUtils._openLink(url);
+      return;
+    }
+    win.switchToTabHavingURI(url, true);
+    // seeing as we are doing this in a tab we close the prefs dialog.
+    window.close();
+  },
+
+  reSignIn: function() {
+    this.openContentInBrowser("about:accounts");
+  },
+
+  manageFirefoxAccount: function() {
+    let url = Services.prefs.getCharPref("identity.fxaccounts.settings.uri");
+    this.openContentInBrowser(url);
+  },
+
+  verifyFirefoxAccount: function() {
+    Components.utils.import("resource://gre/modules/FxAccounts.jsm");
+    fxAccounts.resendVerificationEmail().then(() => {
+      fxAccounts.getSignedInUser().then(data => {
+        let sb = this._stringBundle;
+        let title = sb.GetStringFromName("firefoxAccountsVerificationSentTitle");
+        let heading = sb.formatStringFromName("firefoxAccountsVerificationSentHeading",
+                                              [data.email], 1);
+        let description = sb.GetStringFromName("firefoxAccountVerificationSentDescription");
+
+        Services.prompt.alert(window, title, heading + "\n\n" + description);
+      });
+    });
+  },
+
+  openOldSyncSupportPage: function() {
+    let url = Services.urlFormatter.formatURLPref('app.support.baseURL') + "old-sync"
+    this.openContentInBrowser(url);
+  },
+
+  unlinkFirefoxAccount: function(confirm) {
+    Components.utils.import('resource://gre/modules/FxAccounts.jsm');
+    fxAccounts.signOut().then(() => {
+      this.updateWeavePrefs();
+    });
+  },
+
   openQuotaDialog: function () {
     let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
     if (win) {
       win.focus();
     } else {
       window.openDialog("chrome://browser/content/sync/quota.xul", "",
                         "centerscreen,chrome,dialog,modal");
     }
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -33,16 +33,18 @@
 
     <script type="application/javascript"
             src="chrome://browser/content/preferences/sync.js"/>
     <script type="application/javascript"
             src="chrome://browser/content/sync/utils.js"/>
 
 
       <deck id="weavePrefsDeck">
+
+        <!-- These panels are for the "legacy" sync provider -->
         <vbox id="noAccount" align="center">
           <spacer flex="1"/>
           <description id="syncDesc">
             &weaveDesc.label;
           </description>
           <separator/>
           <label class="text-link"
                  onclick="event.stopPropagation(); gSyncPane.openSetup(null);"
@@ -170,11 +172,142 @@
             <label class="text-link"
                    onclick="gSyncPane.resetPass(); return false;"
                    value="&resetPass.label;"/>
           </hbox>
           <label class="text-link"
                  onclick="gSyncPane.startOver(true); return false;"
                  value="&unlinkDevice.label;"/>
         </vbox>
+
+        <!-- These panels are for the Firefox Accounts identity provider -->
+        <vbox id="fxaDeterminingStatus" align="center">
+          <spacer flex="1"/>
+          <p>&determiningStatus.label;</p>
+          <spacer flex="1"/>
+        </vbox>
+
+        <vbox id="noFxaAccount">
+          <description>&welcome.description;</description>
+          <label class="text-link"
+                 onclick="gSyncPane.openContentInBrowser('about:accounts?signin=true'); return false;"
+                 value="&welcome.startButton.label;"/>
+          <spacer flex="1"/>
+          <label class="text-link"
+                 onclick="gSyncPane.openOldSyncSupportPage(); return false;"
+                 value="&welcome.useOldSync.label;"/>
+          <spacer flex="10"/>
+        </vbox>
+
+        <vbox id="hasFxaAccount">
+          <groupbox id="fxaGroup">
+            <caption label="&syncBrand.fxa-singular.label;"/>
+
+            <deck id="fxaLoginStatus">
+
+              <!-- logged in and verified and all is good -->
+              <hbox flex="1">
+                <label id="fxaEmailAddress1"/>
+                <label class="text-link"
+                       onclick="gSyncPane.manageFirefoxAccount();"
+                       value="&manage.label;"/>
+                <spacer flex="1"/>
+                <vbox align="end">
+                  <button onclick="gSyncPane.unlinkFirefoxAccount(true);"
+                          label="&unlink.label;" />
+                </vbox>
+              </hbox>
+
+              <!-- logged in to an unverified account -->
+              <hbox flex="1">
+                <description>
+                  &signedInUnverified.beforename.label;
+                  <span id="fxaEmailAddress2"></span>
+                  &signedInUnverified.aftername.label;
+                </description>
+                <spacer flex="1"/>
+                <vbox align="end">
+                  <button onclick="gSyncPane.verifyFirefoxAccount();"
+                          caption="&verify.label;"/>
+                  <label class="text-link"
+                         onclick="/* no warning as account can't have previously synced */ gSyncPane.unlinkFirefoxAccount(false);"
+                         value="&forget.label;"/>
+                </vbox>
+              </hbox>
+
+              <!-- logged in locally but server rejected credentials -->
+              <hbox flex="1">
+                <description>
+                  &signedInLoginFailure.beforename.label;
+                  <span id="fxaEmailAddress3"></span>
+                  &signedInLoginFailure.aftername.label;
+                </description>
+                <spacer flex="1"/>
+                <vbox align="end">
+                  <button onclick="gSyncPane.reSignIn();"
+                         label="&signIn.label;"/>
+                  <label class="text-link"
+                         onclick="gSyncPane.unlinkFirefoxAccount(true);"
+                         value="&forget.label;"/>
+                </vbox>
+              </hbox>
+            </deck>
+          </groupbox>
+
+          <groupbox id="syncOptions">
+            <caption label="&syncBrand.fullName.label;"/>
+            <vbox>
+              <label value="&syncMy.label;" />
+              <richlistbox id="fxaSyncEnginesList"
+                           orient="vertical"
+                           onselect="if (this.selectedCount) this.clearSelection();">
+                <richlistitem>
+                  <checkbox label="&engine.addons.label;"
+                            accesskey="&engine.addons.accesskey;"
+                            preference="engine.addons"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.bookmarks.label;"
+                            accesskey="&engine.bookmarks.accesskey;"
+                            preference="engine.bookmarks"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.passwords.label;"
+                            accesskey="&engine.passwords.accesskey;"
+                            preference="engine.passwords"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.prefs.label;"
+                            accesskey="&engine.prefs.accesskey;"
+                            preference="engine.prefs"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.history.label;"
+                            accesskey="&engine.history.accesskey;"
+                            preference="engine.history"/>
+                </richlistitem>
+                <richlistitem>
+                  <checkbox label="&engine.tabs.label;"
+                            accesskey="&engine.tabs.accesskey;"
+                            preference="engine.tabs"/>
+                </richlistitem>
+              </richlistbox>
+            </vbox>
+          </groupbox>
+          <vbox>
+            <label value="&syncDeviceName.label;"
+                   accesskey="&syncDeviceName.accesskey;"
+                   control="syncComputerName"/>
+            <textbox id="fxaSyncComputerName"
+                     onchange="gSyncUtils.changeName(this)"/>
+          </vbox>
+          <hbox id="tosPP" pack="center">
+            <label class="text-link"
+                   onclick="event.stopPropagation();gSyncUtils.openToS();"
+                   value="&prefs.tosLink.label;"/>
+            <label class="text-link"
+                   onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
+                   value="&fxaPrivacyNotice.link.label;"/>
+          </hbox>
+        </vbox>
       </deck>
   </prefpane>
 </overlay>
--- a/browser/devtools/inspector/selector-search.js
+++ b/browser/devtools/inspector/selector-search.js
@@ -45,17 +45,16 @@ function SelectorSearch(aInspector, aCon
   this._onHTMLSearch = this._onHTMLSearch.bind(this);
   this._onSearchKeypress = this._onSearchKeypress.bind(this);
   this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
 
   // Options for the AutocompletePopup.
   let options = {
     panelId: "inspector-searchbox-panel",
     listBoxId: "searchbox-panel-listbox",
-    fixedWidth: true,
     autoSelect: true,
     position: "before_start",
     direction: "ltr",
     onClick: this._onListBoxKeypress,
     onKeypress: this._onListBoxKeypress,
   };
   this.searchPopup = new AutocompletePopup(this.panelDoc, options);
 
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -71,17 +71,16 @@ function MarkupView(aInspector, aFrame, 
   try {
     this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
   } catch(ex) {
     this.maxChildren = DEFAULT_MAX_CHILDREN;
   }
 
   // Creating the popup to be used to show CSS suggestions.
   let options = {
-    fixedWidth: true,
     autoSelect: true,
     theme: "auto"
   };
   this.popup = new AutocompletePopup(this.doc.defaultView.parent.document, options);
 
   this.undo = new UndoStack();
   this.undo.installController(aControllerWindow);
 
--- a/browser/devtools/shared/autocomplete-popup.js
+++ b/browser/devtools/shared/autocomplete-popup.js
@@ -20,27 +20,25 @@ loader.lazyImporter(this, "gDevTools", "
  * @param Object aOptions
  *        An object consiting any of the following options:
  *        - panelId {String} The id for the popup panel.
  *        - listBoxId {String} The id for the richlistbox inside the panel.
  *        - position {String} The position for the popup panel.
  *        - theme {String} String related to the theme of the popup.
  *        - autoSelect {Boolean} Boolean to allow the first entry of the popup
  *                     panel to be automatically selected when the popup shows.
- *        - fixedWidth {Boolean} Boolean to control dynamic width of the popup.
  *        - direction {String} The direction of the text in the panel. rtl or ltr
  *        - onSelect {String} The select event handler for the richlistbox
  *        - onClick {String} The click event handler for the richlistbox.
  *        - onKeypress {String} The keypress event handler for the richlistitems.
  */
 function AutocompletePopup(aDocument, aOptions = {})
 {
   this._document = aDocument;
 
-  this.fixedWidth = aOptions.fixedWidth || false;
   this.autoSelect = aOptions.autoSelect || false;
   this.position = aOptions.position || "after_start";
   this.direction = aOptions.direction || "ltr";
 
   this.onSelect = aOptions.onSelect;
   this.onClick = aOptions.onClick;
   this.onKeypress = aOptions.onKeypress;
 
@@ -70,17 +68,16 @@ function AutocompletePopup(aDocument, aO
 
     let mainPopupSet = this._document.getElementById("mainPopupSet");
     if (mainPopupSet) {
       mainPopupSet.appendChild(this._panel);
     }
     else {
       this._document.documentElement.appendChild(this._panel);
     }
-    this._list = null;
   }
   else {
     this._list = this._panel.firstChild;
   }
 
   if (!this._list) {
     this._list = this._document.createElementNS(XUL_NS, "richlistbox");
     this._panel.appendChild(this._list);
@@ -132,24 +129,23 @@ AutocompletePopup.prototype = {
    *        Horizontal offset in pixels from the left of the node to the left
    *        of the popup.
    * @param Number aYOffset
    *        Vertical offset in pixels from the top of the node to the starting
    *        of the popup.
    */
   openPopup: function AP_openPopup(aAnchor, aXOffset = 0, aYOffset = 0)
   {
+    this.__maxLabelLength = -1;
+    this._updateSize();
     this._panel.openPopup(aAnchor, this.position, aXOffset, aYOffset);
 
     if (this.autoSelect) {
       this.selectFirstItem();
     }
-    if (!this.fixedWidth) {
-      this._updateSize();
-    }
   },
 
   /**
    * Hide the autocomplete popup panel.
    */
   hidePopup: function AP_hidePopup()
   {
     this._panel.hidePopup();
@@ -236,90 +232,95 @@ AutocompletePopup.prototype = {
     this.clearItems();
     aItems.forEach(this.appendItem, this);
 
     // Make sure that the new content is properly fitted by the XUL richlistbox.
     if (this.isOpen) {
       if (this.autoSelect) {
         this.selectFirstItem();
       }
-      if (!this.fixedWidth) {
-        this._updateSize();
-      }
+      this._updateSize();
     }
   },
 
   /**
    * Selects the first item of the richlistbox. Note that first item here is the
    * item closes to the input element, which means that 0th index if position is
    * below, and last index if position is above.
    */
   selectFirstItem: function AP_selectFirstItem()
   {
     if (this.position.contains("before")) {
       this.selectedIndex = this.itemCount - 1;
     }
     else {
       this.selectedIndex = 0;
     }
+    this._list.ensureIndexIsVisible(this._list.selectedIndex);
+  },
+
+  __maxLabelLength: -1,
+
+  get _maxLabelLength() {
+    if (this.__maxLabelLength != -1) {
+      return this.__maxLabelLength;
+    }
+
+    let max = 0;
+    for (let i = 0; i < this._list.childNodes.length; i++) {
+      let item = this._list.childNodes[i]._autocompleteItem;
+      let str = item.label;
+      if (item.count) {
+        str += (item.count + "");
+      }
+      max = Math.max(str.length, max);
+    }
+
+    this.__maxLabelLength = max;
+    return this.__maxLabelLength;
   },
 
   /**
    * Update the panel size to fit the content.
    *
    * @private
    */
   _updateSize: function AP__updateSize()
   {
     if (!this._panel) {
       return;
     }
-    // Flush the layout so that we get the latest height.
-    this._panel.boxObject.height;
-    let height = {};
-    this._list.scrollBoxObject.getScrolledSize({}, height);
-    // Change the width of the popup only if the scrollbar is visible.
-    if (height.value > this._panel.clientHeight) {
-      this._list.width = this._panel.clientWidth + this._scrollbarWidth;
-    }
-    // Height change is required, otherwise the panel is drawn at an offset
-    // the first time.
-    this._list.height = this._list.clientHeight;
-    // This brings the panel back at right position.
-    this._list.top = 0;
-    // Move the panel to -1,-1 to realign the popup with its anchor node when
-    // decreasing the panel height.
-    this._panel.moveTo(-1, -1);
-    // Changing panel height might make the selected item out of view, so
-    // bring it back to view.
+
+    this._list.style.width = (this._maxLabelLength + 3) +"ch";
     this._list.ensureIndexIsVisible(this._list.selectedIndex);
   },
 
   /**
    * Clear all the items from the autocomplete list.
    */
   clearItems: function AP_clearItems()
   {
     // Reset the selectedIndex to -1 before clearing the list
     this.selectedIndex = -1;
 
     while (this._list.hasChildNodes()) {
       this._list.removeChild(this._list.firstChild);
     }
 
-    if (!this.fixedWidth) {
-      // Reset the panel and list dimensions. New dimensions are calculated when
-      // a new set of items is added to the autocomplete popup.
-      this._list.width = "";
-      this._list.height = "";
-      this._panel.width = "";
-      this._panel.height = "";
-      this._panel.top = "";
-      this._panel.left = "";
-    }
+    this.__maxLabelLength = -1;
+
+    // Reset the panel and list dimensions. New dimensions are calculated when
+    // a new set of items is added to the autocomplete popup.
+    this._list.width = "";
+    this._list.style.width = "";
+    this._list.height = "";
+    this._panel.width = "";
+    this._panel.height = "";
+    this._panel.top = "";
+    this._panel.left = "";
   },
 
   /**
    * Getter for the index of the selected item.
    *
    * @type number
    */
   get selectedIndex() {
@@ -492,48 +493,16 @@ AutocompletePopup.prototype = {
    * Focuses the richlistbox.
    */
   focus: function AP_focus()
   {
     this._list.focus();
   },
 
   /**
-   * Determine the scrollbar width in the current document.
-   *
-   * @private
-   */
-  get _scrollbarWidth()
-  {
-    if (this.__scrollbarWidth !== null) {
-      return this.__scrollbarWidth;
-    }
-
-    let doc = this._document;
-    if (doc.defaultView.matchMedia("(-moz-overlay-scrollbars)").matches) {
-      // This is for the Mac's floating scrollbar, which actually is drawn over
-      // the content, thus taking no extra width.
-      return (this.__scrollbarWidth = 0);
-    }
-
-    let hbox = doc.createElementNS(XUL_NS, "hbox");
-    hbox.setAttribute("style", "height: 0%; overflow: hidden");
-
-    let scrollbar = doc.createElementNS(XUL_NS, "scrollbar");
-    scrollbar.setAttribute("orient", "vertical");
-    hbox.appendChild(scrollbar);
-    doc.documentElement.appendChild(hbox);
-
-    this.__scrollbarWidth = scrollbar.clientWidth;
-    doc.documentElement.removeChild(hbox);
-
-    return this.__scrollbarWidth;
-  },
-
-  /**
    * Manages theme switching for the popup based on the devtools.theme pref.
    *
    * @private
    *
    * @param String aEvent
    *        The name of the event. In this case, "pref-changed".
    * @param Object aData
    *        An object passed by the emitter of the event. In this case, the
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1084,17 +1084,16 @@ function CssRuleView(aInspector, aDoc, a
   this._handlePrefChange = this._handlePrefChange.bind(this);
   gDevTools.on("pref-changed", this._handlePrefChange);
 
   this._onSourcePrefChanged = this._onSourcePrefChanged.bind(this);
   this._prefObserver = new PrefObserver("devtools.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
 
   let options = {
-    fixedWidth: true,
     autoSelect: true,
     theme: "auto"
   };
   this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
 
   // Create a tooltip for previewing things in the rule view (images for now)
   this.previewTooltip = new Tooltip(this.inspector.panelDoc);
   this.previewTooltip.startTogglingOnHover(this.element,
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -91,18 +91,29 @@ These should match what Safari and other
 <!ENTITY exitFullScreenCmd.label "Exit Full Screen">
 <!ENTITY exitFullScreenCmd.accesskey "F">
 <!ENTITY fullScreenCmd.label "Full Screen">
 <!ENTITY fullScreenCmd.accesskey "F">
 <!ENTITY fullScreenCmd.macCommandKey "f">
 <!ENTITY showAllTabsCmd.label "Show All Tabs">
 <!ENTITY showAllTabsCmd.accesskey "A">
 
-<!ENTITY syncPanel.descriptionSyncing "&brandShortName; is now syncing.">
-<!ENTITY syncPanel.descriptionPrefs "You can manage Sync from your browser's Preferences.">
+<!ENTITY fxaSignIn.label "Sign in to &syncBrand.shortName.label;">
+<!ENTITY syncStartPanel.title "&brandShortName; is now syncing.">
+<!ENTITY syncStartPanel.subTitle "You can manage &syncBrand.shortName.label; in Options.">
+<!ENTITY syncStartPanel.subTitleUnix "You can manage &syncBrand.shortName.label; in Preferences.">
+<!ENTITY syncErrorPanel.title "Cannot connect to &syncBrand.shortName.label;">
+<!ENTITY syncErrorPanel.subTitle "Please sign in to resume syncing.">
+<!ENTITY syncErrorPanel.prefButton.label "Options">
+<!ENTITY syncErrorPanel.prefButton.accesskey "O">
+<!ENTITY syncErrorPanel.prefButtonUnix.label "Preferences">
+<!ENTITY syncErrorPanel.prefButtonUnix.accesskey "P">
+<!ENTITY syncErrorPanel.signInButton.label "Sign In">
+<!ENTITY syncErrorPanel.signInButton.accesskey "S">
+
 
 <!ENTITY fullScreenMinimize.tooltip "Minimize">
 <!ENTITY fullScreenRestore.tooltip "Restore">
 <!ENTITY fullScreenClose.tooltip "Close">
 <!ENTITY fullScreenAutohide.label "Hide Toolbars">
 <!ENTITY fullScreenAutohide.accesskey "H">
 <!ENTITY fullScreenExit.label "Exit Full Screen Mode">
 <!ENTITY fullScreenExit.accesskey "F">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -130,8 +130,14 @@ updateAutoDesktop.accessKey=A
 syncUnlink.title=Do you want to unlink your device?
 syncUnlink.label=This device will no longer be associated with your Sync account. All of your personal data, both on this device and in your Sync account, will remain intact.
 syncUnlinkConfirm.label=Unlink
 
 # LOCALIZATION NOTE (featureEnableRequiresRestart, featureDisableRequiresRestart, restartTitle): %S = brandShortName
 featureEnableRequiresRestart=%S must restart to enable this feature.
 featureDisableRequiresRestart=%S must restart to disable this feature.
 shouldRestartTitle=Restart %S
+
+###Preferences::Sync::Firefox Accounts
+firefoxAccountsVerificationSentTitle=Verification Sent
+# LOCALIZATION NOTE: %S = user's email address.
+firefoxAccountsVerificationSentHeading=A verification link has been sent to %S
+firefoxAccountVerificationSentDescription=Please check your email and click the verification link to begin syncing.
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -40,8 +40,28 @@
 <!-- Device Settings -->
 <!ENTITY syncDeviceName.label       "Device Name:">
 <!ENTITY syncDeviceName.accesskey   "c">
 <!ENTITY unlinkDevice.label           "Unlink This Device">
 
 <!-- Footer stuff -->
 <!ENTITY prefs.tosLink.label        "Terms of Service">
 <!ENTITY prefs.ppLink.label         "Privacy Policy">
+
+<!-- Firefox Accounts stuff -->
+<!ENTITY fxaPrivacyNotice.link.label "Privacy Notice">
+<!ENTITY determiningStatus.label    "Determining your Firefox Account status…">
+<!ENTITY signedInUnverified.beforename.label "">
+<!ENTITY signedInUnverified.aftername.label "is not verified.">
+
+<!ENTITY signedInLoginFailure.beforename.label "Please sign in to reconnect">
+<!ENTITY signedInLoginFailure.aftername.label "">
+
+<!ENTITY notSignedIn.label           "You are not signed in.">
+<!ENTITY signIn.label                "Sign in">
+<!ENTITY manage.label                "Manage">
+<!ENTITY unlink.label                "Unlink This Browser…">
+<!ENTITY verify.label                "Verify Email">
+<!ENTITY forget.label                "Forget this Email">
+
+<!ENTITY welcome.description "Access your tabs, bookmarks, passwords and more wherever you use &brandShortName;.">
+<!ENTITY welcome.startButton.label "Sign in or Create an Account">
+<!ENTITY welcome.useOldSync.label "Using an older version of Sync?">
--- a/browser/locales/en-US/chrome/browser/syncBrand.dtd
+++ b/browser/locales/en-US/chrome/browser/syncBrand.dtd
@@ -1,6 +1,8 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY syncBrand.shortName.label  "Sync">
 <!ENTITY syncBrand.fullName.label   "Firefox Sync">
+<!ENTITY syncBrand.fxa-singular.label "Firefox Account">
+<!ENTITY syncBrand.fxa-plural.label "Firefox Accounts">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncCustomize.dtd
@@ -0,0 +1,27 @@
+<!-- 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/. -->
+
+<!ENTITY syncCustomize.dialog.title       "Sync Selection">
+<!ENTITY syncCustomize.acceptButton.label "Start">
+
+<!ENTITY syncCustomize.title              "What would you like to sync?">
+<!ENTITY syncCustomize.subTitle           "You can manage this selection in Options.">
+<!ENTITY syncCustomize.subTitleUnix       "You can manage this selection in Preferences.">
+
+<!--
+  These engine names are the same as in browser/preferences/sync.dtd except
+  for the last two that are marked as being specific to Desktop browsers.
+-->
+<!ENTITY engine.bookmarks.label           "Bookmarks">
+<!ENTITY engine.bookmarks.accesskey       "m">
+<!ENTITY engine.history.label             "History">
+<!ENTITY engine.history.accesskey         "r">
+<!ENTITY engine.tabs.label                "Tabs">
+<!ENTITY engine.tabs.accesskey            "T">
+<!ENTITY engine.passwords.label           "Passwords">
+<!ENTITY engine.passwords.accesskey       "P">
+<!ENTITY engine.addons.label              "Desktop Add-ons">
+<!ENTITY engine.addons.accesskey          "A">
+<!ENTITY engine.prefs.label               "Desktop Preferences">
+<!ENTITY engine.prefs.accesskey           "S">
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -13,16 +13,17 @@
     locale/browser/aboutRobots.dtd                 (%chrome/browser/aboutRobots.dtd)
     locale/browser/aboutHome.dtd                   (%chrome/browser/aboutHome.dtd)
 #ifdef MOZ_SERVICES_HEALTHREPORT
     locale/browser/aboutHealthReport.dtd           (%chrome/browser/aboutHealthReport.dtd)
 #endif
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/syncProgress.dtd                (%chrome/browser/syncProgress.dtd)
+    locale/browser/syncCustomize.dtd               (%chrome/browser/syncCustomize.dtd)
     locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
 #endif
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
     locale/browser/devtools/appcacheutils.properties  (%chrome/browser/devtools/appcacheutils.properties)
     locale/browser/devtools/debugger.dtd              (%chrome/browser/devtools/debugger.dtd)
--- a/browser/metro/base/tests/mochitest/browser_context_ui.js
+++ b/browser/metro/base/tests/mochitest/browser_context_ui.js
@@ -240,17 +240,17 @@ gTests.push({
   }
 });
 
 gTests.push({
   desc: "Bug 933989 - New tab button in tab bar always sets focus to the url bar, triggering soft keyboard",
   run: function () {
     let mozTab = yield addTab("about:mozilla");
 
-    // addTab will dismiss navbar, but lets check anyway.
+    yield hideNavBar();
     ok(!ContextUI.navbarVisible, "navbar dismissed");
 
     BrowserUI.doCommand("cmd_newTab");
     let newTab = Browser.selectedTab;
     yield newTab.pageShowPromise;
 
     yield waitForCondition(() => ContextUI.navbarVisible);
     ok(ContextUI.navbarVisible, "navbar visible");
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_sessionstore.js
@@ -0,0 +1,55 @@
+/* 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/. */
+
+"use strict";
+
+var gSessionStore = Cc["@mozilla.org/browser/sessionstore;1"]
+                    .getService(Ci.nsISessionStore);
+
+function test() {
+  runTests();
+}
+
+function getState() {
+  return JSON.parse(gSessionStore.getBrowserState());
+}
+
+function getTabData() {
+  return getState().windows[0].tabs;
+}
+
+function isValidTabData(aData) {
+  return aData && aData.entries && aData.entries.length &&
+           typeof aData.index == "number";
+}
+
+gTests.push({
+  desc: "getBrowserState tests",
+  run: function() {
+    // Wait for Session Manager to be initialized.
+    yield waitForCondition(() => window.__SSID);
+    info(window.__SSID);
+    let tabData1 = getTabData();
+    ok(tabData1.every(isValidTabData), "Tab data starts out valid");
+
+    // Open a tab.
+    let tab = Browser.addTab("about:mozilla");
+    let tabData2 = getTabData();
+    is(tabData2.length, tabData1.length, "New tab not added yet.");
+
+    // Wait for the tab's session data to be initialized.
+    yield waitForMessage("Content:SessionHistory", tab.browser.messageManager);
+    yield waitForMs(0);
+    let tabData3 = getTabData();
+    is(tabData3.length, tabData1.length + 1, "New tab added.");
+    ok(tabData3.every(isValidTabData), "Tab data still valid");
+
+    // Close the tab.
+    Browser.closeTab(tab, { forceClose: true } );
+    let tabData4 = getTabData();
+    is(tabData4.length, tabData1.length, "Closed tab removed.");
+    ok(tabData4.every(isValidTabData), "Tab data valid again");
+  }
+});
--- a/browser/metro/base/tests/mochitest/metro.ini
+++ b/browser/metro/base/tests/mochitest/metro.ini
@@ -51,16 +51,17 @@ support-files =
 [browser_link_click.js]
 [browser_menu_hoverstate.js]
 [browser_mouse_events.js]
 [browser_onscreen_keyboard.js]
 [browser_prefs_ui.js]
 [browser_private_browsing.js]
 [browser_prompt.js]
 [browser_remotetabs.js]
+[browser_sessionstore.js]
 [browser_snappedState.js]
 [browser_tabs.js]
 [browser_tabs_container.js]
 [browser_test.js]
 [browser_tiles.js]
 [browser_topsites.js]
 [browser_urlbar.js]
 [browser_urlbar_highlightURLs.js]
--- a/browser/metro/components/SessionStore.js
+++ b/browser/metro/components/SessionStore.js
@@ -333,29 +333,27 @@ SessionStore.prototype = {
 
     // Ignore non-browser windows and windows opened while shutting down
     if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState == STATE_QUITTING)
       return;
 
     // Assign it a unique identifier and create its data object
     aWindow.__SSID = "window" + gUUIDGenerator.generateUUID().toString();
     this._windows[aWindow.__SSID] = { tabs: [], selected: 0, _closedTabs: [] };
+    this._orderedWindows.push(aWindow.__SSID);
 
     // Perform additional initialization when the first window is loading
     if (this._loadState == STATE_STOPPED) {
       this._loadState = STATE_RUNNING;
       this._lastSaveTime = Date.now();
 
       // Nothing to restore, notify observers things are complete
       if (!this.shouldRestore()) {
         this._clearCache();
         Services.obs.notifyObservers(null, "sessionstore-windows-restored", "");
-
-        // If nothing is being restored, we only have our single Metro window.
-        this._orderedWindows.push(aWindow.__SSID);
       }
     }
 
     // Add tab change listeners to all already existing tabs
     let tabs = aWindow.Browser.tabs;
     for (let i = 0; i < tabs.length; i++)
       this.onTabAdd(aWindow, tabs[i].browser, true);
 
@@ -549,25 +547,23 @@ SessionStore.prototype = {
     tabData.index = aHistory.index;
     tabData.attributes = { image: aBrowser.mIconURL };
 
     aBrowser.__SS_data = tabData;
   },
 
   _getTabData: function(aWindow) {
     return aWindow.Browser.tabs
-      .filter(tab => !tab.isPrivate)
+      .filter(tab => !tab.isPrivate && tab.browser.__SS_data)
       .map(tab => {
         let browser = tab.browser;
-        if (browser.__SS_data) {
-          let tabData = browser.__SS_data;
-          if (browser.__SS_extdata)
-            tabData.extData = browser.__SS_extdata;
-          return tabData;
-        }
+        let tabData = browser.__SS_data;
+        if (browser.__SS_extdata)
+          tabData.extData = browser.__SS_extdata;
+        return tabData;
       });
   },
 
   _collectWindowData: function ss__collectWindowData(aWindow) {
     // Ignore windows not tracked by SessionStore
     if (!aWindow.__SSID || !this._windows[aWindow.__SSID])
       return;
 
@@ -796,16 +792,17 @@ SessionStore.prototype = {
         let selected = data.windows[windowIndex].selected;
 
         let currentGroupId;
         try {
           currentGroupId = JSON.parse(data.windows[windowIndex].extData["tabview-groups"]).activeGroupId;
         } catch (ex) { /* currentGroupId is undefined if user has no tab groups */ }
 
         // Move all window data from sessionstore.js to this._windows.
+        this._orderedWindows = [];
         for (let i = 0; i < data.windows.length; i++) {
           let SSID;
           if (i != windowIndex) {
             SSID = "window" + gUUIDGenerator.generateUUID().toString();
             this._windows[SSID] = data.windows[i];
           } else {
             SSID = window.__SSID;
             this._windows[SSID].extData = data.windows[i].extData;
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1677,31 +1677,40 @@ toolbarbutton.chevron > .toolbarbutton-i
   -moz-appearance: button;
   color: ButtonText;
   padding: 0 3px;
   margin-top: 10px;
 }
 
 /* Sync Panel */
 
-#sync-popup {
+.sync-panel {
   padding: 10px;
 }
 
-#sync-popup-icon {
+.sync-panel-icon {
   width: 32px;
-  background: url("chrome://browser/content/abouthome/sync.png") center center no-repeat;
+  background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
 }
 
-#sync-popup-inner {
+.sync-panel-inner {
   width: 0;
   padding-left: 10px;
 }
 
-#sync-popup-desc-prefs {
+.sync-panel-button-box {
+  margin-top: 1em;
+}
+
+#sync-error-panel-title {
+  font-weight: bold;
+}
+
+#sync-start-panel-subtitle,
+#sync-error-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3591,40 +3591,64 @@ toolbarbutton.chevron > .toolbarbutton-m
 .ctrlTab-preview:not(#ctrlTab-showAll):focus > * > .ctrlTab-preview-inner {
   margin: -10px -10px 0;
 }
 
 #ctrlTab-showAll {
   margin-top: .5em;
 }
 
-/* Sync Panel */
-
-#sync-popup {
+/* Sync Panels */
+
+.sync-panel {
   padding: 10px;
 }
 
-#sync-popup-icon {
+.sync-panel-icon {
   width: 32px;
-  background: url("chrome://browser/content/abouthome/sync.png") center center no-repeat;
+  background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
 }
 
 @media (min-resolution: 2dppx) {
-  #sync-popup-icon {
-    background: url("chrome://browser/content/abouthome/sync@2x.png") center center no-repeat;
+  .sync-panel-icon {
+    background: url("chrome://browser/content/abouthome/sync@2x.png") top left no-repeat;
     background-size: 32px 32px;
   }
 }
 
-#sync-popup-inner {
+.sync-panel-inner {
   width: 0;
   padding-left: 10px;
 }
 
-#sync-popup-desc-prefs {
+.sync-panel-button-box {
+  margin-top: 1em;
+}
+
+.sync-panel-button {
+  @hudButton@
+  margin: 0;
+  min-width: 72px;
+  min-height: 22px;
+}
+
+.sync-panel-button:hover:active {
+  @hudButtonPressed@
+}
+
+.sync-panel-button:-moz-focusring {
+  @hudButtonFocused@
+}
+
+#sync-error-panel-title {
+  font-weight: bold;
+}
+
+#sync-start-panel-subtitle,
+#sync-error-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %filter substitution
 
 %define menuPanelWidth 22.35em
 %define exitSubviewGutterWidth 38px
-%define buttonStateHover :not(:-moz-any([disabled],[checked="true"],[open],:active)):hover
-%define buttonStateActive :not([disabled]):-moz-any([open],[checked="true"],:hover:active)
+%define buttonStateHover :not(:-moz-any([disabled],[open],[checked="true"],:active)):-moz-any(:hover,[_moz-menuactive])
+%define buttonStateActive :not([disabled]):-moz-any([open],[checked="true"],:hover:active,[_moz-menuactive]:active)
 
 %include ../browser.inc
 
 .panel-subviews {
   padding: 4px;
   background-color: hsla(0,0%,100%,.97);
   background-clip: padding-box;
   border-right: 1px solid hsla(210,4%,10%,.2);
@@ -211,83 +211,102 @@ toolbarpaletteitem[place="palette"] > to
 #zoom-in-button > .toolbarbutton-text,
 #zoom-out-button > .toolbarbutton-text,
 #zoom-reset-button > .toolbarbutton-icon {
   display: none;
 }
 
 #PanelUI-footer {
   display: flex;
+  flex-direction: column;
   background-color: rgba(0, 0, 0, 0.05);
   box-shadow: 0 -1px 0 rgba(0,0,0,.15);
   padding: 0;
   margin: 0;
-  min-height: 4em;
+}
+
+#PanelUI-footer-inner {
+  display: flex;
+  box-shadow: 0 -1px 0 rgba(0,0,0,.15);
 }
 
 #PanelUI-footer > toolbarseparator {
   border: 0;
   border-left: 1px solid rgba(0,0,0,0.1);
   margin: 7px 0 7px;
 }
 
 #PanelUI-footer:hover > toolbarseparator {
   margin: 0;
 }
 
 #PanelUI-help,
+#PanelUI-fxa-status,
 #PanelUI-customize,
 #PanelUI-quit {
   margin: 0;
   padding: 10px 0;
+  min-height: 2em;
   -moz-appearance: none;
   box-shadow: none;
   background-image: none;
   border: 1px solid transparent;
   border-bottom-style: none;
   border-radius: 0;
   transition: background-color;
   -moz-box-orient: horizontal;
 }
 
+#PanelUI-fxa-status {
+  width: calc(@menuPanelWidth@ + 20px);
+  border-bottom-style: solid;
+}
+
+#PanelUI-fxa-status[signedin] {
+  font-weight: bold;
+}
+
 #PanelUI-help,
 #PanelUI-quit {
   min-width: 46px;
 }
 
+#PanelUI-fxa-status > .toolbarbutton-text,
 #PanelUI-customize > .toolbarbutton-text {
   text-align: start;
 }
 
 #PanelUI-help > .toolbarbutton-text,
 #PanelUI-quit > .toolbarbutton-text {
   display: none;
 }
 
 #PanelUI-help > .toolbarbutton-icon,
 #PanelUI-quit > .toolbarbutton-icon {
   -moz-margin-end: 0;
 }
 
+#PanelUI-fxa-status,
 #PanelUI-customize {
   flex: 1;
   -moz-padding-start: 15px;
   -moz-border-start-style: none;
   list-style-image: url(chrome://browser/skin/menuPanel-customize.png);
 }
 
 #PanelUI-help {
   list-style-image: url(chrome://browser/skin/menuPanel-help.png);
 }
 
 #PanelUI-quit {
   -moz-border-end-style: none;
   list-style-image: url(chrome://browser/skin/menuPanel-exit.png);
 }
 
+#PanelUI-fxa-status,
 #PanelUI-customize,
 #PanelUI-help,
 #PanelUI-quit {
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 #PanelUI-customize:hover,
 #PanelUI-help:not([disabled]):hover,
@@ -309,16 +328,22 @@ toolbarpaletteitem[place="palette"] > to
 #PanelUI-help:not([disabled]):hover,
 #PanelUI-customize:hover,
 #PanelUI-quit:not([disabled]):hover {
   outline: 1px solid rgba(0,0,0,0.1);
   background-color: rgba(0,0,0,0.1);
   box-shadow: none;
 }
 
+#PanelUI-fxa-status:not([disabled]):hover {
+  background-color: rgba(0,0,0,0.1);
+  border-bottom-color: rgba(0,0,0,0.1);
+  box-shadow: 0 -1px 0 rgba(0,0,0,0.2);
+}
+
 #PanelUI-quit:not([disabled]):hover {
   background-color: #d94141;
   outline-color: #c23a3a;
 }
 
 #PanelUI-quit:not([disabled]):hover:active {
   background-color: #ad3434;
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2136,31 +2136,40 @@ toolbarbutton.bookmark-item[dragover="tr
 }
 
 #ctrlTab-showAll {
   margin-top: .5em;
 }
 
 /* Sync Panel */
 
-#sync-popup {
+.sync-panel {
   padding: 10px;
 }
 
-#sync-popup-icon {
+.sync-panel-icon {
   width: 32px;
-  background: url("chrome://browser/content/abouthome/sync.png") center center no-repeat;
-}
-
-#sync-popup-inner {
+  background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
+}
+
+.sync-panel-inner {
   width: 0;
   padding-left: 10px;
 }
 
-#sync-popup-desc-prefs {
+.sync-panel-button-box {
+  margin-top: 1em;
+}
+
+#sync-error-panel-title {
+  font-weight: bold;
+}
+
+#sync-start-panel-subtitle,
+#sync-error-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -67,20 +67,27 @@ DEBUGGER_INFO = {
 
   "lldb": {
     "interactive": True,
     "args": "--",
     "requiresEscapedArgs": True
   },
 
   # valgrind doesn't explain much about leaks unless you set the
-  # '--leak-check=full' flag.
+  # '--leak-check=full' flag. But there are a lot of objects that are
+  # semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
+  # uninteresting output from those objects. We set '--smc-check==all-non-file'
+  # and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
+  # deals properly with JIT'd JavaScript code.  
   "valgrind": {
     "interactive": False,
-    "args": "--leak-check=full"
+    "args": " ".join(["--leak-check=full",
+                      "--show-possibly-lost=no",
+                      "--smc-check=all-non-file,"
+                      "--vex-iropt-register-updates=allregs-at-mem-access"])
   }
 }
 
 class ZipFileReader(object):
   """
   Class to read zip files in Python 2.5 and later. Limited to only what we
   actually use.
   """
--- a/content/media/test/test_texttracklist.html
+++ b/content/media/test/test_texttracklist.html
@@ -7,16 +7,19 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Media test: TextTrackList change event</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   SimpleTest.waitForExplicitFinish();
   video = document.createElement("video");
+
+  isnot(video.textTracks, null, "Video should have a list of TextTracks.");
+
   video.addTextTrack("subtitles", "", "");
 
   track = video.textTracks[0];
   video.textTracks.addEventListener("change", changed);
 
   is(track.mode, "hidden", "New TextTrack's mode should be hidden.");
   track.mode = "showing";
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8220,20 +8220,30 @@ nsGlobalWindow::EnterModalState()
 
       if (activeShell) {
         nsRefPtr<nsFrameSelection> frameSelection = activeShell->FrameSelection();
         frameSelection->SetMouseDownState(false);
       }
     }
   }
 
+  // Clear the capturing content if it is under topDoc.
+  // Usually the activeESM check above does that, but there are cases when
+  // we don't have activeESM, or it is for different document.
+  nsIDocument* topDoc = topWin->GetExtantDoc();
+  nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
+  if (capturingContent && topDoc &&
+      nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) {
+    nsIPresShell::SetCapturingContent(nullptr, 0);
+  }
+
   if (topWin->mModalStateDepth == 0) {
     NS_ASSERTION(!mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
 
-    mSuspendedDoc = topWin->GetExtantDoc();
+    mSuspendedDoc = topDoc;
     if (mSuspendedDoc) {
       mSuspendedDoc->SuppressEventHandling();
     }
   }
   topWin->mModalStateDepth++;
 }
 
 // static
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -677,22 +677,22 @@ TabParent::MapEventCoordinatesForChildPr
   }
 }
 
 bool TabParent::SendRealMouseEvent(WidgetMouseEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  WidgetMouseEvent e(event);
-  MaybeForwardEventToRenderFrame(event, nullptr, &e);
-  if (!MapEventCoordinatesForChildProcess(&e)) {
+  WidgetMouseEvent outEvent(event);
+  MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
+  if (!MapEventCoordinatesForChildProcess(&outEvent)) {
     return false;
   }
-  return PBrowserParent::SendRealMouseEvent(e);
+  return PBrowserParent::SendRealMouseEvent(outEvent);
 }
 
 CSSIntPoint TabParent::AdjustTapToChildWidget(const CSSIntPoint& aPoint)
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
 
   if (!content || !content->OwnerDoc()) {
     return aPoint;
@@ -745,35 +745,35 @@ bool TabParent::SendHandleDoubleTap(cons
   return PBrowserParent::SendHandleDoubleTap(AdjustTapToChildWidget(aPoint));
 }
 
 bool TabParent::SendMouseWheelEvent(WidgetWheelEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  WidgetWheelEvent e(event);
-  MaybeForwardEventToRenderFrame(event, nullptr, &e);
-  if (!MapEventCoordinatesForChildProcess(&e)) {
+  WidgetWheelEvent outEvent(event);
+  MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
+  if (!MapEventCoordinatesForChildProcess(&outEvent)) {
     return false;
   }
-  return PBrowserParent::SendMouseWheelEvent(event);
+  return PBrowserParent::SendMouseWheelEvent(outEvent);
 }
 
 bool TabParent::SendRealKeyEvent(WidgetKeyboardEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  WidgetKeyboardEvent e(event);
-  MaybeForwardEventToRenderFrame(event, nullptr, &e);
-  if (!MapEventCoordinatesForChildProcess(&e)) {
+  WidgetKeyboardEvent outEvent(event);
+  MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
+  if (!MapEventCoordinatesForChildProcess(&outEvent)) {
     return false;
   }
-  return PBrowserParent::SendRealKeyEvent(e);
+  return PBrowserParent::SendRealKeyEvent(outEvent);
 }
 
 bool TabParent::SendRealTouchEvent(WidgetTouchEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
   if (event.message == NS_TOUCH_START) {
@@ -792,40 +792,46 @@ bool TabParent::SendRealTouchEvent(Widge
     MOZ_ASSERT((!sEventCapturer && mEventCaptureDepth == 0) ||
                (sEventCapturer == this && mEventCaptureDepth > 0));
     // We want to capture all remaining touch events in this series
     // for fast-path dispatch.
     sEventCapturer = this;
     ++mEventCaptureDepth;
   }
 
-  WidgetTouchEvent e(event);
-  // PresShell::HandleEventInternal adds touches on touch end/cancel.
-  // This confuses remote content into thinking that the added touches
-  // are part of the touchend/cancel, when actually they're not.
+  // PresShell::HandleEventInternal adds touches on touch end/cancel.  This
+  // confuses remote content and the panning and zooming logic into thinking
+  // that the added touches are part of the touchend/cancel, when actually
+  // they're not.
   if (event.message == NS_TOUCH_END || event.message == NS_TOUCH_CANCEL) {
-    for (int i = e.touches.Length() - 1; i >= 0; i--) {
-      if (!e.touches[i]->mChanged) {
-        e.touches.RemoveElementAt(i);
+    for (int i = event.touches.Length() - 1; i >= 0; i--) {
+      if (!event.touches[i]->mChanged) {
+        event.touches.RemoveElementAt(i);
       }
     }
   }
 
+  // Create an out event for remote content that is identical to the event that
+  // we send to the render frame. The out event will be transformed in such a
+  // way that its async transform in the compositor is unapplied. The event that
+  // it is created from does not get mutated.
+  WidgetTouchEvent outEvent(event);
+
   ScrollableLayerGuid guid;
-  MaybeForwardEventToRenderFrame(event, &guid, &e);
+  MaybeForwardEventToRenderFrame(event, &guid, &outEvent);
 
   if (mIsDestroyed) {
     return false;
   }
 
-  MapEventCoordinatesForChildProcess(mChildProcessOffsetAtTouchStart, &e);
+  MapEventCoordinatesForChildProcess(mChildProcessOffsetAtTouchStart, &outEvent);
 
-  return (e.message == NS_TOUCH_MOVE) ?
-    PBrowserParent::SendRealTouchMoveEvent(e, guid) :
-    PBrowserParent::SendRealTouchEvent(e, guid);
+  return (outEvent.message == NS_TOUCH_MOVE) ?
+    PBrowserParent::SendRealTouchMoveEvent(outEvent, guid) :
+    PBrowserParent::SendRealTouchEvent(outEvent, guid);
 }
 
 /*static*/ TabParent*
 TabParent::GetEventCapturer()
 {
   return sEventCapturer;
 }
 
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -574,44 +574,35 @@ Promise::Reject(nsPIDOMWindow* aWindow, 
   // aWindow may be null.
   nsRefPtr<Promise> promise = new Promise(aWindow);
 
   promise->MaybeRejectInternal(aCx, aValue);
   return promise.forget();
 }
 
 already_AddRefed<Promise>
-Promise::Then(const Optional<nsRefPtr<AnyCallback>>& aResolveCallback,
-              const Optional<nsRefPtr<AnyCallback>>& aRejectCallback)
+Promise::Then(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback)
 {
   nsRefPtr<Promise> promise = new Promise(GetParentObject());
 
   nsRefPtr<PromiseCallback> resolveCb =
-    PromiseCallback::Factory(promise,
-                             aResolveCallback.WasPassed()
-                               ? aResolveCallback.Value()
-                               : nullptr,
-                             PromiseCallback::Resolve);
+    PromiseCallback::Factory(promise, aResolveCallback, PromiseCallback::Resolve);
 
   nsRefPtr<PromiseCallback> rejectCb =
-    PromiseCallback::Factory(promise,
-                             aRejectCallback.WasPassed()
-                               ? aRejectCallback.Value()
-                               : nullptr,
-                             PromiseCallback::Reject);
+    PromiseCallback::Factory(promise, aRejectCallback, PromiseCallback::Reject);
 
   AppendCallbacks(resolveCb, rejectCb);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
-Promise::Catch(const Optional<nsRefPtr<AnyCallback>>& aRejectCallback)
+Promise::Catch(AnyCallback* aRejectCallback)
 {
-  Optional<nsRefPtr<AnyCallback>> resolveCb;
+  nsRefPtr<AnyCallback> resolveCb;
   return Then(resolveCb, aRejectCallback);
 }
 
 /**
  * The CountdownHolder class encapsulates Promise.all countdown functions and
  * the countdown holder parts of the Promises spec. It maintains the result
  * array and AllResolveHandlers use SetValue() to set the array indices.
  */
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -78,22 +78,20 @@ public:
   Reject(const GlobalObject& aGlobal, JSContext* aCx,
          const Optional<JS::Handle<JS::Value>>& aValue, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Reject(nsPIDOMWindow* aWindow, JSContext* aCx,
          JS::Handle<JS::Value> aValue, ErrorResult& aRv);
 
   already_AddRefed<Promise>
-  Then(const Optional<nsRefPtr<AnyCallback>>& aResolveCallback,
-       const Optional<nsRefPtr<AnyCallback>>& aRejectCallback);
-
+  Then(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback);
 
   already_AddRefed<Promise>
-  Catch(const Optional<nsRefPtr<AnyCallback>>& aRejectCallback);
+  Catch(AnyCallback* aRejectCallback);
 
   // FIXME(nsm): Bug 956197
   static already_AddRefed<Promise>
   All(const GlobalObject& aGlobal, JSContext* aCx,
       const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Cast(const GlobalObject& aGlobal, JSContext* aCx,
--- a/dom/promise/PromiseCallback.cpp
+++ b/dom/promise/PromiseCallback.cpp
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PromiseCallback.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 
+#include "js/OldDebugAPI.h"
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
@@ -172,31 +174,91 @@ WrapperPromiseCallback::Call(JS::Handle<
   Maybe<JSAutoCompartment> ac;
   EnterCompartment(ac, cx, aValue);
 
   ErrorResult rv;
 
   // If invoking callback threw an exception, run resolver's reject with the
   // thrown exception as argument and the synchronous flag set.
   JS::Rooted<JS::Value> value(cx,
-    mCallback->Call(mNextPromise->GetParentObject(), aValue, rv,
-                    CallbackObject::eRethrowExceptions));
+    mCallback->Call(aValue, rv, CallbackObject::eRethrowExceptions));
 
   rv.WouldReportJSException();
 
   if (rv.Failed() && rv.IsJSException()) {
     JS::Rooted<JS::Value> value(cx);
     rv.StealJSException(cx, &value);
 
     Maybe<JSAutoCompartment> ac2;
     EnterCompartment(ac2, cx, value);
     mNextPromise->RejectInternal(cx, value, Promise::SyncTask);
     return;
   }
 
+  // If the return value is the same as the promise itself, throw TypeError.
+  if (value.isObject()) {
+    JS::Rooted<JSObject*> valueObj(cx, &value.toObject());
+    Promise* returnedPromise;
+    nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise);
+
+    if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) {
+      const char* fileName = nullptr;
+      uint32_t lineNumber = 0;
+
+      // Try to get some information about the callback to report a sane error,
+      // but don't try too hard (only deals with scripted functions).
+      JS::Rooted<JSObject*> unwrapped(cx,
+        js::CheckedUnwrap(mCallback->Callback()));
+
+      if (unwrapped) {
+        JSAutoCompartment ac(cx, unwrapped);
+        if (JS_ObjectIsFunction(cx, unwrapped)) {
+          JS::Rooted<JS::Value> asValue(cx, JS::ObjectValue(*unwrapped));
+          JS::Rooted<JSFunction*> func(cx, JS_ValueToFunction(cx, asValue));
+
+          MOZ_ASSERT(func);
+          JSScript* script = JS_GetFunctionScript(cx, func);
+          if (script) {
+            fileName = JS_GetScriptFilename(cx, script);
+            lineNumber = JS_GetScriptBaseLineNumber(cx, script);
+          }
+        }
+      }
+
+      // We're back in aValue's compartment here.
+      JS::Rooted<JSString*> stack(cx, JS_GetEmptyString(JS_GetRuntime(cx)));
+      JS::Rooted<JSString*> fn(cx, JS_NewStringCopyZ(cx, fileName));
+      if (!fn) {
+        // Out of memory. Promise will stay unresolved.
+        JS_ClearPendingException(cx);
+        return;
+      }
+
+      JS::Rooted<JSString*> message(cx,
+        JS_NewStringCopyZ(cx,
+          "then() cannot return same Promise that it resolves."));
+      if (!message) {
+        // Out of memory. Promise will stay unresolved.
+        JS_ClearPendingException(cx);
+        return;
+      }
+
+      JS::Rooted<JS::Value> typeError(cx);
+      if (!JS::CreateTypeError(cx, stack, fn, lineNumber, 0,
+                               nullptr, message, &typeError)) {
+        // Out of memory. Promise will stay unresolved.
+        JS_ClearPendingException(cx);
+        return;
+      }
+
+      mNextPromise->RejectInternal(cx, typeError, Promise::SyncTask);
+      return;
+    }
+  }
+
   // Otherwise, run resolver's resolve with value and the synchronous flag
   // set.
   Maybe<JSAutoCompartment> ac2;
   EnterCompartment(ac2, cx, value);
   mNextPromise->ResolveInternal(cx, value, Promise::SyncTask);
 }
 
 // SimpleWrapperPromiseCallback
--- a/dom/promise/tests/test_promise.html
+++ b/dom/promise/tests/test_promise.html
@@ -559,16 +559,25 @@ function promiseWithThenReplaced() {
     ok(false, "promiseWithThenReplaced: Should've rejected");
     runTest();
   }, function(e) {
     ok(e instanceof TypeError, "promiseWithThenReplaced");
     runTest();
   });
 }
 
+function promiseStrictHandlers() {
+  var promise = Promise.resolve(5);
+  promise.then(function() {
+    "use strict";
+    ok(this === undefined, "Strict mode callback should have this === undefined.");
+    runTest();
+  });
+}
+
 var tests = [ promiseResolve, promiseReject,
               promiseException, promiseGC, promiseAsync,
               promiseDoubleThen, promiseThenException,
               promiseThenCatchThen, promiseRejectThenCatchThen,
               promiseRejectThenCatchThen2,
               promiseRejectThenCatchExceptionThen,
               promiseThenCatchOrderingResolve,
               promiseThenCatchOrderingReject,
@@ -584,16 +593,17 @@ var tests = [ promiseResolve, promiseRej
               promiseCatchNoArg,
               promiseRejectNoHandler,
               promiseSimpleThenableResolve,
               promiseSimpleThenableReject,
               promiseThenableThrowsBeforeCallback,
               promiseThenableThrowsAfterCallback,
               promiseThenableRejectThenResolve,
               promiseWithThenReplaced,
+              promiseStrictHandlers,
             ];
 
 function runTest() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/dom/promise/tests/test_promise_utils.html
+++ b/dom/promise/tests/test_promise_utils.html
@@ -169,17 +169,17 @@ function promiseCastArray() {
     is(v[0], 1, "Resolved value should match original");
     is(v[1], 2, "Resolved value should match original");
     is(v[2], 3, "Resolved value should match original");
     runTest();
   });
 }
 
 function promiseCastThenable() {
-  var p = Promise.cast({ then: function(resolve) { resolve(2); } });
+  var p = Promise.cast({ then: function(onFulfill, onReject) { onFulfill(2); } });
   ok(p instanceof Promise, "Should cast to a Promise.");
   p.then(function(v) {
     is(v, 2, "Should resolve to 2.");
     runTest();
   }, function(e) {
     ok(false, "promiseCastThenable should've resolved");
     runTest();
   });
--- a/dom/webidl/Promise.webidl
+++ b/dom/webidl/Promise.webidl
@@ -6,38 +6,42 @@
  * The origin of this IDL file is
  * http://dom.spec.whatwg.org/#promises
  */
 
 // TODO We use object instead Function.  There is an open issue on WebIDL to
 // have different types for "platform-provided function" and "user-provided
 // function"; for now, we just use "object".
 callback PromiseInit = void (object resolve, object reject);
+
+[TreatNonCallableAsNull]
 callback AnyCallback = any (any value);
 
 [Func="mozilla::dom::Promise::EnabledForScope", Constructor(PromiseInit init)]
 interface Promise {
   // TODO bug 875289 - static Promise fulfill(any value);
 
   // Disable the static methods when the interface object is supposed to be
   // disabled, just in case some code decides to walk over to .constructor from
   // the proto of a promise object or someone screws up and manages to create a
   // Promise object in this scope without having resolved the interface object
   // first.
   [NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"]
   static Promise resolve(optional any value);
   [NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"]
   static Promise reject(optional any value);
 
+  // The [TreatNonCallableAsNull] annotation is required since then() should do
+  // nothing instead of throwing errors when non-callable arguments are passed.
   [NewObject]
-  Promise then(optional AnyCallback? fulfillCallback,
-               optional AnyCallback? rejectCallback);
+  Promise then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
+               [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
 
   [NewObject]
-  Promise catch(optional AnyCallback? rejectCallback);
+  Promise catch([TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
 
   [NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"]
   static Promise all(sequence<any> iterable);
 
   [NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"]
   static Promise cast(optional any value);
 
   [NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"]
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -1634,16 +1634,17 @@ XMLHttpRequest::MaybePin(ErrorResult& aR
 
   if (mRooted) {
     return;
   }
 
   JSContext* cx = GetCurrentThreadJSContext();
 
   if (!mWorkerPrivate->AddFeature(cx, this)) {
+    aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   NS_ADDREF_THIS();
 
   mRooted = true;
 }
 
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -848,27 +848,18 @@ nsEventStatus AsyncPanZoomController::On
     mLastZoomFocus = focusPoint;
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale-end in state %d\n", this, mState);
-  // When a pinch ends, it might either turn into a pan (if only one finger
-  // was lifted) or not (if both fingers were lifted). GestureEventListener
-  // sets mCurrentSpan to a negative value in the latter case, and sets
-  // mFocusPoint to the remaining touch point in the former case.
-  if (aEvent.mCurrentSpan >= 0) {
-    SetState(PANNING);
-    mX.StartTouch(aEvent.mFocusPoint.x);
-    mY.StartTouch(aEvent.mFocusPoint.y);
-  } else {
-    SetState(NOTHING);
-  }
+
+  SetState(NOTHING);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
     ScheduleComposite();
     RequestContentRepaint();
     UpdateSharedCompositorFrameMetrics();
   }
 
--- a/gfx/layers/ipc/GestureEventListener.cpp
+++ b/gfx/layers/ipc/GestureEventListener.cpp
@@ -262,35 +262,34 @@ nsEventStatus GestureEventListener::Hand
     }
 
     mPreviousSpan = currentSpan;
 
     rv = nsEventStatus_eConsumeNoDefault;
   } else if (mState == GESTURE_PINCH) {
     PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                  aEvent.mTime,
-                                 ScreenPoint(),  // may change below
-                                 1.0f,           // may change below
-                                 1.0f,           // may change below
+                                 ScreenPoint(),
+                                 1.0f,
+                                 1.0f,
                                  aEvent.modifiers);
-
-    if (mTouches.Length() > 0) {
-      // Pinch is changing to pan. APZC will start a pan at mFocusPoint
-      // (which isn't really a focus point in this case...).
-      pinchEvent.mFocusPoint = mTouches[0].mScreenPoint;
-    } else {
-      // Pinch is ending, no pan to follow. APZC will check for the spans
-      // being negative.
-      pinchEvent.mCurrentSpan = pinchEvent.mPreviousSpan = -1.0f;
-    }
-
     mAsyncPanZoomController->HandleInputEvent(pinchEvent);
 
     mState = GESTURE_NONE;
 
+    // If the user left a finger on the screen, spoof a touch start event and
+    // send it to APZC so that they can continue panning from that point.
+    if (mTouches.Length() == 1) {
+      MultiTouchInput touchEvent(MultiTouchInput::MULTITOUCH_START,
+                                 aEvent.mTime,
+                                 aEvent.modifiers);
+      touchEvent.mTouches.AppendElement(mTouches[0]);
+      mAsyncPanZoomController->HandleInputEvent(touchEvent);
+    }
+
     rv = nsEventStatus_eConsumeNoDefault;
   } else if (mState == GESTURE_WAITING_PINCH) {
     mState = GESTURE_NONE;
   }
 
   if (aClearTouches) {
     mTouches.Clear();
   }
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -2164,17 +2164,17 @@ BufferOffset
 Assembler::as_vcvtFixed(VFPRegister vd, bool isSigned, uint32_t fixedPoint, bool toFixed, Condition c)
 {
     JS_ASSERT(vd.isFloat());
     uint32_t sx = 0x1;
     vfp_size sf = vd.isDouble() ? isDouble : isSingle;
     int32_t imm5 = fixedPoint;
     imm5 = (sx ? 32 : 16) - imm5;
     JS_ASSERT(imm5 >= 0);
-    imm5 = imm5 >> 1 | (imm5 & 1) << 6;
+    imm5 = imm5 >> 1 | (imm5 & 1) << 5;
     return writeVFPInst(sf, 0x02BA0040 | VD(vd) | toFixed << 18 | sx << 7 |
                         (!isSigned) << 16 | imm5 | c);
 }
 
 // xfer between VFP and memory
 BufferOffset
 Assembler::as_vdtr(LoadStore ls, VFPRegister vd, VFPAddr addr,
                    Condition c /* vfp doesn't have a wb option*/,
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2178,18 +2178,27 @@ public:
                                   nsDisplayList* aList) {
     SetCount(++mCount);
     return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, mScrolledFrame, mScrolledFrame, mScrollFrame);
   }
 
   virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
                                   nsDisplayItem* aItem) {
 
-    SetCount(++mCount);
-    return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->Frame(), mScrolledFrame, mScrollFrame);
+    // If the display item is for a frame that is absolutely positioned, it
+    // should only scroll with the scrolled content if its frame its contained
+    // within the scrolled content's frame.
+    bool shouldWrap = !aItem->Frame()->IsAbsolutelyPositioned() ||
+                      nsLayoutUtils::IsProperAncestorFrame(mScrolledFrame, aItem->Frame(), nullptr);
+    if (shouldWrap) {
+      SetCount(++mCount);
+      return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->Frame(), mScrolledFrame, mScrollFrame);
+    } else {
+      return aItem;
+    }
   }
 
 protected:
   void SetCount(intptr_t aCount) {
     mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast<void*>(aCount));
   }
 
   intptr_t mCount;
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -265,16 +265,17 @@ public:
            (mHasHorizontalScrollbar ? nsIScrollableFrame::HORIZONTAL : 0);
   }
   nsMargin GetActualScrollbarSizes() const;
   nsMargin GetDesiredScrollbarSizes(nsBoxLayoutState* aState);
   nscoord GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState);
   bool IsLTR() const;
   bool IsScrollbarOnRight() const;
   bool IsScrollingActive() const { return mScrollingActive || mShouldBuildScrollableLayer; }
+  bool IsProcessingAsyncScroll() const { return mAsyncScroll != nullptr; }
   void ResetScrollPositionForLayerPixelAlignment()
   {
     mScrollPosForLayerPixelAlignment = GetScrollPosition();
   }
 
   bool UpdateOverflow();
 
   void UpdateSticky();
@@ -631,16 +632,19 @@ public:
   }
   NS_IMETHOD PostScrolledAreaEventForCurrentArea() MOZ_OVERRIDE {
     mHelper.PostScrolledAreaEvent();
     return NS_OK;
   }
   virtual bool IsScrollingActive() MOZ_OVERRIDE {
     return mHelper.IsScrollingActive();
   }
+  virtual bool IsProcessingAsyncScroll() MOZ_OVERRIDE {
+    return mHelper.IsProcessingAsyncScroll();
+  }
   virtual void ResetScrollPositionForLayerPixelAlignment() MOZ_OVERRIDE {
     mHelper.ResetScrollPositionForLayerPixelAlignment();
   }
   virtual bool DidHistoryRestore() MOZ_OVERRIDE {
     return mHelper.mDidHistoryRestore;
   }
   virtual void ClearDidHistoryRestore() MOZ_OVERRIDE {
     mHelper.mDidHistoryRestore = false;
@@ -929,16 +933,19 @@ public:
   }
   NS_IMETHOD PostScrolledAreaEventForCurrentArea() MOZ_OVERRIDE {
     mHelper.PostScrolledAreaEvent();
     return NS_OK;
   }
   virtual bool IsScrollingActive() MOZ_OVERRIDE {
     return mHelper.IsScrollingActive();
   }
+  virtual bool IsProcessingAsyncScroll() MOZ_OVERRIDE {
+    return mHelper.IsProcessingAsyncScroll();
+  }
   virtual void ResetScrollPositionForLayerPixelAlignment() MOZ_OVERRIDE {
     mHelper.ResetScrollPositionForLayerPixelAlignment();
   }
   virtual bool DidHistoryRestore() MOZ_OVERRIDE {
     return mHelper.mDidHistoryRestore;
   }
   virtual void ClearDidHistoryRestore() MOZ_OVERRIDE {
     mHelper.mDidHistoryRestore = false;
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -246,16 +246,21 @@ public:
 
   /**
    * Returns true if this scrollframe is being "actively scrolled".
    * This basically means that we should allocate resources in the
    * expectation that scrolling is going to happen.
    */
   virtual bool IsScrollingActive() = 0;
   /**
+   * Returns true if the scrollframe is currently processing an async
+   * or smooth scroll.
+   */
+  virtual bool IsProcessingAsyncScroll() = 0;
+  /**
    * Call this when the layer(s) induced by active scrolling are being
    * completely redrawn.
    */
   virtual void ResetScrollPositionForLayerPixelAlignment() = 0;
   /**
    * Was the current presentation state for this frame restored from history?
    */
   virtual bool DidHistoryRestore() = 0;
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -296,17 +296,17 @@
                   android:protectionLevel="signature"/>
 
         <provider android:name="org.mozilla.gecko.db.TabsProvider"
                   android:label="@string/sync_configure_engines_title_tabs"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
         <provider android:name="org.mozilla.gecko.db.HomeProvider"
-                  android:authorities="@ANDROID_PACKAGE_NAME@.db.homelists"
+                  android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
         <service
             android:exported="false"
             android:name="org.mozilla.gecko.updater.UpdateService"
             android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
         </service>
 
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1357,25 +1357,25 @@ abstract public class BrowserApp extends
      * @return true if we successfully switched to a tab, false otherwise.
      */
     private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
         if (!flags.contains(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)) {
             return false;
         }
 
         final Tabs tabs = Tabs.getInstance();
-        final int tabId = tabs.getTabIdForUrl(url, tabs.getSelectedTab().isPrivate());
-        if (tabId < 0) {
+        final Tab tab = tabs.getFirstTabForUrl(url, tabs.getSelectedTab().isPrivate());
+        if (tab == null) {
             return false;
         }
 
         // Set the target tab to null so it does not get selected (on editing
         // mode exit) in lieu of the tab we are about to select.
         mTargetTabForEditingMode = null;
-        Tabs.getInstance().selectTab(tabId);
+        tabs.selectTab(tab.getId());
 
         mBrowserToolbar.cancelEdit();
 
         return true;
     }
 
     private void openUrlAndStopEditing(String url) {
         openUrlAndStopEditing(url, null, false);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -388,17 +388,17 @@ public class GeckoAppShell
 
     // Tell the Gecko event loop that an event is available.
     public static native void notifyGeckoOfEvent(GeckoEvent event);
 
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
 
-    @WrapElementForJNI(generateStatic = true, noThrow = true)
+    @WrapElementForJNI(allowMultithread = true, generateStatic = true, noThrow = true)
     public static void handleUncaughtException(Thread thread, Throwable e) {
         if (thread == null) {
             thread = Thread.currentThread();
         }
         // If the uncaught exception was rethrown, walk the exception `cause` chain to find
         // the original exception so Socorro can correctly collate related crash reports.
         Throwable cause;
         while ((cause = e.getCause()) != null) {
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -158,17 +158,17 @@ public class Tabs implements GeckoEventL
         for (Tab tab : mOrder) {
             if (tab.isPrivate() == getPrivate) {
                 count++;
             }
         }
         return count;
     }
 
-    public synchronized int isOpen(String url) {
+    public int isOpen(String url) {
         for (Tab tab : mOrder) {
             if (tab.getURL().equals(url)) {
                 return tab.getId();
             }
         }
         return -1;
     }
 
@@ -640,42 +640,54 @@ public class Tabs implements GeckoEventL
 
     private void registerEventListener(String event) {
         GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
     }
 
     /**
      * Looks for an open tab with the given URL.
      * @param url       the URL of the tab we're looking for
+     *
+     * @return first Tab with the given URL, or null if there is no such tab.
+     */
+    public Tab getFirstTabForUrl(String url) {
+        return getFirstTabForUrlHelper(url, null);
+    }
+
+    /**
+     * Looks for an open tab with the given URL and private state.
+     * @param url       the URL of the tab we're looking for
      * @param isPrivate if true, only look for tabs that are private. if false,
      *                  only look for tabs that are non-private.
      *
-     * @return id of an open tab with the given URL; -1 if the tab doesn't exist.
+     * @return first Tab with the given URL, or null if there is no such tab.
      */
-    public int getTabIdForUrl(String url, boolean isPrivate) {
+    public Tab getFirstTabForUrl(String url, boolean isPrivate) {
+        return getFirstTabForUrlHelper(url, isPrivate);
+    }
+
+    private Tab getFirstTabForUrlHelper(String url, Boolean isPrivate) {
+        if (url == null) {
+            return null;
+        }
+
         for (Tab tab : mOrder) {
+            if (isPrivate != null && isPrivate != tab.isPrivate()) {
+                continue;
+            }
             String tabUrl = tab.getURL();
             if (AboutPages.isAboutReader(tabUrl)) {
                 tabUrl = ReaderModeUtils.getUrlFromAboutReader(tabUrl);
             }
-            if (TextUtils.equals(tabUrl, url) && isPrivate == tab.isPrivate()) {
-                return tab.getId();
+            if (url.equals(tabUrl)) {
+                return tab;
             }
         }
 
-        return -1;
-    }
-
-    public int getTabIdForUrl(String url) {
-        return getTabIdForUrl(url, Tabs.getInstance().getSelectedTab().isPrivate());
-    }
-
-    public synchronized Tab getTabForUrl(String url) {
-        int tabId = getTabIdForUrl(url);
-        return getTab(tabId);
+        return null;
     }
 
     /**
      * Loads a tab with the given URL in the currently selected tab.
      *
      * @param url URL of page to load, or search term used if searchEngine is given
      */
     @RobocopTarget
--- a/mobile/android/base/favicons/Favicons.java
+++ b/mobile/android/base/favicons/Favicons.java
@@ -234,17 +234,17 @@ public class Favicons {
      * @param pageURL The URL of a webpage with a Favicon.
      * @return The URL of the Favicon used by that webpage, according to either the History database
      *         or a somewhat educated guess.
      */
     public static String getFaviconURLForPageURL(String pageURL) {
         // Attempt to determine the Favicon URL from the Tabs datastructure. Can dodge having to use
         // the database sometimes by doing this.
         String targetURL;
-        Tab theTab = Tabs.getInstance().getTabForUrl(pageURL);
+        Tab theTab = Tabs.getInstance().getFirstTabForUrl(pageURL);
         if (theTab != null) {
             targetURL = theTab.getFaviconURL();
             if (targetURL != null) {
                 return targetURL;
             }
         }
 
         targetURL = BrowserDB.getFaviconUrlForHistoryUrl(sContext.getContentResolver(), pageURL);
--- a/mobile/android/base/home/DynamicPanel.java
+++ b/mobile/android/base/home/DynamicPanel.java
@@ -214,26 +214,21 @@ public class DynamicPanel extends HomeFr
         public String getDatasetId() {
             return mDatasetId;
         }
 
         @Override
         public Cursor loadCursor() {
             final ContentResolver cr = getContext().getContentResolver();
 
-            // XXX: Use the test URI for static fake data
-            final Uri fakeItemsUri = HomeItems.CONTENT_FAKE_URI.buildUpon().
-                appendQueryParameter(BrowserContract.PARAM_PROFILE, "default").build();
-
             final String selection = HomeItems.DATASET_ID + " = ?";
             final String[] selectionArgs = new String[] { mDatasetId };
 
-            Log.i(LOGTAG, "Loading fake data for list provider: " + mDatasetId);
-
-            return cr.query(fakeItemsUri, null, selection, selectionArgs, null);
+            // XXX: You can use CONTENT_FAKE_URI for development to pull items from fake_home_items.json.
+            return cr.query(HomeItems.CONTENT_URI, null, selection, selectionArgs, null);
         }
     }
 
     /**
      * LoaderCallbacks implementation that interacts with the LoaderManager.
      */
     private class PanelLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
--- a/mobile/android/base/home/TwoLinePageRow.java
+++ b/mobile/android/base/home/TwoLinePageRow.java
@@ -115,18 +115,18 @@ public class TwoLinePageRow extends TwoL
 
     /**
      * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
      * Only looks for tabs that are either private or non-private, depending on the current 
      * selected tab.
      */
     private void updateDisplayedUrl() {
         boolean isPrivate = Tabs.getInstance().getSelectedTab().isPrivate();
-        int tabId = Tabs.getInstance().getTabIdForUrl(mPageUrl, isPrivate);
-        if (!mShowIcons || tabId < 0) {
+        Tab tab = Tabs.getInstance().getFirstTabForUrl(mPageUrl, isPrivate);
+        if (!mShowIcons || tab == null) {
             setSecondaryText(mPageUrl);
             setSecondaryIcon(NO_ICON);
         } else {
             setSecondaryText(R.string.switch_to_tab);
             setSecondaryIcon(R.drawable.ic_url_bar_tab);
         }
     }
 
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -633,17 +633,17 @@ public class BrowserToolbar extends Geck
         // and the tabs button is translated offscreen. Don't trigger tabs counter
         // updates until the tabs button is back on screen.
         // See stopEditing()
         if (isEditing() && !HardwareUtils.isTablet()) {
             return;
         }
 
         // Set TabCounter based on visibility
-        if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0) {
+        if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0 && !isEditing()) {
             mTabsCounter.setCountWithAnimation(count);
         } else {
             mTabsCounter.setCount(count);
         }
 
         // Update A11y information
         mTabs.setContentDescription((count > 1) ?
                                     mActivity.getString(R.string.num_tabs, count) :
@@ -858,16 +858,20 @@ public class BrowserToolbar extends Geck
 
         // Disable toolbar elemens while in editing mode
         final boolean enabled = !isEditing();
 
         // This alpha value has to be in sync with the one used
         // in setButtonEnabled().
         final float alpha = (enabled ? 1.0f : 0.24f);
 
+        if (!enabled) {
+            mTabsCounter.onEnterEditingMode();
+        }
+
         mTabs.setEnabled(enabled);
         ViewHelper.setAlpha(mTabsCounter, alpha);
         mMenu.setEnabled(enabled);
         ViewHelper.setAlpha(mMenuIcon, alpha);
 
         final int actionItemsCount = mActionItemBar.getChildCount();
         for (int i = 0; i < actionItemsCount; i++) {
             mActionItemBar.getChildAt(i).setEnabled(enabled);
--- a/mobile/android/base/toolbar/TabCounter.java
+++ b/mobile/android/base/toolbar/TabCounter.java
@@ -97,16 +97,26 @@ public class TabCounter extends GeckoTex
         mCount = count;
     }
 
     public void setCount(int count) {
         setCurrentText(String.valueOf(count));
         mCount = count;
     }
 
+    // Alpha animations in editing mode cause action bar corruption on the
+    // Nexus 7 (bug 961749). As a workaround, skip these animations in editing
+    // mode.
+    void onEnterEditingMode() {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            getChildAt(i).clearAnimation();
+        }
+    }
+
     private AnimationSet createAnimation(float startAngle, float endAngle,
                                          FadeMode fadeMode,
                                          float zEnd, boolean reverse) {
         final Context context = getContext();
         AnimationSet set = new AnimationSet(context, null);
         set.addAnimation(new Rotate3DAnimation(startAngle, endAngle, CENTER_X, CENTER_Y, zEnd, reverse));
         set.addAnimation(fadeMode == FadeMode.FADE_IN ? new AlphaAnimation(0.0f, 1.0f) :
                                                         new AlphaAnimation(1.0f, 0.0f));
--- a/mobile/android/base/widget/ActivityChooserModel.java
+++ b/mobile/android/base/widget/ActivityChooserModel.java
@@ -26,21 +26,16 @@ import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.database.DataSetObservable;
 import android.os.AsyncTask;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Xml;
 
 /**
- * Mozilla: Extra imports.
- */
-import android.content.pm.ApplicationInfo;
-
-/**
  * Mozilla: Unused import.
  */
 //import com.android.internal.content.PackageMonitor;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
@@ -258,21 +253,16 @@ public class ActivityChooserModel extend
      * Monitor for added and removed packages.
      */
     /**
      * Mozilla: Not needed for the application.
      */
     //private final PackageMonitor mPackageMonitor = new DataModelPackageMonitor();
 
     /**
-     * Mozilla: Count to monitor added and removed packages.
-     */
-    private int mApplicationsCount;
-
-    /**
      * Context for accessing resources.
      */
     private final Context mContext;
 
     /**
      * The name of the history file that backs this model.
      */
     private final String mHistoryFileName;
@@ -737,25 +727,16 @@ public class ActivityChooserModel extend
 
     /**
      * Loads the activities for the current intent if needed which is
      * if they are not already loaded for the current intent.
      *
      * @return Whether loading was performed.
      */
     private boolean loadActivitiesIfNeeded() {
-        /**
-         * Mozilla: Hack to find change in the installed/uninstalled applications.
-         */
-        List<ApplicationInfo> applications = mContext.getPackageManager().getInstalledApplications(0);
-        if (applications != null && applications.size() != mApplicationsCount) {
-            mApplicationsCount = applications.size();
-            mReloadActivities = true;
-        }
-
         if (mReloadActivities && mIntent != null) {
             mReloadActivities = false;
             mActivities.clear();
             List<ResolveInfo> resolveInfos = mContext.getPackageManager()
                     .queryIntentActivities(mIntent, 0);
             final int resolveInfoCount = resolveInfos.size();
             for (int i = 0; i < resolveInfoCount; i++) {
                 ResolveInfo resolveInfo = resolveInfos.get(i);
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -556,16 +556,33 @@ var SelectionHandler = {
     return (this._activeType == this.TYPE_SELECTION);
   },
 
   selectAll: function sh_selectAll(aElement) {
     this.startSelection(aElement, { mode : this.SELECT_ALL });
   },
 
   /*
+   * Helper function for moving the selection inside an editable element.
+   *
+   * @param aAnchorX the stationary handle's x-coordinate in client coordinates
+   * @param aX the moved handle's x-coordinate in client coordinates
+   * @param aCaretPos the current position of the caret
+   */
+  _moveSelectionInEditable: function sh_moveSelectionInEditable(aAnchorX, aX, aCaretPos) {
+    let anchorOffset = aX < aAnchorX ? this._targetElement.selectionEnd
+                                     : this._targetElement.selectionStart;
+    let newOffset = aCaretPos.offset;
+    let [start, end] = anchorOffset <= newOffset ?
+                       [anchorOffset, newOffset] :
+                       [newOffset, anchorOffset];
+    this._targetElement.setSelectionRange(start, end);
+  },
+
+  /*
    * Moves the selection as the user drags a selection handle.
    *
    * @param aIsStartHandle whether the user is moving the start handle (as opposed to the end handle)
    * @param aX, aY selection point in client coordinates
    */
   _moveSelection: function sh_moveSelection(aIsStartHandle, aX, aY) {
     // XXX We should be smarter about the coordinates we pass to caretPositionFromPoint, especially
     // in editable targets. We should factor out the logic that's currently in _sendMouseEvents.
@@ -592,28 +609,28 @@ var SelectionHandler = {
     }
 
     let selection = this._getSelection();
 
     // The handles work the same on both LTR and RTL pages, but the anchor/focus nodes
     // are reversed, so we need to reverse the logic to extend the selection.
     if ((aIsStartHandle && !this._isRTL) || (!aIsStartHandle && this._isRTL)) {
       if (targetIsEditable) {
-        // XXX This will just collapse the selection if the start handle goes past the end handle.
-        this._targetElement.selectionStart = caretPos.offset;
+        let anchorX = this._isRTL ? this._cache.start.x : this._cache.end.x;
+        this._moveSelectionInEditable(anchorX, aX, caretPos);
       } else {
         let focusNode = selection.focusNode;
         let focusOffset = selection.focusOffset;
         selection.collapse(caretPos.offsetNode, caretPos.offset);
         selection.extend(focusNode, focusOffset);
       }
     } else {
       if (targetIsEditable) {
-        // XXX This will just collapse the selection if the end handle goes past the start handle.
-        this._targetElement.selectionEnd = caretPos.offset;
+        let anchorX = this._isRTL ? this._cache.end.x : this._cache.start.x;
+        this._moveSelectionInEditable(anchorX, aX, caretPos);
       } else {
         selection.extend(caretPos.offsetNode, caretPos.offset);
       }
     }
   },
 
   _sendMouseEvents: function sh_sendMouseEvents(aX, aY, useShift) {
     // If we're positioning a cursor in an input field, make sure the handle
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -23,17 +23,16 @@ function dump(a) {
 }
 
 // -----------------------------------------------------------------------
 // Session Store
 // -----------------------------------------------------------------------
 
 const STATE_STOPPED = 0;
 const STATE_RUNNING = 1;
-const STATE_QUITTING = -1;
 
 function SessionStore() { }
 
 SessionStore.prototype = {
   classID: Components.ID("{8c1f07d6-cba3-4226-a315-8bd43d67d032}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
                                          Ci.nsIDOMEventListener,
@@ -71,21 +70,17 @@ SessionStore.prototype = {
   observe: function ss_observe(aSubject, aTopic, aData) {
     let self = this;
     let observerService = Services.obs;
     switch (aTopic) {
       case "app-startup":
         observerService.addObserver(this, "final-ui-startup", true);
         observerService.addObserver(this, "domwindowopened", true);
         observerService.addObserver(this, "domwindowclosed", true);
-        observerService.addObserver(this, "browser-lastwindow-close-granted", true);
         observerService.addObserver(this, "browser:purge-session-history", true);
-        observerService.addObserver(this, "quit-application-requested", true);
-        observerService.addObserver(this, "quit-application-granted", true);
-        observerService.addObserver(this, "quit-application", true);
         observerService.addObserver(this, "Session:Restore", true);
         break;
       case "final-ui-startup":
         observerService.removeObserver(this, "final-ui-startup");
         this.init();
         break;
       case "domwindowopened": {
         let window = aSubject;
@@ -93,70 +88,19 @@ SessionStore.prototype = {
           self.onWindowOpen(window);
           window.removeEventListener("load", arguments.callee, false);
         }, false);
         break;
       }
       case "domwindowclosed": // catch closed windows
         this.onWindowClose(aSubject);
         break;
-      case "browser-lastwindow-close-granted":
-        // If a save has been queued, kill the timer and save state now
-        if (this._saveTimer) {
-          this._saveTimer.cancel();
-          this._saveTimer = null;
-          this.saveState();
-        }
-
-        // Freeze the data at what we've got (ignoring closing windows)
-        this._loadState = STATE_QUITTING;
-        break;
-      case "quit-application-requested":
-        // Get a current snapshot of all windows
-        this._forEachBrowserWindow(function(aWindow) {
-          self._collectWindowData(aWindow);
-        });
-        break;
-      case "quit-application-granted":
-        // Get a current snapshot of all windows
-        this._forEachBrowserWindow(function(aWindow) {
-          self._collectWindowData(aWindow);
-        });
-
-        // Freeze the data at what we've got (ignoring closing windows)
-        this._loadState = STATE_QUITTING;
-        break;
-      case "quit-application":
-        // Freeze the data at what we've got (ignoring closing windows)
-        this._loadState = STATE_QUITTING;
-
-        observerService.removeObserver(this, "domwindowopened");
-        observerService.removeObserver(this, "domwindowclosed");
-        observerService.removeObserver(this, "browser-lastwindow-close-granted");
-        observerService.removeObserver(this, "quit-application-requested");
-        observerService.removeObserver(this, "quit-application-granted");
-        observerService.removeObserver(this, "quit-application");
-        observerService.removeObserver(this, "Session:Restore");
-
-        // If a save has been queued, kill the timer and save state now
-        if (this._saveTimer) {
-          this._saveTimer.cancel();
-          this._saveTimer = null;
-          this.saveState();
-        }
-        break;
       case "browser:purge-session-history": // catch sanitization 
         this._clearDisk();
 
-        // If the browser is shutting down, simply return after clearing the
-        // session data on disk as this notification fires after the
-        // quit-application notification so the browser is about to exit.
-        if (this._loadState == STATE_QUITTING)
-          return;
-
         // Clear all data about closed tabs
         for (let [ssid, win] in Iterator(this._windows))
           win.closedTabs = [];
 
         if (this._loadState == STATE_RUNNING) {
           // Save the purged state immediately
           this.saveStateNow();
         }
@@ -232,17 +176,17 @@ SessionStore.prototype = {
   },
 
   onWindowOpen: function ss_onWindowOpen(aWindow) {
     // Return if window has already been initialized
     if (aWindow && aWindow.__SSID && this._windows[aWindow.__SSID])
       return;
 
     // Ignore non-browser windows and windows opened while shutting down
-    if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState == STATE_QUITTING)
+    if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser")
       return;
 
     // Assign it a unique identifier (timestamp) and create its data object
     aWindow.__SSID = "window" + Date.now();
     this._windows[aWindow.__SSID] = { tabs: [], selected: 0, closedTabs: [] };
 
     // Perform additional initialization when the first window is loading
     if (this._loadState == STATE_STOPPED) {
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -212,17 +212,17 @@ let HomePanels = {
     this._panels[panel.id] = panel;
   },
 
   remove: function(id) {
     delete this._panels[id];
 
     sendMessageToJava({
       type: "HomePanels:Remove",
-      id: panel.id
+      id: id
     });
   },
 
   // Helper function used to see if a value is in an object.
   _valueExists: function(obj, value) {
     for (let key in obj) {
       if (obj[key] == value) {
         return true;
--- a/mobile/android/modules/HomeProvider.jsm
+++ b/mobile/android/modules/HomeProvider.jsm
@@ -19,17 +19,17 @@ const SCHEMA_VERSION = 1;
 const DB_PATH = OS.Path.join(OS.Constants.Path.profileDir, "home.sqlite");
 
 /**
  * All SQL statements should be defined here.
  */
 const SQL = {
   createItemsTable:
     "CREATE TABLE items (" +
-      "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+      "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
       "dataset_id TEXT NOT NULL, " +
       "url TEXT," +
       "title TEXT," +
       "description TEXT," +
       "image_url TEXT," +
       "created INTEGER" +
     ")",
 
@@ -72,16 +72,17 @@ HomeStorage.prototype = {
   save: function(data) {
     return Task.spawn(function save_task() {
       let db = yield Sqlite.openConnection({ path: DB_PATH });
 
       try {
         // XXX: Factor this out to some migration path.
         if (!(yield db.tableExists("items"))) {
           yield db.execute(SQL.createItemsTable);
+          yield db.setSchemaVersion(SCHEMA_VERSION);
         }
 
         // Insert data into DB.
         for (let item of data) {
           // XXX: Directly pass item as params? More validation for item? Batch insert?
           let params = {
             dataset_id: this.datasetId,
             url: item.url,
--- a/netwerk/ipc/NeckoCommon.h
+++ b/netwerk/ipc/NeckoCommon.h
@@ -118,22 +118,16 @@ UsingNeckoIPCSecurity()
   return !NeckoCommonInternal::gSecurityDisabled;
 }
 
 inline bool
 MissingRequiredTabChild(mozilla::dom::TabChild* tabChild,
                         const char* context)
 {
   if (UsingNeckoIPCSecurity()) {
-    // Bug 833935: during navigation away from page some loads may lack
-    // TabParent: we don't want to kill browser for that.  Doesn't happen in
-    // test harness, so fail in debug mode so we can catch new code that fails
-    // to pass security info.
-    MOZ_ASSERT(tabChild);
-
     if (!tabChild) {
       printf_stderr("WARNING: child tried to open %s IPDL channel w/o "
                     "security info\n", context);
       return true;
     }
   }
   return false;
 }
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -131,16 +131,27 @@ class BaseBootstrapper(object):
         self.run_as_root(command)
 
     def apt_install(self, *packages):
         command = ['apt-get', 'install']
         command.extend(packages)
 
         self.run_as_root(command)
 
+    def apt_update(self):
+        command = ['apt-get', 'update']
+
+        self.run_as_root(command)
+
+    def apt_add_architecture(self, arch):
+        command = ['dpkg', '--add-architecture']
+        command.extemd(arch)
+
+        self.run_as_root(command)
+
     def check_output(self, *args, **kwargs):
         """Run subprocess.check_output even if Python doesn't provide it."""
         fn = getattr(subprocess, 'check_output', BaseBootstrapper._check_output)
 
         return fn(*args, **kwargs)
 
     @staticmethod
     def _check_output(*args, **kwargs):
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -28,17 +28,19 @@ Or, if you prefer Git:
 
     git clone https://git.mozilla.org/integration/gecko-dev.git
 '''
 
 
 class Bootstrapper(object):
     """Main class that performs system bootstrap."""
 
-    def bootstrap(self):
+    def __init__(self, finished=FINISHED):
+        self.instance = None
+        self.finished = finished
         cls = None
         args = {}
 
         if sys.platform.startswith('linux'):
             distro, version, dist_id = platform.linux_distribution()
 
             if distro == 'CentOS':
                 cls = CentOSBootstrapper
@@ -80,14 +82,17 @@ class Bootstrapper(object):
         elif sys.platform.startswith('freebsd'):
             cls = FreeBSDBootstrapper
             args['version'] = platform.release()
 
         if cls is None:
             raise NotImplementedError('Bootstrap support is not yet available '
                                       'for your OS.')
 
-        instance = cls(**args)
-        instance.install_system_packages()
-        instance.ensure_mercurial_modern()
-        instance.ensure_python_modern()
+        self.instance = cls(**args)
+
 
-        print(FINISHED)
+    def bootstrap(self):
+        self.instance.install_system_packages()
+        self.instance.ensure_mercurial_modern()
+        self.instance.ensure_python_modern()
+
+        print(self.finished)
--- a/python/mozboot/mozboot/centos.py
+++ b/python/mozboot/mozboot/centos.py
@@ -9,39 +9,45 @@ from mozboot.base import BaseBootstrappe
 
 class CentOSBootstrapper(BaseBootstrapper):
     def __init__(self, version, dist_id):
         BaseBootstrapper.__init__(self)
 
         self.version = version
         self.dist_id = dist_id
 
-    def install_system_packages(self):
-        kern = platform.uname()
-
-        self.yum_groupinstall(
+        self.group_packages = [
             'Development Tools',
             'Development Libraries',
-            'GNOME Software Development')
-        self.yum_install(
+            'GNOME Software Development',
+        ]
+
+        self.packages = [
             'alsa-lib-devel',
             'autoconf213',
             'curl-devel',
             'dbus-glib-devel',
             'glibc-static',
             'gstreamer-devel',
             'gstreamer-plugins-base-devel',
             'gtk2-devel',
             'libstdc++-static',
             'libXt-devel',
             'mercurial',
             'mesa-libGL-devel',
             'pulseaudio-libs-devel',
             'wireless-tools-devel',
-            'yasm')
+            'yasm',
+        ]
+
+    def install_system_packages(self):
+        kern = platform.uname()
+
+        self.yum_groupinstall(*self.group_packages)
+        self.yum_install(*self.packages)
 
         yasm = 'http://pkgs.repoforge.org/yasm/yasm-1.1.0-1.el6.rf.i686.rpm'
         if 'x86_64' in kern[2]:
             yasm = 'http://pkgs.repoforge.org/yasm/yasm-1.1.0-1.el6.rf.x86_64.rpm'
 
         self.run_as_root(['rpm', '-ivh', yasm])
 
     def upgrade_mercurial(self, current):
--- a/python/mozboot/mozboot/debian.py
+++ b/python/mozboot/mozboot/debian.py
@@ -37,14 +37,15 @@ class DebianBootstrapper(BaseBootstrappe
     DISTRO_PACKAGES = []
 
     def __init__(self, version, dist_id):
         BaseBootstrapper.__init__(self)
 
         self.version = version
         self.dist_id = dist_id
 
+        self.packages = self.COMMON_PACKAGES + self.DISTRO_PACKAGES
+
     def install_system_packages(self):
-        packages = self.COMMON_PACKAGES + self.DISTRO_PACKAGES
-        self.apt_install(*packages)
+        self.apt_install(*self.packages)
 
     def _update_package_manager(self):
         self.run_as_root(['apt-get', 'update'])
--- a/python/mozboot/mozboot/fedora.py
+++ b/python/mozboot/mozboot/fedora.py
@@ -8,31 +8,36 @@ from mozboot.base import BaseBootstrappe
 
 class FedoraBootstrapper(BaseBootstrapper):
     def __init__(self, version, dist_id):
         BaseBootstrapper.__init__(self)
 
         self.version = version
         self.dist_id = dist_id
 
-    def install_system_packages(self):
-        self.yum_groupinstall(
+        self.group_packages = [
             'Development Tools',
             'Development Libraries',
-            'GNOME Software Development')
+            'GNOME Software Development',
+        ]
 
-        self.yum_install(
+        self.packages = [
             'alsa-lib-devel',
             'autoconf213',
             'gcc-c++',
             'glibc-static',
             'gstreamer-devel',
             'gstreamer-plugins-base-devel',
             'libstdc++-static',
             'libXt-devel',
             'mercurial',
             'mesa-libGL-devel',
             'pulseaudio-libs-devel',
             'wireless-tools-devel',
-            'yasm')
+            'yasm',
+        ]
+
+    def install_system_packages(self):
+        self.yum_groupinstall(*self.group_packages)
+        self.yum_install(*self.packages)
 
     def upgrade_mercurial(self, current):
         self.yum_update('mercurial')
--- a/python/mozboot/mozboot/freebsd.py
+++ b/python/mozboot/mozboot/freebsd.py
@@ -10,39 +10,43 @@ import sys
 
 from mozboot.base import BaseBootstrapper
 
 class FreeBSDBootstrapper(BaseBootstrapper):
     def __init__(self, version):
         BaseBootstrapper.__init__(self)
         self.version = int(version.split('.')[0])
 
+        self.packages = [
+            ('autoconf-2.13', 'autoconf213'),
+            ('dbus-glib',),
+            ('gmake',),
+            ('gstreamer-plugins',),
+            ('gtk-2', 'gtk20'),
+            ('libGL',),
+            ('libIDL',),
+            ('libv4l',),
+            ('mercurial',),
+            ('pulseaudio',),
+            ('yasm',),
+            ('zip',),
+        ]
+
+        # using clang since 9.0
+        if self.version < 9:
+            self.packages.append(('gcc',))
+
+
     def pkg_install(self, *packages):
         if self.which('pkg'):
             command = ['pkg', 'install', '-x']
             command.extend([i[0] for i in packages])
         else:
             command = ['pkg_add', '-Fr']
             command.extend([i[-1] for i in packages])
 
         self.run_as_root(command)
 
     def install_system_packages(self):
-        # using clang since 9.0
-        if self.version < 9:
-            self.pkg_install(('gcc',))
-
-        self.pkg_install(
-            ('autoconf-2.13', 'autoconf213'),
-            ('dbus-glib',),
-            ('gmake',),
-            ('gstreamer-plugins',),
-            ('gtk-2', 'gtk20'),
-            ('libGL',),
-            ('libIDL',),
-            ('libv4l',),
-            ('mercurial',),
-            ('pulseaudio',),
-            ('yasm',),
-            ('zip',))
+        self.pkg_install(*self.packages)
 
     def upgrade_mercurial(self, current):
         self.pkg_install('mercurial')
--- a/python/mozboot/mozboot/openbsd.py
+++ b/python/mozboot/mozboot/openbsd.py
@@ -5,25 +5,28 @@
 import os
 
 from mozboot.base import BaseBootstrapper
 
 class OpenBSDBootstrapper(BaseBootstrapper):
     def __init__(self, version):
         BaseBootstrapper.__init__(self)
 
-    def install_system_packages(self):
-        # we use -z because there's no other way to say "any autoconf-2.13"
-        self.run_as_root(['pkg_add', '-z',
+        self.packages = [
             'mercurial',
             'llvm',
             'autoconf-2.13',
             'yasm',
             'gtk+2',
             'dbus-glib',
             'gstreamer-plugins-base',
             'pulseaudio',
             'libIDL',
             'gmake',
             'gtar',
             'wget',
             'unzip',
-            'zip'])
+            'zip',
+        ]
+
+    def install_system_packages(self):
+        # we use -z because there's no other way to say "any autoconf-2.13"
+        self.run_as_root(['pkg_add', '-z'] + self.packages)
--- a/services/common/tests/unit/head_helpers.js
+++ b/services/common/tests/unit/head_helpers.js
@@ -167,8 +167,18 @@ function installFakePAC() {
                      PACSystemSettings);
 }
 
 function uninstallFakePAC() {
   _("Uninstalling fake PAC.");
   let CID = PACSystemSettings.CID;
   Cm.nsIComponentRegistrar.unregisterFactory(CID, PACSystemSettings);
 }
+
+// We want to ensure the legacy provider is used for most of these tests; the
+// tests that know how to deal with the Firefox Accounts identity hack things
+// to ensure that still works.
+function setDefaultIdentityConfig() {
+  Cu.import("resource://gre/modules/Services.jsm");
+  Services.prefs.setBoolPref("identity.fxaccounts.enabled", false);
+//  Services.prefs.setBoolPref("services.sync.fxaccounts.enabled", false);
+}
+setDefaultIdentityConfig();
--- a/services/sync/Weave.js
+++ b/services/sync/Weave.js
@@ -64,17 +64,19 @@ WeaveService.prototype = {
     // Side-effect of accessing the service is that it is instantiated.
     Weave.Service;
   },
 
   get fxAccountsEnabled() {
     // first check if Firefox accounts is available at all.  This is so we can
     // get this landed without forcing Fxa to be used (and require nightly
     // testers to manually set this pref)
-    // Once we decide we want Fxa to be available, we just remove this block.
+    // Once we decide we want Fxa to be available, we just remove this block
+    // (although a fly in this ointment is tests - it might be that we must
+    // just set this as a pref with a default of true)
     let fxAccountsAvailable;
     try {
       fxAccountsAvailable = Services.prefs.getBoolPref("identity.fxaccounts.enabled");
     } catch (_) {
     }
     if (!fxAccountsAvailable) {
       // Currently we don't support toggling this pref after initialization, so
       // inject the pref value as a regular boolean.
@@ -96,122 +98,47 @@ WeaveService.prototype = {
       Services.prefs.setBoolPref("services.sync.fxaccounts.enabled", fxAccountsEnabled);
     }
     // Currently we don't support toggling this pref after initialization, so
     // inject the pref value as a regular boolean.
     delete this.fxAccountsEnabled;
     return this.fxAccountsEnabled = fxAccountsEnabled;
   },
 
-  maybeInitWithFxAccountsAndEnsureLoaded: function() {
-    Components.utils.import("resource://services-sync/main.js");
-    // FxAccounts imports lots of stuff, so only do this as we need it
-    Cu.import("resource://gre/modules/FxAccounts.jsm");
-
-    // This isn't quite sufficient here to handle all the cases. Cases
-    // we need to handle:
-    //  - User is signed in to FxAccounts, btu hasn't set up sync.
-    return fxAccounts.getSignedInUser().then(
-      (accountData) => {
-        if (accountData) {
-          Cu.import("resource://services-sync/browserid_identity.js");
-          // The Sync Identity module needs to be set in both these places if
-          // it's swapped out as we are doing here. When Weave.Service initializes
-          // it grabs a reference to Weave.Status._authManager, and for references
-          // to Weave.Service.identity to resolve correctly, we also need to reset
-          // Weave.Service.identity as well.
-          Weave.Service.identity = Weave.Status._authManager = new BrowserIDManager(),
-          // Init the identity module with any account data from
-          // firefox accounts. The Identity module will fetch the signed in
-          // user from fxAccounts directly.
-          Weave.Service.identity.initWithLoggedInUser().then(function () {
-            // Set the cluster data that we got from the token
-            Weave.Service.clusterURL = Weave.Service.identity.clusterURL;
-            // checkSetup() will check the auth state of the identity module
-            // and records that status in Weave.Status
-            if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
-              // This makes sure that Weave.Service is loaded
-              Svc.Obs.notify("weave:service:setup-complete");
-              // TODO: this shouldn't be here. It should be at the end
-              // of the promise chain of the 'fxaccounts:onverified' handler.
-              Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
-              this.ensureLoaded();
-            }
-          }.bind(this));
-        } else if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
-          // This makes sure that Weave.Service is loaded
-          this.ensureLoaded();
-        }
-      },
-      (err) => {dump("err in getting logged in account "+err.message)}
-    ).then(null, (err) => {dump("err in processing logged in account "+err.message)});
-  },
-
   observe: function (subject, topic, data) {
     switch (topic) {
     case "app-startup":
       let os = Cc["@mozilla.org/observer-service;1"].
                getService(Ci.nsIObserverService);
       os.addObserver(this, "final-ui-startup", true);
-      os.addObserver(this, "fxaccounts:onverified", true);
-      os.addObserver(this, "fxaccounts:onlogout", true);
       break;
 
     case "final-ui-startup":
       // Force Weave service to load if it hasn't triggered from overlays
       this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       this.timer.initWithCallback({
         notify: function() {
-          if (this.fxAccountsEnabled) {
-            // init  the fxAccounts identity manager.
-            this.maybeInitWithFxAccountsAndEnsureLoaded();
-          } else {
-            // init the "old" style, sync-specific identity manager.
-            // We only load more if it looks like Sync is configured.
-            let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
-            if (!prefs.prefHasUserValue("username")) {
-              return;
-            }
+          // We only load more if it looks like Sync is configured.
+          let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
+          if (!prefs.prefHasUserValue("username")) {
+            return;
+          }
 
-            // We have a username. So, do a more thorough check. This will
-            // import a number of modules and thus increase memory
-            // accordingly. We could potentially copy code performed by
-            // this check into this file if our above code is yielding too
-            // many false positives.
-            Components.utils.import("resource://services-sync/main.js");
-            if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
-              this.ensureLoaded();
-            }
+          // We have a username. So, do a more thorough check. This will
+          // import a number of modules and thus increase memory
+          // accordingly. We could potentially copy code performed by
+          // this check into this file if our above code is yielding too
+          // many false positives.
+          Components.utils.import("resource://services-sync/main.js");
+          if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
+            this.ensureLoaded();
           }
         }.bind(this)
       }, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
       break;
-
-    case 'fxaccounts:onverified':
-        // Tell sync that if this is a first sync, it should try and sync the
-        // server data with what is on the client - despite the name implying
-        // otherwise, this is what "resetClient" does.
-        // TOOD: This implicitly assumes we're in the CLIENT_NOT_CONFIGURED state, and
-        // if we're not, we should handle it here.
-        Components.utils.import("resource://services-sync/main.js"); // ensure 'Weave' exists
-        Weave.Svc.Prefs.set("firstSync", "resetClient");
-        this.maybeInitWithFxAccountsAndEnsureLoaded().then(() => {
-          // and off we go...
-          // TODO: I have this being done in maybeInitWithFxAccountsAndEnsureLoaded
-          // because I had a bug in the promise chains that was triggering this
-          // too early. This should be fixed.
-          //Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
-        });
-      break;
-    case 'fxaccounts:onlogout':
-      Components.utils.import("resource://services-sync/main.js"); // ensure 'Weave' exists
-      // startOver is throwing some errors and we can't re-log in in this
-      // session - so for now, we don't do this!
-      //Weave.Service.startOver();
-      break;
     }
   }
 };
 
 function AboutWeaveLog() {}
 AboutWeaveLog.prototype = {
   classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"),
 
--- a/services/sync/modules-testing/utils.js
+++ b/services/sync/modules-testing/utils.js
@@ -145,17 +145,20 @@ this.configureFxAccountIdentity = functi
 this.configureIdentity = function(identityOverrides) {
   let config = makeIdentityConfig(identityOverrides);
   let ns = {};
   Cu.import("resource://services-sync/service.js", ns);
 
   if (ns.Service.identity instanceof BrowserIDManager) {
     // do the FxAccounts thang...
     configureFxAccountIdentity(ns.Service.identity, config);
-    return ns.Service.identity.initWithLoggedInUser();
+    return ns.Service.identity.initializeWithCurrentIdentity().then(() => {
+      // need to wait until this identity manager is readyToAuthenticate.
+      return ns.Service.identity.whenReadyToAuthenticate.promise;
+    });
   }
   // old style identity provider.
   setBasicCredentials(config.username, config.sync.password, config.sync.syncKey);
   let deferred = Promise.defer();
   deferred.resolve();
   return deferred.promise;
 }
 
@@ -167,17 +170,19 @@ this.SyncTestingInfrastructure = functio
   let config = makeIdentityConfig();
   // XXX - hacks for the sync identity provider.
   if (username)
     config.username = username;
   if (password)
     config.sync.password = password;
   if (syncKey)
     config.sync.syncKey = syncKey;
-  configureIdentity(config);
+  let cb = Async.makeSpinningCallback();
+  configureIdentity(config).then(cb, cb);
+  cb.wait();
 
   let i = server.identity;
   let uri = i.primaryScheme + "://" + i.primaryHost + ":" +
             i.primaryPort + "/";
 
   ns.Service.serverURL = uri;
   ns.Service.clusterURL = uri;
 
@@ -219,21 +224,21 @@ this.add_identity_test = function(test, 
               {_message: "TEST-INFO | | " + msg + "\n"});
   }
   let ns = {};
   Cu.import("resource://services-sync/service.js", ns);
   // one task for the "old" identity manager.
   test.add_task(function() {
     note("sync");
     let oldIdentity = Status._authManager;
-    Status._authManager = ns.Service.identity = new IdentityManager();
+    Status.__authManager = ns.Service.identity = new IdentityManager();
     yield testFunction();
-    Status._authManager = ns.Service.identity = oldIdentity;
+    Status.__authManager = ns.Service.identity = oldIdentity;
   });
   // another task for the FxAccounts identity manager.
   test.add_task(function() {
     note("FxAccounts");
     let oldIdentity = Status._authManager;
-    Status._authManager = ns.Service.identity = new BrowserIDManager();
+    Status.__authManager = ns.Service.identity = new BrowserIDManager();
     yield testFunction();
-    Status._authManager = ns.Service.identity = oldIdentity;
+    Status.__authManager = ns.Service.identity = oldIdentity;
   });
 }
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -13,60 +13,168 @@ Cu.import("resource://services-common/as
 Cu.import("resource://services-common/tokenserverclient.js");
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-common/tokenserverclient.js");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://services-sync/stages/cluster.js");
 
 // Lazy imports to prevent unnecessary load on startup.
 XPCOMUtils.defineLazyModuleGetter(this, "BulkKeyBundle",
                                   "resource://services-sync/keys.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                   "resource://gre/modules/FxAccounts.jsm");
 
+XPCOMUtils.defineLazyGetter(this, 'fxAccountsCommon', function() {
+  let ob = {};
+  Cu.import("resource://gre/modules/FxAccountsCommon.js", ob);
+  return ob;
+});
+
 function deriveKeyBundle(kB) {
   let out = CryptoUtils.hkdf(kB, undefined,
                              "identity.mozilla.com/picl/v1/oldsync", 2*32);
   let bundle = new BulkKeyBundle();
   // [encryptionKey, hmacKey]
   bundle.keyPair = [out.slice(0, 32), out.slice(32, 64)];
   return bundle;
 }
 
 
 this.BrowserIDManager = function BrowserIDManager() {
   this._fxaService = fxAccounts;
   this._tokenServerClient = new TokenServerClient();
+  // will be a promise that resolves when we are ready to authenticate
+  this.whenReadyToAuthenticate = null;
   this._log = Log.repository.getLogger("Sync.BrowserIDManager");
   this._log.Level = Log.Level[Svc.Prefs.get("log.logger.identity")];
 
 };
 
 this.BrowserIDManager.prototype = {
   __proto__: IdentityManager.prototype,
 
   _fxaService: null,
   _tokenServerClient: null,
   // https://docs.services.mozilla.com/token/apis.html
   _token: null,
   _account: null,
 
+  // it takes some time to fetch a sync key bundle, so until this flag is set,
+  // we don't consider the lack of a keybundle as a failure state.
+  _shouldHaveSyncKeyBundle: false,
+
+  get readyToAuthenticate() {
+    // We are finished initializing when we *should* have a sync key bundle,
+    // although we might not actually have one due to auth failures etc.
+    return this._shouldHaveSyncKeyBundle;
+  },
+
+  get needsCustomization() {
+    try {
+      return Services.prefs.getBoolPref("services.sync.needsCustomization");
+    } catch (e) {
+      return false;
+    }
+  },
+
+  initialize: function() {
+    Services.obs.addObserver(this, fxAccountsCommon.ONVERIFIED_NOTIFICATION, false);
+    Services.obs.addObserver(this, fxAccountsCommon.ONLOGOUT_NOTIFICATION, false);
+    return this.initializeWithCurrentIdentity();
+  },
+
+  initializeWithCurrentIdentity: function() {
+    this._log.trace("initializeWithCurrentIdentity");
+    Components.utils.import("resource://services-sync/main.js");
+
+    // Reset the world before we do anything async.
+    this.whenReadyToAuthenticate = Promise.defer();
+    this._shouldHaveSyncKeyBundle = false;
+    this.username = ""; // this calls resetCredentials which drops the key bundle.
+
+    return fxAccounts.getSignedInUser().then(accountData => {
+      if (!accountData) {
+        this._log.info("initializeWithCurrentIdentity has no user logged in");
+        this._account = null;
+        return;
+      }
+
+      if (this.needsCustomization) {
+        // If the user chose to "Customize sync options" when signing
+        // up with Firefox Accounts, ask them to choose what to sync.
+        const url = "chrome://browser/content/sync/customize.xul";
+        const features = "centerscreen,chrome,modal,dialog,resizable=no";
+        let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+        let data = {accepted: false};
+        win.openDialog(url, "_blank", features, data);
+
+        if (data.accepted) {
+          Services.prefs.clearUserPref("services.sync.needsCustomization");
+        } else {
+          // Log out if the user canceled the dialog.
+          return fxAccounts.signOut();
+        }
+      }
+
+      this._account = accountData.email;
+      // We start a background keybundle fetch...
+      this._log.info("Starting background fetch for key bundle.");
+      this._fetchSyncKeyBundle().then(() => {
+        this._shouldHaveSyncKeyBundle = true; // and we should actually have one...
+        this.whenReadyToAuthenticate.resolve();
+        this._log.info("Background fetch for key bundle done");
+      }).then(null, err => {
+        this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
+        this.whenReadyToAuthenticate.reject(err);
+        // report what failed...
+        this._log.error("Background fetch for key bundle failed: " + err);
+        throw err;
+      });
+      // and we are done - the fetch continues on in the background...
+    }).then(null, err => {
+      dump("err in processing logged in account "+err.message);
+    });
+  },
+
+  observe: function (subject, topic, data) {
+    switch (topic) {
+    case fxAccountsCommon.ONVERIFIED_NOTIFICATION:
+    case fxAccountsCommon.ONLOGIN_NOTIFICATION:
+      // For now, we just assume it's the same user logging back in.
+      // Bug 958927 exists to work out what to do if that's not true.  It might
+      // be that the :onlogout observer does a .startOver (or maybe not - TBD)
+      // But for now, do nothing, and sync will just start re-synching in its
+      // own sweet time...
+      this.initializeWithCurrentIdentity();
+      break;
+
+    case fxAccountsCommon.ONLOGOUT_NOTIFICATION:
+      Components.utils.import("resource://services-sync/main.js");
+      // Setting .username calls resetCredentials which drops the key bundle
+      // and resets _shouldHaveSyncKeyBundle.
+      this.username = "";
+      this._account = null;
+      Weave.Service.logout();
+      break;
+    }
+  },
+
   /**
    * Provide override point for testing token expiration.
    */
   _now: function() {
     return Date.now();
   },
 
-  clusterURL: null,
-
   get account() {
     return this._account;
   },
 
   /**
    * Sets the active account name.
    *
    * This should almost always be called in favor of setting username, as
@@ -143,16 +251,17 @@ this.BrowserIDManager.prototype = {
 
   /**
    * Resets/Drops the sync key we hold for the current user.
    */
   resetSyncKey: function() {
     this._syncKey = null;
     this._syncKeyBundle = null;
     this._syncKeyUpdated = true;
+    this._shouldHaveSyncKeyBundle = false;
   },
 
   /**
    * The current state of the auth credentials.
    *
    * This essentially validates that enough credentials are available to use
    * Sync.
    */
@@ -161,18 +270,18 @@ this.BrowserIDManager.prototype = {
     // both the username and syncKeyBundle are both configured and having no
     // username seems to make things fail fast so that's good.
     if (!this.username) {
       return LOGIN_FAILED_NO_USERNAME;
     }
 
     // No need to check this.syncKey as our getter for that attribute
     // uses this.syncKeyBundle
-    // If bundle creation failed.
-    if (!this.syncKeyBundle) {
+    // If bundle creation started, but failed.
+    if (this._shouldHaveSyncKeyBundle && !this.syncKeyBundle) {
       return LOGIN_FAILED_NO_PASSPHRASE;
     }
 
     return STATUS_OK;
   },
 
   /**
    * Do we have a non-null, not yet expired token whose email field
@@ -216,55 +325,34 @@ this.BrowserIDManager.prototype = {
       userData = cb.wait();
     } catch (err) {
       this._log.error("FxAccounts.getSignedInUser() failed with: " + err);
       return null;
     }
     return userData;
   },
 
-  // initWithLoggedInUser will fetch the logged in user from firefox accounts,
-  // and if such a logged in user exists, will use that user to initialize
-  // the identity module. Returns a Promise.
-  initWithLoggedInUser: function() {
-    // Get the signed in user from FxAccounts.
-    return this._fxaService.getSignedInUser()
-      .then(userData => {
-        if (!userData) {
-          this._log.warn("initWithLoggedInUser found no logged in user");
-          throw new Error("initWithLoggedInUser found no logged in user");
-        }
-        // Make a note of the last logged in user.
-        this._account = userData.email;
-        // Fetch a sync token for the logged in user from the token server.
-        return this._refreshTokenForLoggedInUser();
-      })
-      .then(token => {
-        this._token = token;
-        // Set the username to be the uid returned by the token server.
-        // TODO: check here to see if the uid is different that the current
-        // this.username. If so, we may need to reinit sync, detect if the new
-        // user has sync set up, etc
-        this.username = this._token.uid.toString();
-
-        return this._fxaService.getKeys();
-      })
-      .then(userData => {
-        // both Jelly and FxAccounts give us kA/kB as hex.
-        let kB = Utils.hexToBytes(userData.kB);
-        this._syncKeyBundle = deriveKeyBundle(kB);
-
-        // Set the clusterURI for this user based on the endpoint in the
-        // token. This is a bit of a hack, and we should figure out a better
-        // way of distributing it to components that need it.
-        let clusterURI = Services.io.newURI(this._token.endpoint, null, null);
-        clusterURI.path = "/";
-        this.clusterURL = clusterURI.spec;
-        this._log.info("initWithLoggedUser has username " + this.username + ", endpoint is " + this.clusterURL);
-      });
+  _fetchSyncKeyBundle: function() {
+    // Fetch a sync token for the logged in user from the token server.
+    return this._refreshTokenForLoggedInUser(
+    ).then(token => {
+      this._token = token;
+      return this._fxaService.getKeys();
+    }).then(userData => {
+      // unlikely, but if the logged in user somehow changed between these
+      // calls we better fail.
+      if (!userData || userData.email !== this.account) {
+        throw new Error("The currently logged-in user has changed.");
+      }
+      // Set the username to be the uid returned by the token server.
+      this.username = this._token.uid.toString();
+      // both Jelly and FxAccounts give us kA/kB as hex.
+      let kB = Utils.hexToBytes(userData.kB);
+      this._syncKeyBundle = deriveKeyBundle(kB);
+    });
   },
 
   // Refresh the sync token for the currently logged in Firefox Accounts user.
   // This method requires that this module has been intialized for a user.
   _refreshTokenForLoggedInUser: function() {
     return this._fxaService.getSignedInUser().then(function (userData) {
       if (!userData || userData.email !== this.account) {
         // This means the logged in user changed or the identity module
@@ -363,10 +451,50 @@ this.BrowserIDManager.prototype = {
 
   _addAuthenticationHeader: function(request, method) {
     let header = this._getAuthenticationHeader(request, method);
     if (!header) {
       return null;
     }
     request.setHeader("authorization", header.headers.authorization);
     return request;
+  },
+
+  createClusterManager: function(service) {
+    return new BrowserIDClusterManager(service);
   }
+
 };
+
+/* An implementation of the ClusterManager for this identity
+ */
+
+function BrowserIDClusterManager(service) {
+  ClusterManager.call(this, service);
+}
+
+BrowserIDClusterManager.prototype = {
+  __proto__: ClusterManager.prototype,
+
+  _findCluster: function() {
+    let promiseClusterURL = function() {
+      return fxAccounts.getSignedInUser().then(userData => {
+        return this.identity._fetchTokenForUser(userData).then(token => {
+          // Set the clusterURI for this user based on the endpoint in the
+          // token. This is a bit of a hack, and we should figure out a better
+          // way of distributing it to components that need it.
+          let clusterURI = Services.io.newURI(token.endpoint, null, null);
+          clusterURI.path = "/";
+          return clusterURI.spec;
+        });
+      });
+    }.bind(this);
+
+    let cb = Async.makeSpinningCallback();
+    promiseClusterURL().then(function (clusterURL) {
+        cb(null, clusterURL);
+    },
+    function (err) {
+        cb(err);
+    });
+    return cb.wait();
+  },
+}
--- a/services/sync/modules/constants.js
+++ b/services/sync/modules/constants.js
@@ -114,16 +114,17 @@ ENGINE_SUCCEEDED:                      "
 // login failure status codes:
 LOGIN_FAILED_NO_USERNAME:              "error.login.reason.no_username",
 LOGIN_FAILED_NO_PASSWORD:              "error.login.reason.no_password2",
 LOGIN_FAILED_NO_PASSPHRASE:            "error.login.reason.no_recoverykey",
 LOGIN_FAILED_NETWORK_ERROR:            "error.login.reason.network",
 LOGIN_FAILED_SERVER_ERROR:             "error.login.reason.server",
 LOGIN_FAILED_INVALID_PASSPHRASE:       "error.login.reason.recoverykey",
 LOGIN_FAILED_LOGIN_REJECTED:           "error.login.reason.account",
+LOGIN_FAILED_NOT_READY:                "error.login.reason.initializing",
 
 // sync failure status codes
 METARECORD_DOWNLOAD_FAIL:              "error.sync.reason.metarecord_download_fail",
 VERSION_OUT_OF_DATE:                   "error.sync.reason.version_out_of_date",
 DESKTOP_VERSION_OUT_OF_DATE:           "error.sync.reason.desktop_version_out_of_date",
 SETUP_FAILED_NO_PASSPHRASE:            "error.sync.reason.setup_failed_no_passphrase",
 CREDENTIALS_CHANGED:                   "error.sync.reason.credentials_changed",
 ABORT_SYNC_COMMAND:                    "aborting sync, process commands said so",
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -4,29 +4,31 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["IdentityManager"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/util.js");
 
 // Lazy import to prevent unnecessary load on startup.
 for (let symbol of ["BulkKeyBundle", "SyncKeyBundle"]) {
   XPCOMUtils.defineLazyModuleGetter(this, symbol,
                                     "resource://services-sync/keys.js",
                                     symbol);
 }
 
 /**
- * Manages identity and authentication for Sync.
+ * Manages "legacy" identity and authentication for Sync.
+ * See browserid_identity for the Firefox Accounts based identity manager.
  *
  * The following entities are managed:
  *
  *   account - The main Sync/services account. This is typically an email
  *     address.
  *   username - A normalized version of your account. This is what's
  *     transmitted to the server.
  *   basic password - UTF-8 password used for authenticating when using HTTP
@@ -76,16 +78,34 @@ IdentityManager.prototype = {
   _basicPasswordUpdated: false,
 
   _syncKey: null,
   _syncKeyAllowLookup: true,
   _syncKeySet: false,
 
   _syncKeyBundle: null,
 
+  /**
+   * Initialize the identity provider.  Returns a promise that is resolved
+   * when initialization is complete and the provider can be queried for
+   * its state
+   */
+  initialize: function() {
+    // nothing to do for this identity provider
+    return Promise.resolve();
+  },
+
+  /**
+   * Indicates if the identity manager is still initializing
+   */
+  get readyToAuthenticate() {
+    // We initialize in a fully sync manner, so we are always finished.
+    return true;
+  },
+
   get account() {
     return Svc.Prefs.get("account", this.username);
   },
 
   /**
    * Sets the active account name.
    *
    * This should almost always be called in favor of setting username, as
@@ -500,10 +520,15 @@ IdentityManager.prototype = {
     }
 
     return null;
   },
 
   onRESTRequestBasic: function onRESTRequestBasic(request) {
     let up = this.username + ":" + this.basicPassword;
     request.setHeader("authorization", "Basic " + btoa(up));
+  },
+
+  createClusterManager: function(service) {
+    Cu.import("resource://services-sync/stages/cluster.js");
+    return new ClusterManager(service);
   }
 };
--- a/services/sync/modules/policies.js
+++ b/services/sync/modules/policies.js
@@ -7,19 +7,21 @@ this.EXPORTED_SYMBOLS = [
   "SyncScheduler",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
-Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/util.js");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Status",
+                                  "resource://services-sync/status.js");
+
 this.SyncScheduler = function SyncScheduler(service) {
   this.service = service;
   this.init();
 }
 SyncScheduler.prototype = {
   _log: Log.repository.getLogger("Sync.SyncScheduler"),
 
   _fatalLoginStatus: [LOGIN_FAILED_NO_USERNAME,
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -25,17 +25,16 @@ Cu.import("resource://services-common/ut
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/rest.js");
-Cu.import("resource://services-sync/stages/cluster.js");
 Cu.import("resource://services-sync/stages/enginesync.js");
 Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/userapi.js");
 Cu.import("resource://services-sync/util.js");
 
 const ENGINE_MODULES = {
   Addons: "addons.js",
   Bookmarks: "bookmarks.js",
@@ -318,17 +317,17 @@ Sync11Service.prototype = {
     this.errorHandler = new ErrorHandler(this);
 
     this._log = Log.repository.getLogger("Sync.Service");
     this._log.level =
       Log.Level[Svc.Prefs.get("log.logger.service.main")];
 
     this._log.info("Loading Weave " + WEAVE_VERSION);
 
-    this._clusterManager = new ClusterManager(this);
+    this._clusterManager = this.identity.createClusterManager(this);
     this.recordManager = new RecordManager(this);
 
     this.enabled = true;
 
     this._registerEngines();
 
     let ua = Cc["@mozilla.org/network/protocol;1?name=http"].
       getService(Ci.nsIHttpProtocolHandler).userAgent;
@@ -644,16 +643,23 @@ Sync11Service.prototype = {
       this._log.debug("Failed to fetch and verify keys: "
                       + Utils.exceptionStr(ex));
       this.errorHandler.checkServerError(ex);
       return false;
     }
   },
 
   verifyLogin: function verifyLogin() {
+    // If the identity isn't ready it  might not know the username...
+    if (!this.identity.readyToAuthenticate) {
+      this._log.info("Not ready to authenticate in verifyLogin.");
+      this.status.login = LOGIN_FAILED_NOT_READY;
+      return false;
+    }
+
     if (!this.identity.username) {
       this._log.warn("No username in verifyLogin.");
       this.status.login = LOGIN_FAILED_NO_USERNAME;
       return false;
     }
 
     // Unlock master password, or return.
     // Attaching auth credentials to a request requires access to
--- a/services/sync/modules/status.js
+++ b/services/sync/modules/status.js
@@ -7,23 +7,41 @@ this.EXPORTED_SYMBOLS = ["Status"];
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/identity.js");
+Cu.import("resource://services-sync/browserid_identity.js");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://services-common/async.js");
 
 this.Status = {
   _log: Log.repository.getLogger("Sync.Status"),
-  _authManager: new IdentityManager(),
+  __authManager: null,
   ready: false,
 
+  get _authManager() {
+    if (this.__authManager) {
+      return this.__authManager;
+    }
+    let service = Components.classes["@mozilla.org/weave/service;1"]
+                    .getService(Components.interfaces.nsISupports)
+                    .wrappedJSObject;
+    let idClass = service.fxAccountsEnabled ? BrowserIDManager : IdentityManager;
+    this.__authManager = new idClass();
+    // .initialize returns a promise, so we need to spin until it resolves.
+    let cb = Async.makeSpinningCallback();
+    this.__authManager.initialize().then(cb, cb);
+    cb.wait();
+    return this.__authManager;
+  },
+
   get service() {
     return this._service;
   },
 
   set service(code) {
     this._log.debug("Status.service: " + this._service + " => " + code);
     this._service = code;
   },
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -68,8 +68,11 @@ pref("services.sync.log.logger.engine.pa
 pref("services.sync.log.logger.engine.prefs", "Debug");
 pref("services.sync.log.logger.engine.tabs", "Debug");
 pref("services.sync.log.logger.engine.addons", "Debug");
 pref("services.sync.log.logger.engine.apps", "Debug");
 pref("services.sync.log.logger.userapi", "Debug");
 pref("services.sync.log.cryptoDebug", false);
 
 pref("services.sync.tokenServerURI", "http://auth.oldsync.dev.lcip.org/1.0/sync/1.1");
+
+pref("services.sync.fxa.termsURL", "https://accounts.firefox.com/legal/terms");
+pref("services.sync.fxa.privacyURL", "https://accounts.firefox.com/legal/privacy");
--- a/testing/marionette/client/marionette/runner/base.py
+++ b/testing/marionette/client/marionette/runner/base.py
@@ -527,16 +527,17 @@ class BaseMarionetteTestRunner(object):
         self.timeout = timeout
         self._device = None
         self._capabilities = None
         self._appName = None
         self.es_servers = es_servers
         self.shuffle = shuffle
         self.sdcard = sdcard
         self.mixin_run_tests = []
+        self.manifest_skipped_tests = []
 
         if testvars:
             if not os.path.exists(testvars):
                 raise IOError('--testvars file does not exist')
 
             import json
             try:
                 with open(testvars) as f:
@@ -801,32 +802,40 @@ class BaseMarionetteTestRunner(object):
 
         testloader = unittest.TestLoader()
         suite = unittest.TestSuite()
 
         if file_ext == '.ini':
             manifest = TestManifest()
             manifest.read(filepath)
 
-            all_tests = manifest.active_tests(exists=False, disabled=False)
             manifest_tests = manifest.active_tests(exists=False,
-                                                   disabled=False,
+                                                   disabled=True,
                                                    device=self.device,
                                                    app=self.appName,
                                                    **mozinfo.info)
-            skip_tests = list(set([x['path'] for x in all_tests]) -
-                              set([x['path'] for x in manifest_tests]))
-            for skipped in skip_tests:
-                self.logger.info('TEST-SKIP | %s | device=%s, app=%s' %
-                                 (os.path.basename(skipped),
-                                  self.device,
-                                  self.appName))
+            unfiltered_tests = []
+            for test in manifest_tests:
+                if test.get('disabled'):
+                    self.manifest_skipped_tests.append(test)
+                else:
+                    unfiltered_tests.append(test)
+
+            target_tests = manifest.get(tests=unfiltered_tests, **testargs)
+            for test in unfiltered_tests:
+                if test['path'] not in [x['path'] for x in target_tests]:
+                    test.setdefault('disabled', 'filtered by type (%s)' % self.type)
+                    self.manifest_skipped_tests.append(test)
+
+            for test in self.manifest_skipped_tests:
+                self.logger.info('TEST-SKIP | %s | %s' % (
+                    os.path.basename(test['path']),
+                    test['disabled']))
                 self.todo += 1
 
-            target_tests = manifest.get(tests=manifest_tests, **testargs)
             if self.shuffle:
                 random.shuffle(target_tests)
             for i in target_tests:
                 if not os.path.exists(i["path"]):
                     raise IOError("test file: %s does not exist" % i["path"])
                 self.run_test(i["path"], i["expected"])
                 if self.marionette.check_for_crash():
                     return
@@ -869,27 +878,42 @@ class BaseMarionetteTestRunner(object):
     def cleanup(self):
         if self.httpd:
             self.httpd.stop()
 
     __del__ = cleanup
 
     def generate_xml(self, results_list):
 
-        def _extract_xml(test, result='passed'):
+        def _extract_xml_from_result(test_result, result='passed'):
+            _extract_xml(
+                test_name=unicode(test_result.name).split()[0],
+                test_class=test_result.test_class,
+                duration=test_result.duration,
+                result=result,
+                output='\n'.join(test_result.output))
+
+        def _extract_xml_from_skipped_manifest_test(test):
+            _extract_xml(
+                test_name=test['name'],
+                result='skipped',
+                output=test['disabled'])
+
+        def _extract_xml(test_name, test_class='', duration=0,
+                         result='passed', output=''):
             testcase = doc.createElement('testcase')
-            testcase.setAttribute('classname', test.test_class)
-            testcase.setAttribute('name', unicode(test.name).split()[0])
-            testcase.setAttribute('time', str(test.duration))
+            testcase.setAttribute('classname', test_class)
+            testcase.setAttribute('name', test_name)
+            testcase.setAttribute('time', str(duration))
             testsuite.appendChild(testcase)
 
             if result in ['failure', 'error', 'skipped']:
                 f = doc.createElement(result)
                 f.setAttribute('message', 'test %s' % result)
-                f.appendChild(doc.createTextNode(test.reason))
+                f.appendChild(doc.createTextNode(output))
                 testcase.appendChild(f)
 
         doc = dom.Document()
 
         testsuite = doc.createElement('testsuite')
         testsuite.setAttribute('name', 'Marionette')
         testsuite.setAttribute('time', str(self.elapsedtime.total_seconds()))
         testsuite.setAttribute('tests', str(sum([results.testsRun for
@@ -900,39 +924,44 @@ class BaseMarionetteTestRunner(object):
             if hasattr(results, 'unexpectedSuccesses'):
                 count += len(results.unexpectedSuccesses)
             return count
 
         testsuite.setAttribute('failures', str(sum([failed_count(results)
                                                for results in results_list])))
         testsuite.setAttribute('errors', str(sum([len(results.errors)
                                              for results in results_list])))
-        testsuite.setAttribute('skips', str(sum([len(results.skipped) +
-                                                     len(results.expectedFailures)
-                                                     for results in results_list])))
+        testsuite.setAttribute(
+            'skips', str(sum([len(results.skipped) +
+                         len(results.expectedFailures)
+                         for results in results_list]) +
+                         len(self.manifest_skipped_tests)))
 
         for results in results_list:
 
             for result in results.errors:
-                _extract_xml(result, result='error')
+                _extract_xml_from_result(result, result='error')
 
             for result in results.failures:
-                _extract_xml(result, result='failure')
+                _extract_xml_from_result(result, result='failure')
 
             if hasattr(results, 'unexpectedSuccesses'):
                 for test in results.unexpectedSuccesses:
                     # unexpectedSuccesses is a list of Testcases only, no tuples
-                    _extract_xml(test, result='failure')
+                    _extract_xml_from_result(test, result='failure')
 
             if hasattr(results, 'skipped'):
                 for result in results.skipped:
-                    _extract_xml(result, result='skipped')
+                    _extract_xml_from_result(result, result='skipped')
 
             if hasattr(results, 'expectedFailures'):
                 for result in results.expectedFailures:
-                    _extract_xml(result, result='skipped')
+                    _extract_xml_from_result(result, result='skipped')
 
             for result in results.tests_passed:
-                _extract_xml(result)
+                _extract_xml_from_result(result)
+
+        for test in self.manifest_skipped_tests:
+            _extract_xml_from_skipped_manifest_test(test)
 
         doc.appendChild(testsuite)
         return doc.toprettyxml(encoding='utf-8')
 
--- a/testing/marionette/client/marionette/runner/mixins/reporting.py
+++ b/testing/marionette/client/marionette/runner/mixins/reporting.py
@@ -39,40 +39,56 @@ class HTMLReportingTestRunnerMixin(objec
                 os.makedirs(html_dir)
             with open(self.html_output, 'w') as f:
                 f.write(self.generate_html(self.results))
 
     def generate_html(self, results_list):
         tests = sum([results.testsRun for results in results_list])
         failures = sum([len(results.failures) for results in results_list])
         expected_failures = sum([len(results.expectedFailures) for results in results_list])
-        skips = sum([len(results.skipped) for results in results_list])
+        skips = sum([len(results.skipped) for results in results_list]) + len(self.manifest_skipped_tests)
         errors = sum([len(results.errors) for results in results_list])
         passes = sum([results.passed for results in results_list])
         unexpected_passes = sum([len(results.unexpectedSuccesses) for results in results_list])
         test_time = self.elapsedtime.total_seconds()
         test_logs = []
 
-        def _extract_html(test):
+        def _extract_html_from_result(result):
+            _extract_html(
+                result=result.result,
+                test_name=result.name,
+                test_class=result.test_class,
+                debug=result.debug,
+                output='\n'.join(result.output))
+
+        def _extract_html_from_skipped_manifest_test(test):
+            _extract_html(
+                result='skipped',
+                test_name=test['name'],
+                output=test.get('disabled'))
+
+        def _extract_html(result, test_name, test_class='', duration=0,
+                          debug=None, output=''):
             additional_html = []
+            debug = debug or {}
             links_html = []
 
             result_map = {
                 'KNOWN-FAIL': 'expected failure',
                 'PASS': 'passed',
                 'UNEXPECTED-FAIL': 'failure',
                 'UNEXPECTED-PASS': 'unexpected pass'}
 
-            if test.result in ['SKIPPED', 'UNEXPECTED-FAIL', 'KNOWN-FAIL', 'ERROR']:
-                if test.debug.get('screenshot'):
-                    screenshot = 'data:image/png;base64,%s' % test.debug['screenshot']
+            if result.upper() in ['SKIPPED', 'UNEXPECTED-FAIL', 'KNOWN-FAIL', 'ERROR']:
+                if debug.get('screenshot'):
+                    screenshot = 'data:image/png;base64,%s' % debug['screenshot']
                     additional_html.append(html.div(
                         html.a(html.img(src=screenshot), href="#"),
                         class_='screenshot'))
-                for name, content in test.debug.items():
+                for name, content in debug.items():
                     try:
                         if 'screenshot' in name:
                             href = '#'
                         else:
                             # use base64 to avoid that some browser (such as Firefox, Opera)
                             # treats '#' as the start of another link if the data URL contains.
                             # use 'charset=utf-8' to show special characters like Chinese.
                             href = 'data:text/plain;charset=utf-8;base64,%s' % base64.b64encode(content)
@@ -81,39 +97,42 @@ class HTMLReportingTestRunnerMixin(objec
                             class_=name,
                             href=href,
                             target='_blank'))
                         links_html.append(' ')
                     except:
                         pass
 
                 log = html.div(class_='log')
-                for line in '\n'.join(test.output).splitlines():
+                for line in output.splitlines():
                     separator = line.startswith(' ' * 10)
                     if separator:
                         log.append(line[:80])
                     else:
                         if line.lower().find("error") != -1 or line.lower().find("exception") != -1:
                             log.append(html.span(raw(cgi.escape(line)), class_='error'))
                         else:
                             log.append(raw(cgi.escape(line)))
                     log.append(html.br())
                 additional_html.append(log)
 
             test_logs.append(html.tr([
-                html.td(result_map.get(test.result, test.result).title(), class_='col-result'),
-                html.td(test.test_class, class_='col-class'),
-                html.td(unicode(test.name), class_='col-name'),
-                html.td(int(test.duration), class_='col-duration'),
+                html.td(result_map.get(result, result).title(), class_='col-result'),
+                html.td(test_class, class_='col-class'),
+                html.td(test_name, class_='col-name'),
+                html.td(str(duration), class_='col-duration'),
                 html.td(links_html, class_='col-links'),
                 html.td(additional_html, class_='debug')],
-                class_=result_map.get(test.result, test.result).lower() + ' results-table-row'))
+                class_=result_map.get(result, result).lower() + ' results-table-row'))
 
         for results in results_list:
-            [_extract_html(test) for test in results.tests]
+            [_extract_html_from_result(test) for test in results.tests]
+
+        for test in self.manifest_skipped_tests:
+            _extract_html_from_skipped_manifest_test(test)
 
         generated = datetime.datetime.now()
         doc = html.html(
             html.head(
                 html.meta(charset='utf-8'),
                 html.title('Test Report'),
                 #TODO: must redisgn this to use marionette's resourcs, instead of the caller folder's
                 html.style(raw(pkg_resources.resource_string(
--- a/toolkit/components/places/tests/unit/test_bookmarks_html.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_html.js
@@ -325,41 +325,31 @@ function checkItem(aExpected, aNode)
           do_check_eq(PlacesUtils.bookmarks.getItemDateAdded(id),
                       aExpected.dateAdded);
           break;
         case "lastModified":
           do_check_eq(PlacesUtils.bookmarks.getItemLastModified(id),
                       aExpected.lastModified);
           break;
         case "url":
-          yield function() {
-            let deferred = Promise.defer();
-            PlacesUtils.livemarks.getLivemark(
-              { id: id },
-              function (aStatus, aLivemark) {
-                if (!Components.isSuccessCode(aStatus)) {
-                  do_check_eq(aNode.uri, aExpected.url);
-                }
-                deferred.resolve();
-              }
-            );
-          return deferred.promise; }();
+          if (!("feedUrl" in aExpected))
+            do_check_eq(aNode.uri, aExpected.url)
           break;
         case "icon":
-          yield function() {
-            let deferred = Promise.defer();
+          let (deferred = Promise.defer(), data) {
             PlacesUtils.favicons.getFaviconDataForPage(
               NetUtil.newURI(aExpected.url),
               function (aURI, aDataLen, aData, aMimeType) {
-                let base64Icon = "data:image/png;base64," +
-                  base64EncodeString(String.fromCharCode.apply(String, aData));
-                do_check_true(base64Icon == aExpected.icon);
-                deferred.resolve();
-            });
-            return deferred.promise; }();
+                deferred.resolve(aData);
+              });
+            data = yield deferred.promise;
+            let base64Icon = "data:image/png;base64," +
+                             base64EncodeString(String.fromCharCode.apply(String, data));
+            do_check_true(base64Icon == aExpected.icon);
+          }
           break;
         case "keyword":
           break;
         case "sidebar":
           do_check_eq(PlacesUtils.annotations
                                  .itemHasAnnotation(id, LOAD_IN_SIDEBAR_ANNO),
                       aExpected.sidebar);
           break;
@@ -368,28 +358,23 @@ function checkItem(aExpected, aNode)
                                  .getItemAnnotation(id, PlacesUtils.POST_DATA_ANNO),
                       aExpected.postData);
           break;
         case "charset":
           let testURI = NetUtil.newURI(aNode.uri);
           do_check_eq((yield PlacesUtils.getCharsetForURI(testURI)), aExpected.charset);
           break;
         case "feedUrl":
-          yield function() {
-            let deferred = Promise.defer();
-            PlacesUtils.livemarks.getLivemark(
-              { id: id },
-              function (aStatus, aLivemark) {
-                do_check_true(Components.isSuccessCode(aStatus));
-                do_check_eq(aLivemark.siteURI.spec, aExpected.url);
-                do_check_eq(aLivemark.feedURI.spec, aExpected.feedUrl);
-                deferred.resolve();
-              }
-            );
-          return deferred.promise; }();
+          yield PlacesUtils.livemarks.getLivemark(
+            { id: id },
+            (aStatus, aLivemark) => {
+              do_check_true(Components.isSuccessCode(aStatus));
+              do_check_eq(aLivemark.siteURI.spec, aExpected.url);
+              do_check_eq(aLivemark.feedURI.spec, aExpected.feedUrl);
+            });
           break;
         case "children":
           let folder = aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
           do_check_eq(folder.hasChildren, aExpected.children.length > 0);
           folder.containerOpen = true;
           do_check_eq(folder.childCount, aExpected.children.length);
 
           aExpected.children.forEach(function (item, index) checkItem(item, folder.getChild(index)));
--- a/toolkit/components/places/tests/unit/test_bookmarks_json.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_json.js
@@ -156,67 +156,54 @@ function checkItem(aExpected, aNode) {
           do_check_eq(PlacesUtils.bookmarks.getItemDateAdded(id),
                       aExpected.dateAdded);
           break;
         case "lastModified":
           do_check_eq(PlacesUtils.bookmarks.getItemLastModified(id),
                       aExpected.lastModified);
           break;
         case "url":
-          yield function() {
-            let deferred = Promise.defer();
-            PlacesUtils.livemarks.getLivemark(
-              { id: id },
-              function (aStatus, aLivemark) {
-                if (!Components.isSuccessCode(aStatus)) {
-                  do_check_eq(aNode.uri, aExpected.url);
-                }
-                deferred.resolve();
-              });
-            return deferred.promise; }();
+          if (!("feedUrl" in aExpected))
+            do_check_eq(aNode.uri, aExpected.url);
           break;
         case "icon":
-          yield function() {
-            let deferred = Promise.defer();
+          let (deferred = Promise.defer(), data) {
             PlacesUtils.favicons.getFaviconDataForPage(
               NetUtil.newURI(aExpected.url),
               function (aURI, aDataLen, aData, aMimeType) {
-                let base64Icon = "data:image/png;base64," +
-                      base64EncodeString(String.fromCharCode.apply(String, aData));
-                do_check_true(base64Icon == aExpected.icon);
-                deferred.resolve();
+                deferred.resolve(aData);
               });
-            return deferred.promise; }();
+            data = yield deferred.promise;
+            let base64Icon = "data:image/png;base64," +
+                             base64EncodeString(String.fromCharCode.apply(String, data));
+            do_check_true(base64Icon == aExpected.icon);
+          }
           break;
         case "keyword":
           break;
         case "sidebar":
           do_check_eq(PlacesUtils.annotations.itemHasAnnotation(
                       id, LOAD_IN_SIDEBAR_ANNO), aExpected.sidebar);
           break;
         case "postData":
           do_check_eq(PlacesUtils.annotations.getItemAnnotation(
                       id, PlacesUtils.POST_DATA_ANNO), aExpected.postData);
           break;
         case "charset":
           let testURI = NetUtil.newURI(aNode.uri);
           do_check_eq((yield PlacesUtils.getCharsetForURI(testURI)), aExpected.charset);
           break;
         case "feedUrl":
-          yield function() {
-            let deferred = Promise.defer();
-            PlacesUtils.livemarks.getLivemark(
-              { id: id },
-              function (aStatus, aLivemark) {
-                do_check_true(Components.isSuccessCode(aStatus));
-                do_check_eq(aLivemark.siteURI.spec, aExpected.url);
-                do_check_eq(aLivemark.feedURI.spec, aExpected.feedUrl);
-                deferred.resolve();
-              });
-            return deferred.promise; }();
+          yield PlacesUtils.livemarks.getLivemark(
+            { id: id },
+            (aStatus, aLivemark) => {
+              do_check_true(Components.isSuccessCode(aStatus));
+              do_check_eq(aLivemark.siteURI.spec, aExpected.url);
+              do_check_eq(aLivemark.feedURI.spec, aExpected.feedUrl);
+            });
           break;
         case "children":
           let folder = aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
           do_check_eq(folder.hasChildren, aExpected.children.length > 0);
           folder.containerOpen = true;
           do_check_eq(folder.childCount, aExpected.children.length);
 
           aExpected.children.forEach(function (item, index) checkItem(item, folder.getChild(index)));
--- a/toolkit/content/widgets/toolbarbutton.xml
+++ b/toolkit/content/widgets/toolbarbutton.xml
@@ -14,47 +14,47 @@
     <resources>
       <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
     </resources>
     
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
-                 xbl:inherits="value=label,accesskey,crop"/>
+                 xbl:inherits="value=label,accesskey,crop,wrap"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
-                 xbl:inherits="xbl:text=label,accesskey"/>
+                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
     </content>
   </binding>
 
   <binding id="menu" display="xul:menu" 
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
-                 xbl:inherits="value=label,accesskey,crop,dragover-top"/>
+                 xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
-                 xbl:inherits="xbl:text=label,accesskey"/>
+                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
       <xul:dropmarker anonid="dropmarker" type="menu"
                       class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
     </content>
   </binding>
   
   <binding id="menu-vertical" display="xul:menu"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:hbox flex="1" align="center">
         <xul:vbox flex="1" align="center">
           <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
           <xul:label class="toolbarbutton-text" crop="right" flex="1"
-                     xbl:inherits="value=label,accesskey,crop,dragover-top"/>
+                     xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
           <xul:label class="toolbarbutton-multiline-text" flex="1"
-                     xbl:inherits="xbl:text=label,accesskey"/>
+                     xbl:inherits="xbl:text=label,accesskey,wrap"/>
         </xul:vbox>
         <xul:dropmarker anonid="dropmarker" type="menu"
                         class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
       </xul:hbox>
     </content>
   </binding>
   
   <binding id="menu-button" display="xul:menu" 
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -153,22 +153,23 @@ toolbarbutton[type="panel"] {
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu");
 }
 
 toolbarbutton[type="menu-button"] {
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu-button");
 }
 
 toolbar[mode="icons"] .toolbarbutton-text,
+toolbar[mode="icons"] .toolbarbutton-multiline-text,
 toolbar[mode="text"] .toolbarbutton-icon {
   display: none;
 }
 
-toolbarbutton:not([wrap="true"]) > .toolbarbutton-multiline-text,
-toolbarbutton[wrap="true"] > .toolbarbutton-text {
+.toolbarbutton-multiline-text:not([wrap="true"]),
+.toolbarbutton-text[wrap="true"] {
   display: none;
 }
 
 /******** browser, editor, iframe ********/
 
 browser,
 editor,
 iframe {
--- a/toolkit/mozapps/update/tests/shared.js
+++ b/toolkit/mozapps/update/tests/shared.js
@@ -53,26 +53,24 @@ const NS_APP_USER_PROFILE_50_DIR   = "Pr
 const NS_GRE_DIR                   = "GreD";
 const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
 const XRE_EXECUTABLE_FILE          = "XREExeF";
 const XRE_UPDATE_ROOT_DIR          = "UpdRootD";
 
 const CRC_ERROR   = 4;
 const WRITE_ERROR = 7;
 
-const DIR_PATCH                      = "0";
-const DIR_UPDATES                    = "updates";
+const DIR_PATCH        = "0";
+const DIR_UPDATES      = "updates";
 #ifdef XP_MACOSX
-const DIR_APP_REL_PATH = "/Contents/MacOS/";
-const DIR_APP_SUFFIX = ".app";
-const DIR_UPDATED = "Updated.app";
+const DIR_BIN_REL_PATH = "Contents/MacOS/";
+const DIR_UPDATED      = "Updated.app";
 #else
-const DIR_APP_REL_PATH = "/appdir/";
-const DIR_APP_SUFFIX = "";
-const DIR_UPDATED = "updated";
+const DIR_BIN_REL_PATH = "";
+const DIR_UPDATED      = "updated";
 #endif
 
 const FILE_BACKUP_LOG                = "backup-update.log";
 const FILE_LAST_LOG                  = "last-update.log";
 const FILE_UPDATER_INI               = "updater.ini";
 const FILE_UPDATES_DB                = "updates.xml";
 const FILE_UPDATE_ACTIVE             = "active-update.xml";
 const FILE_UPDATE_ARCHIVE            = "update.mar";
--- a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
@@ -410,19 +410,16 @@ function setupTestCommon() {
 
   // Don't attempt to show a prompt when an update finishes.
   Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
 
   gGREDirOrig = getGREDir();
   gAppDirOrig = getAppBaseDir();
 
   let applyDir = getApplyDirFile(null, true).parent;
-  if (IS_MACOSX) {
-    applyDir = applyDir.parent;
-  }
 
   // Try to remove the directory used to apply updates and the updates directory
   // on platforms other than Windows. Since the test hasn't ran yet and the
   // directory shouldn't exist finished this is non-fatal for the test.
   if (applyDir.exists()) {
     logTestInfo("attempting to remove directory. Path: " + applyDir.path);
     try {
       removeDirRecursive(applyDir);
@@ -535,19 +532,16 @@ function cleanupTestCommon() {
       } catch (e) {
         logTestInfo("non-fatal error removing directory. Path: " +
                     updatesDir.path + ", Exception: " + e);
       }
     }
   }
 
   let applyDir = getApplyDirFile(null, true).parent;
-  if (IS_MACOSX) {
-    applyDir = applyDir.parent;
-  }
 
   // Try to remove the directory used to apply updates. Since the test has
   // already finished this is non-fatal for the test.
   if (applyDir.exists()) {
     logTestInfo("attempting to remove directory. Path: " + applyDir.path);
     try {
       removeDirRecursive(applyDir);
     } catch (e) {
@@ -669,35 +663,28 @@ function getAppVersion() {
   let iniParser = AUS_Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
                   getService(AUS_Ci.nsIINIParserFactory).
                   createINIParser(iniFile);
   return iniParser.getString("App", "Version");
 }
 
 /**
  * Helper function for getting the relative path to the directory where the
- * application binary is located.
- * For Mac OS X the path will be:
- *   <test_file_leafname>.app/Contents/MacOS/
- * For other platforms the path will be:
- *   <test_file_leafname>/appdir/
+ * application binary is located (e.g. <test_file_leafname>/dir.app/).
  *
- * Note: The appdir subdirectory is needed for platforms other than Mac OS X so
- *       the tests can run in parallel due to update staging creating a lock
- *       file named moz_update_in_progress.lock in the parent directory of the
- *       installation directory.
+ * Note: The dir.app subdirectory under <test_file_leafname> is needed for
+ *       platforms other than Mac OS X so the tests can run in parallel due to
+ *       update staging creating a lock file named moz_update_in_progress.lock in
+ *       the parent directory of the installation directory.
  *
  * @return  The relative path to the directory where application binary is
  *          located.
  */
 function getApplyDirPath() {
-  if (IS_MACOSX) {
-    return gTestID + DIR_APP_SUFFIX + DIR_APP_REL_PATH;
-  }
-  return gTestID + DIR_APP_REL_PATH;
+  return gTestID + "/dir.app/";
 }
 
 /**
  * Helper function for getting the nsIFile for a file in the directory where the
  * update will be applied.
  *
  * The files for the update are located two directories below the apply to
  * directory since Mac OS X sets the last modified time for the root directory
@@ -747,64 +734,38 @@ function getTestDirFile(aRelPath) {
 }
 
 /**
  * Helper function for getting the directory that was updated. This can either
  * be the directory where the application binary is located or the directory
  * that contains the staged update.
  */
 function getUpdatedDirPath() {
-  let updatedDirPath = gTestID;
-  if (IS_MACOSX) {
-    updatedDirPath += DIR_APP_SUFFIX;
-  } else {
-    // The appdir subdirectory is needed so the tests can run in parallel due to
-    // update staging creating a lock file named moz_update_in_progress.lock in
-    // the parent directory of the installation directory.
-    updatedDirPath += DIR_APP_REL_PATH;
-  }
-  if (gStageUpdate) {
-    updatedDirPath += DIR_UPDATED + "/";
-  } else if (IS_MACOSX) {
-    updatedDirPath += DIR_APP_REL_PATH;
-  }
-  return updatedDirPath;
+  return getApplyDirPath() + (gStageUpdate ? DIR_UPDATED +  "/" : "");
 }
 
 /**
  * Helper function for getting the directory where files are added, removed,
  * and modified by the simple.mar update file.
  *
  * @return  nsIFile for the directory where files are added, removed, and
  *          modified by the simple.mar update file.
  */
 function getUpdateTestDir() {
-  let updateTestDir = getApplyDirFile(null, true);
-
-  if (IS_MACOSX) {
-    updateTestDir = updateTestDir.parent.parent;
-  }
-  updateTestDir.append("update_test");
-  return updateTestDir;
+  return getApplyDirFile("update_test", true);
 }
 
 /**
  * Helper function for getting the updating directory which is used by the
  * updater to extract the update manifest and patch files.
  *
  * @return  nsIFile for the directory for the updating directory.
  */
 function getUpdatingDir() {
-  let updatingDir = getApplyDirFile(null, true);
-
-  if (IS_MACOSX) {
-    updatingDir = updatingDir.parent.parent;
-  }
-  updatingDir.append("updating");
-  return updatingDir;
+  return getApplyDirFile("updating", true);
 }
 
 #ifdef XP_WIN
 XPCOMUtils.defineLazyGetter(this, "gInstallDirPathHash",
                             function test_gInstallDirPathHash() {
   // Figure out where we should check for a cached hash value
   if (!MOZ_APP_BASENAME)
     return null;
@@ -936,17 +897,17 @@ function getMockUpdRootD() {
 #else
 /**
  * Helper function for getting the update root directory used by the tests. This
  * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
  * in nsXREDirProvider.cpp so an application will be able to find the update
  * when running a test that launches the application.
  */
 function getMockUpdRootD() {
-  return getApplyDirFile(null, true);
+  return getApplyDirFile(DIR_BIN_REL_PATH, true);
 }
 #endif
 
 /**
  * Helper function for getting the nsIFile for the directory where the update
  * has been applied.
  *
  * This will be the same as getApplyDirFile for foreground updates, but will
@@ -1046,16 +1007,17 @@ function runUpdate(aExpectedExitValue, a
     }
   }
 
   let applyToDir = getApplyDirFile(null, true);
   let applyToDirPath = applyToDir.path;
   if (gStageUpdate || gSwitchApp) {
     applyToDirPath += "/" + DIR_UPDATED + "/";
   }
+
   if (IS_WIN) {
     // Convert to native path
     applyToDirPath = applyToDirPath.replace(/\//g, "\\");
   }
 
   let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
   callbackApp.permissions = PERMS_DIRECTORY;
 
@@ -1336,37 +1298,38 @@ function copyFileToTestAppDir(aFileRelPa
   for (let i = 0; i < pathParts.length; i++) {
     if (pathParts[i]) {
       srcFile.append(pathParts[i]);
     }
   }
 
   if (IS_MACOSX && !srcFile.exists()) {
     logTestInfo("unable to copy file since it doesn't exist! Checking if " +
-                 fileRelPath + DIR_APP_SUFFIX + " exists. Path: " +
+                 fileRelPath + ".app exists. Path: " +
                  srcFile.path);
     srcFile = gGREDirOrig.clone();
     for (let i = 0; i < pathParts.length; i++) {
       if (pathParts[i]) {
-        srcFile.append(pathParts[i] + (pathParts.length - 1 == i ? DIR_APP_SUFFIX : ""));
+        srcFile.append(pathParts[i] + (pathParts.length - 1 == i ? ".app" : ""));
       }
     }
-    fileRelPath = fileRelPath + DIR_APP_SUFFIX;
+    fileRelPath = fileRelPath + ".app";
   }
+
   if (!srcFile.exists()) {
     do_throw("Unable to copy file since it doesn't exist! Path: " +
              srcFile.path);
   }
 
   // Symlink libraries. Note that the XUL library on Mac OS X doesn't have a
   // file extension and this will always be false on Windows.
   let shouldSymlink = (pathParts[pathParts.length - 1] == "XUL" ||
                        fileRelPath.substr(fileRelPath.length - 3) == ".so" ||
                        fileRelPath.substr(fileRelPath.length - 6) == ".dylib");
-  let destFile = getApplyDirFile(fileRelPath, true);
+  let destFile = getApplyDirFile(DIR_BIN_REL_PATH + fileRelPath, true);
   if (!shouldSymlink) {
     if (!destFile.exists()) {
       try {
         srcFile.copyToFollowingLinks(destFile.parent, destFile.leafName);
       } catch (e) {
         // Just in case it is partially copied
         if (destFile.exists()) {
           try {
@@ -2586,17 +2549,17 @@ function createAppInfo(aID, aName, aVers
  * and stderr to null. This is needed to prevent output from the application
  * from ending up in the xpchsell log.
  */
 function getProcessArgs(aExtraArgs) {
   if (!aExtraArgs) {
     aExtraArgs = [];
   }
 
-  let appBinPath = getApplyDirFile(FILE_APP_BIN, false).path;
+  let appBinPath = getApplyDirFile(DIR_BIN_REL_PATH + FILE_APP_BIN, false).path;
   if (/ /.test(appBinPath)) {
     appBinPath = '"' + appBinPath + '"';
   }
 
   let args;
   if (IS_UNIX) {
     let launchScript = getLaunchScript();
     // Precreate the script with executable permissions
@@ -2656,22 +2619,22 @@ function getLaunchScript() {
  */
 function adjustGeneralPaths() {
   let dirProvider = {
     getFile: function AGP_DP_getFile(aProp, aPersistent) {
       aPersistent.value = true;
       switch (aProp) {
         case NS_GRE_DIR:
           if (gUseTestAppDir) {
-            return getApplyDirFile(null, true);
+            return getApplyDirFile(DIR_BIN_REL_PATH, true);
           }
           break;
         case XRE_EXECUTABLE_FILE:
           if (gUseTestAppDir) {
-            return getApplyDirFile(FILE_APP_BIN, true);
+            return getApplyDirFile(DIR_BIN_REL_PATH + FILE_APP_BIN, true);
           }
           break;
         case XRE_UPDATE_ROOT_DIR:
           return getMockUpdRootD();
       }
       return null;
     },
     QueryInterface: function(aIID) {
@@ -2739,17 +2702,17 @@ function adjustGeneralPaths() {
 
 
 /**
  * Helper function for launching the application to apply an update.
  */
 function launchAppToApplyUpdate() {
   logTestInfo("start - launching application to apply update");
 
-  let appBin = getApplyDirFile(FILE_APP_BIN, false);
+  let appBin = getApplyDirFile(DIR_BIN_REL_PATH + FILE_APP_BIN, false);
 
   if (typeof(customLaunchAppToApplyUpdate) == typeof(Function)) {
     customLaunchAppToApplyUpdate();
   }
 
   let launchBin = getLaunchBin();
   let args = getProcessArgs();
   logTestInfo("launching " + launchBin.path + " " + args.join(" "));
--- a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
@@ -237,16 +237,18 @@ function runHelperProcess(args) {
   process.run(true, args, args.length);
   do_check_eq(process.exitValue, 0);
 }
 
 function createSymlink() {
   let args = ["setup-symlink", "moz-foo", "moz-bar", "target",
               getApplyDirFile().path + "/a/b/link"];
   runHelperProcess(args);
+  getApplyDirFile("a/b/link", false).permissions = 0o666;
+  
   args = ["setup-symlink", "moz-foo2", "moz-bar2", "target2",
           getApplyDirFile().path + "/a/b/link2", "change-perm"];
   runHelperProcess(args);
 }
 
 function removeSymlink() {
   let args = ["remove-symlink", "moz-foo", "moz-bar", "target",
               getApplyDirFile().path + "/a/b/link"];
@@ -283,18 +285,18 @@ function run_test() {
     TEST_FILES.push({
       description      : "Readable symlink",
       fileName         : "link",
       relPathDir       : "a/b/",
       originalContents : "test",
       compareContents  : "test",
       originalFile     : null,
       compareFile      : null,
-      originalPerms    : 0o664,
-      comparePerms     : 0o664
+      originalPerms    : 0o666,
+      comparePerms     : 0o666
     });
   }
 
   runUpdate(0, STATE_APPLIED, null);
 
   if (IS_MACOSX) {
     logTestInfo("testing last modified time on the apply to directory has " +
                 "changed after a successful update (bug 600098)");
--- a/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
@@ -10,37 +10,36 @@
 head = head_update.js
 tail =
 generated-files = head_update.js
 
 [marSuccessComplete.js]
 [marSuccessPartial.js]
 [marFailurePartial.js]
 [marStageSuccessComplete.js]
-skip-if = os == 'mac' || toolkit == 'gonk'
+skip-if = toolkit == 'gonk'
 reason = bug 820380
 [marStageSuccessPartial.js]
-skip-if = os == 'mac'
 [marVersionDowngrade.js]
 run-if = os == 'win'
 [marWrongChannel.js]
 run-if = os == 'win'
 [marStageFailurePartial.js]
 [marCallbackAppSuccessComplete_win.js]
 run-if = os == 'win'
 [marCallbackAppSuccessPartial_win.js]
 run-if = os == 'win'
 [marCallbackAppStageSuccessComplete_win.js]
 run-if = os == 'win'
 [marCallbackAppStageSuccessPartial_win.js]
 run-if = os == 'win'
 [marAppInUseSuccessComplete.js]
 skip-if = toolkit == 'gonk'
 [marAppInUseStageSuccessComplete_unix.js]
-run-if = os == 'linux' || os == 'sunos'
+run-if = os == 'linux' || os == 'sunos' || os == 'mac'
 [marAppInUseStageFailureComplete_win.js]
 run-if = os == 'win'
 [marAppInUseFallbackStageFailureComplete_win.js]
 run-if = os == 'win'
 [marFileLockedFailureComplete_win.js]
 run-if = os == 'win'
 [marFileLockedFailurePartial_win.js]
 run-if = os == 'win'
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -272,18 +272,21 @@ extern "C" {
     __attribute__ ((visibility("default")))
     JNIEnv * GetJNIForThread()
     {
         JNIEnv *jEnv = static_cast<JNIEnv*>(PR_GetThreadPrivate(sJavaEnvThreadIndex));
         if (jEnv) {
             return jEnv;
         }
         JavaVM *jVm  = mozilla::AndroidBridge::GetVM();
-        if (!jVm->GetEnv(reinterpret_cast<void**>(&jEnv), JNI_VERSION_1_2) ||
-            !jVm->AttachCurrentThread(&jEnv, nullptr)) {
+        if (!jVm->GetEnv(reinterpret_cast<void**>(&jEnv), JNI_VERSION_1_2)) {
+            MOZ_ASSERT(jEnv);
+            return jEnv;
+        }
+        if (!jVm->AttachCurrentThread(&jEnv, nullptr)) {
             MOZ_ASSERT(jEnv);
             PR_SetThreadPrivate(sJavaEnvThreadIndex, jEnv);
             return jEnv;
         }
         MOZ_CRASH();
         return nullptr; // unreachable
     }
 }
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -714,17 +714,17 @@ jstring GeckoAppShell::HandleGeckoMessag
 
     jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jHandleGeckoMessageWrapper, j0);
     AndroidBridge::HandleUncaughtException(env);
     jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
     return ret;
 }
 
 void GeckoAppShell::HandleUncaughtException(jobject a0, jthrowable a1) {
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
+    JNIEnv *env = GetJNIForThread();
     if (env->PushLocalFrame(2) != 0) {
         return;
     }
 
     env->CallStaticVoidMethod(mGeckoAppShellClass, jHandleUncaughtException, a0, a1);
     env->PopLocalFrame(nullptr);
 }
 
--- a/widget/gonk/nsAppShell.cpp
+++ b/widget/gonk/nsAppShell.cpp
@@ -204,17 +204,17 @@ sendMouseEvent(uint32_t msg, UserInputDa
 }
 
 static void
 addDOMTouch(UserInputData& data, WidgetTouchEvent& event, int i)
 {
     const ::Touch& touch = data.motion.touches[i];
     event.touches.AppendElement(
         new dom::Touch(touch.id,
-                       nsIntPoint(touch.coords.getX(), touch.coords.getY()),
+                       nsIntPoint(floor(touch.coords.getX() + 0.5), floor(touch.coords.getY() + 0.5)),
                        nsIntPoint(touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE),
                                   touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE)),
                        0,
                        touch.coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE))
     );
 }
 
 static nsEventStatus
--- a/widget/windows/nsDeviceContextSpecWin.cpp
+++ b/widget/windows/nsDeviceContextSpecWin.cpp
@@ -176,199 +176,16 @@ nsDeviceContextSpecWin::~nsDeviceContext
 // helper
 static char16_t * GetDefaultPrinterNameFromGlobalPrinters()
 {
   nsAutoString printerName;
   GlobalPrinters::GetInstance()->GetDefaultPrinterName(printerName);
   return ToNewUnicode(printerName);
 }
 
-//----------------------------------------------------------------
-static nsresult 
-EnumerateNativePrinters(DWORD aWhichPrinters, const wchar_t *aPrinterName, bool& aIsFound, bool& aIsFile)
-{
-  DWORD             dwSizeNeeded = 0;
-  DWORD             dwNumItems   = 0;
-  LPPRINTER_INFO_2W  lpInfo        = nullptr;
-
-  // Get buffer size
-  if (::EnumPrintersW(aWhichPrinters, nullptr, 2, nullptr, 0, &dwSizeNeeded,
-                      &dwNumItems)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // allocate memory
-  lpInfo = (LPPRINTER_INFO_2W) malloc(dwSizeNeeded);
-  if (!lpInfo) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (::EnumPrintersW(PRINTER_ENUM_LOCAL, nullptr, 2, (LPBYTE)lpInfo,
-                      dwSizeNeeded, &dwSizeNeeded, &dwNumItems) == 0) {
-    free(lpInfo);
-    return NS_OK;
-  }
-
-  for (DWORD i = 0; i < dwNumItems; i++ ) {
-    if (wcscmp(lpInfo[i].pPrinterName, aPrinterName) == 0) {
-      aIsFound = true;
-      aIsFile  = wcscmp(lpInfo[i].pPortName, L"FILE:") == 0;
-      break;
-    }
-  }
-
-  free(lpInfo);
-  return NS_OK;
-}
-
-//----------------------------------------------------------------
-static void 
-CheckForPrintToFileWithName(char16ptr_t aPrinterName, bool& aIsFile)
-{
-  bool isFound = false;
-  aIsFile = false;
-  nsresult rv = EnumerateNativePrinters(PRINTER_ENUM_LOCAL, aPrinterName, isFound, aIsFile);
-  if (isFound) return;
-
-  rv = EnumerateNativePrinters(PRINTER_ENUM_NETWORK, aPrinterName, isFound, aIsFile);
-  if (isFound) return;
-
-  rv = EnumerateNativePrinters(PRINTER_ENUM_SHARED, aPrinterName, isFound, aIsFile);
-  if (isFound) return;
-
-  rv = EnumerateNativePrinters(PRINTER_ENUM_REMOTE, aPrinterName, isFound, aIsFile);
-  if (isFound) return;
-}
-
-static nsresult 
-GetFileNameForPrintSettings(nsIPrintSettings* aPS)
-{
-  // for testing
-#ifdef DEBUG_rods
-  return NS_OK;
-#endif
-
-  nsresult rv;
-
-  nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIStringBundleService> bundleService =
-    mozilla::services::GetStringBundleService();
-  if (!bundleService)
-    return NS_ERROR_FAILURE;
-  nsCOMPtr<nsIStringBundle> bundle;
-  rv = bundleService->CreateBundle(NS_ERROR_GFX_PRINTER_BUNDLE_URL, getter_AddRefs(bundle));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsXPIDLString title;
-  rv = bundle->GetStringFromName(MOZ_UTF16("PrintToFile"), getter_Copies(title));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIWindowWatcher> wwatch =
-    (do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIDOMWindow> window;
-  wwatch->GetActiveWindow(getter_AddRefs(window));
-
-  rv = filePicker->Init(window, title, nsIFilePicker::modeSave);
-  NS_ENSURE_SUCCESS(rv, rv);
- 
-  rv = filePicker->AppendFilters(nsIFilePicker::filterAll);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  char16_t* fileName;
-  aPS->GetToFileName(&fileName);
-
-  if (fileName) {
-    if (*fileName) {
-      nsAutoString leafName;
-      nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
-      if (file) {
-        rv = file->InitWithPath(nsDependentString(fileName));
-        if (NS_SUCCEEDED(rv)) {
-          file->GetLeafName(leafName);
-          filePicker->SetDisplayDirectory(file);
-        }
-      }
-      if (!leafName.IsEmpty()) {
-        rv = filePicker->SetDefaultString(leafName);
-      }
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    nsMemory::Free(fileName);
-  }
-
-  int16_t dialogResult;
-  filePicker->Show(&dialogResult);
-
-  if (dialogResult == nsIFilePicker::returnCancel) {
-    return NS_ERROR_ABORT;
-  }
-
-  nsCOMPtr<nsIFile> localFile;
-  rv = filePicker->GetFile(getter_AddRefs(localFile));
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  if (dialogResult == nsIFilePicker::returnReplace) {
-    // be extra safe and only delete when the file is really a file
-    bool isFile;
-    rv = localFile->IsFile(&isFile);
-    if (NS_SUCCEEDED(rv) && isFile) {
-      rv = localFile->Remove(false /* recursive delete */);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-  }
-
-  nsAutoString unicodePath;
-  rv = localFile->GetPath(unicodePath);
-  NS_ENSURE_SUCCESS(rv,rv);
-
-  if (unicodePath.IsEmpty()) {
-    rv = NS_ERROR_ABORT;
-  }
-
-  if (NS_SUCCEEDED(rv)) aPS->SetToFileName(unicodePath.get());
-
-  return rv;
-}
-
-//----------------------------------------------------------------------------------
-static nsresult
-CheckForPrintToFile(nsIPrintSettings* aPS, const char16_t* aPrinterName, const char16_t* aUPrinterName)
-{
-  nsresult rv = NS_OK;
-
-  if (!aPrinterName && !aUPrinterName) return rv;
-
-  bool toFile;
-  CheckForPrintToFileWithName(aPrinterName?aPrinterName:aUPrinterName, toFile);
-  // Since the driver wasn't a "Print To File" Driver, check to see
-  // if the name of the file has been set to the special "FILE:"
-  if (!toFile) {
-    nsXPIDLString toFileName;
-    aPS->GetToFileName(getter_Copies(toFileName));
-    if (toFileName) {
-      if (*toFileName) {
-        if (toFileName.EqualsLiteral("FILE:")) {
-          // this skips the setting of the "print to file" info below
-          // which we don't want to do.
-          return NS_OK; 
-        }
-      }
-    }
-  }
-  aPS->SetPrintToFile(toFile);
-  if (toFile) {
-    rv = GetFileNameForPrintSettings(aPS);
-  }
-  return rv;
-}
-
 //----------------------------------------------------------------------------------
 NS_IMETHODIMP nsDeviceContextSpecWin::Init(nsIWidget* aWidget,
                                            nsIPrintSettings* aPrintSettings,
                                            bool aIsPrintPreview)
 {
   mPrintSettings = aPrintSettings;
 
   nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
@@ -393,25 +210,16 @@ NS_IMETHODIMP nsDeviceContextSpecWin::In
             devMode->dmScale = 100;
           }
         }
 
         SetDeviceName(deviceName);
         SetDriverName(driverName);
         SetDevMode(devMode);
 
-        if (!aIsPrintPreview) {
-          rv = CheckForPrintToFile(mPrintSettings, deviceName, nullptr);
-          if (NS_FAILED(rv)) {
-            free(deviceName);
-            free(driverName);
-            return NS_ERROR_FAILURE;
-          }
-        }
-
         // clean up
         free(deviceName);
         free(driverName);
 
         return NS_OK;
       } else {
         PR_PL(("***** nsDeviceContextSpecWin::Init - deviceName/driverName/devMode was NULL!\n"));
         if (deviceName) free(deviceName);
@@ -431,20 +239,16 @@ NS_IMETHODIMP nsDeviceContextSpecWin::In
 
   // If there is no name then use the default printer
   if (!printerName || (printerName && !*printerName)) {
     printerName = GetDefaultPrinterNameFromGlobalPrinters();
   }
 
   NS_ASSERTION(printerName, "We have to have a printer name");
   if (!printerName || !*printerName) return rv;
-
-  if (!aIsPrintPreview) {
-    CheckForPrintToFile(mPrintSettings, nullptr, printerName);
-  }
  
   return GetDataFromPrinter(printerName, mPrintSettings);
 }
 
 //----------------------------------------------------------
 // Helper Function - Free and reallocate the string
 static void CleanAndCopyString(wchar_t*& aStr, const wchar_t* aNewStr)
 {
--- a/widget/xpwidgets/APZCCallbackHelper.cpp
+++ b/widget/xpwidgets/APZCCallbackHelper.cpp
@@ -87,26 +87,29 @@ MaybeAlignAndClampDisplayPort(mozilla::l
 
 static CSSPoint
 ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint)
 {
   if (!aFrame) {
     return CSSPoint();
   }
 
-  // If the scrollable frame got a scroll request from something other than us
+  // If the scrollable frame is currently in the middle of an async or smooth
+  // scroll then we don't want to interrupt it (see bug 961280).
+  // Also if the scrollable frame got a scroll request from something other than us
   // since the last layers update, then we don't want to push our scroll request
   // because we'll clobber that one, which is bad.
   // Note that content may have just finished sending a layers update with a scroll
   // offset update to the APZ, in which case the origin will be reset to null and we
   // might actually be clobbering the content-side scroll offset with a stale APZ
   // scroll offset. This is unavoidable because of the async communication between
   // APZ and content; however the code in NotifyLayersUpdated should reissue a new
   // repaint request to bring everything back into sync.
-  if (!aFrame->OriginOfLastScroll() || aFrame->OriginOfLastScroll() == nsGkAtoms::apz) {
+  if (!aFrame->IsProcessingAsyncScroll() &&
+     (!aFrame->OriginOfLastScroll() || aFrame->OriginOfLastScroll() == nsGkAtoms::apz)) {
     aFrame->ScrollToCSSPixelsApproximate(aPoint, nsGkAtoms::apz);
   }
   // Return the final scroll position after setting it so that anything that relies
   // on it can have an accurate value. Note that even if we set it above re-querying it
   // is a good idea because it may have gotten clamped or rounded.
   return CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
 }
 
--- a/xpcom/components/ManifestParser.cpp
+++ b/xpcom/components/ManifestParser.cpp
@@ -446,22 +446,25 @@ ParseManifest(NSLocationType type, FileL
         abi.Insert(char16_t('_'), 0);
         abi.Insert(osTarget, 0);
       }
     }
   }
 
   nsAutoString osVersion;
 #if defined(XP_WIN)
+#pragma warning(push)
+#pragma warning(disable:4996) // VC12+ deprecates GetVersionEx
   OSVERSIONINFO info = { sizeof(OSVERSIONINFO) };
   if (GetVersionEx(&info)) {
     nsTextFormatter::ssprintf(osVersion, MOZ_UTF16("%ld.%ld"),
                                          info.dwMajorVersion,
                                          info.dwMinorVersion);
   }
+#pragma warning(pop)
 #elif defined(MOZ_WIDGET_COCOA)
   SInt32 majorVersion = nsCocoaFeatures::OSXVersionMajor();
   SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor();
   nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(),
                                        majorVersion,
                                        minorVersion);
 #elif defined(MOZ_WIDGET_GTK)
   nsTextFormatter::ssprintf(osVersion, MOZ_UTF16("%ld.%ld"),