Merge m-c to b2g-inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 22 Jan 2014 15:56:42 -0500
changeset 180819 ee87d9380ff1d644dd816486b9f1f8b806105b85
parent 180818 f449298d7a2553406f8eb4dc79f23e4ad54e2886 (current diff)
parent 180734 cad591993bfa410da4954c2f9e8617818b6a216d (diff)
child 180820 47c7cd88eee195d5e91693bcebe70ec504d4044b
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 m-c to b2g-inbound.
accessible/tests/mochitest/jsat/test_braille.html
accessible/tests/mochitest/jsat/test_utterance_order.html
browser/metro/base/content/config.js
browser/metro/base/content/config.xul
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,10 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-CLOBBER due to recent changes to JS build files that were causing
-"STOP! configure has changed and needs to be run in this build directory." bustage
+JS build system changes are apparently requiring clobbers.
--- a/accessible/src/jsat/OutputGenerator.jsm
+++ b/accessible/src/jsat/OutputGenerator.jsm
@@ -673,44 +673,57 @@ this.UtteranceGenerator = {
 
 this.BrailleGenerator = {
   __proto__: OutputGenerator,
 
   genForContext: function genForContext(aContext) {
     let output = OutputGenerator.genForContext.apply(this, arguments);
 
     let acc = aContext.accessible;
+
+    // add the static text indicating a list item; do this for both listitems or
+    // direct first children of listitems, because these are both common browsing
+    // scenarios
+    let addListitemIndicator = function addListitemIndicator(indicator = '*') {
+      output.output.unshift(indicator);
+    };
+
+    if (acc.indexInParent === 1 &&
+        acc.parent.role == Roles.LISTITEM &&
+        acc.previousSibling.role == Roles.STATICTEXT) {
+      if (acc.parent.parent && acc.parent.parent.DOMNode &&
+          acc.parent.parent.DOMNode.nodeName == 'UL') {
+        addListitemIndicator();
+      } else {
+        addListitemIndicator(acc.previousSibling.name.trim());
+      }
+    } else if (acc.role == Roles.LISTITEM && acc.firstChild &&
+               acc.firstChild.role == Roles.STATICTEXT) {
+      if (acc.parent.DOMNode.nodeName == 'UL') {
+        addListitemIndicator();
+      } else {
+        addListitemIndicator(acc.firstChild.name.trim());
+      }
+    }
+
     if (acc instanceof Ci.nsIAccessibleText) {
       output.endOffset = this.outputOrder === OUTPUT_DESC_FIRST ?
                          output.output.join(' ').length : acc.characterCount;
       output.startOffset = output.endOffset - acc.characterCount;
     }
 
     return output;
   },
 
   objectOutputFunctions: {
 
     __proto__: OutputGenerator.objectOutputFunctions,
 
     defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
-      let braille = this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
-
-      if (aAccessible.indexInParent === 1 &&
-          aAccessible.parent.role == Roles.LISTITEM &&
-          aAccessible.previousSibling.role == Roles.STATICTEXT) {
-        if (aAccessible.parent.parent && aAccessible.parent.parent.DOMNode &&
-            aAccessible.parent.parent.DOMNode.nodeName == 'UL') {
-          braille.unshift('*');
-        } else {
-          braille.unshift(aAccessible.previousSibling.name);
-        }
-      }
-
-      return braille;
+      return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
     },
 
     listitem: function listitem(aAccessible, aRoleStr, aStates, aFlags) {
       let braille = [];
 
       this._addName(braille, aAccessible, aFlags);
       this._addLandmark(braille, aAccessible);
 
--- a/accessible/tests/mochitest/jsat/a11y.ini
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -1,14 +1,13 @@
 [DEFAULT]
 support-files =
   jsatcommon.js
   output.js
   doc_traversal.html
 
 [test_alive.html]
-[test_braille.html]
 [test_explicit_names.html]
 [test_landmarks.html]
 [test_live_regions.html]
+[test_output.html]
 [test_tables.html]
 [test_traversal.html]
-[test_utterance_order.html]
deleted file mode 100644
--- a/accessible/tests/mochitest/jsat/test_braille.html
+++ /dev/null
@@ -1,122 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=876475
--->
-  <head>
-    <title>[AccessFu] braille generation test</title>
-    <meta charset="utf-8">
-    <link rel="stylesheet" type="text/css"
-          href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-    <script type="application/javascript"
-            src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-    <script type="application/javascript"
-            src="../common.js"></script>
-    <script type="application/javascript"
-            src="./output.js"></script>
-    <script type="application/javascript">
-
-      function doTest() {
-        // Test the following accOrElmOrID (with optional old accOrElmOrID).
-        // Note: each accOrElmOrID entry maps to a unique object braille
-        // generator function within the BrailleGenerator.
-        var tests = [{
-          accOrElmOrID: "link",
-          expected: [["lnk", "Link"], ["Link", "lnk"]]
-        },{
-          accOrElmOrID: "button",
-          expected: [["btn", "I am a button"], ["I am a button", "btn"]]
-        },{
-          accOrElmOrID: "password_input",
-          expected: [["passwdtxt", "Secret Password"], ["Secret Password", "passwdtxt"]]
-        },{
-          accOrElmOrID: "checkbox_unchecked",
-          expected: [["( )", "checkboxtext"], ["checkboxtext", "( )"]]
-        },{
-          accOrElmOrID: "checkbox_checked",
-          expected: [["(x)", "some more checkbox text"], ["some more checkbox text", "(x)"]]
-        },{
-          accOrElmOrID: "radio_unselected",
-          expected: [["( )", "any old radio button"], ["any old radio button", "( )"]]
-        },{
-          accOrElmOrID: "radio_selected",
-          expected: [["(x)", "a unique radio button"], ["a unique radio button", "(x)"]]
-        },{
-          accOrElmOrID: "togglebutton_notpressed",
-          expected: [["( )", "I ain't pressed"], ["I ain't pressed", "( )"]]
-        },{
-          accOrElmOrID: "togglebutton_pressed",
-          expected: [["(x)", "I am pressed!"], ["I am pressed!", "(x)"]]
-        },{
-          accOrElmOrID: "ul_li_one",
-          expected: [["*", "ul item 1"], ["*", "ul item 1"]]
-        },{
-          accOrElmOrID: "ol_li_one",
-          expected: [["1.", "ol item 1"], ["1.", "ol item 1"]]
-        },{
-          accOrElmOrID: "textarea",
-          expected: [["txtarea", "Here lies treasure."], ["Here lies treasure.", "txtarea"]]
-        },{
-          accOrElmOrID: "textentry",
-          expected: [["entry", "Mario", "First name:"], ["First name:", "Mario", "entry"]]
-        },{
-          accOrElmOrID: "range",
-          expected: [["slider", "3", "Points:"], ["Points:", "3", "slider"]]
-        }];
-
-        // Test all possible braille order preference values.
-        tests.forEach(function run(test) {
-          var brailleOrderValues = [0, 1];
-          brailleOrderValues.forEach(
-            function testBrailleOrder(brailleOrder) {
-              SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, brailleOrder);
-              var expected = test.expected[brailleOrder];
-              testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 2);
-            }
-          );
-        });
-
-        // If there was an original utterance order preference, revert to it.
-        SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER);
-        SimpleTest.finish();
-      }
-
-      SimpleTest.waitForExplicitFinish();
-      addA11yLoadEvent(doTest);
-
-    </script>
-  </head>
-  <body>
-    <div id="root">
-      <p id="display"></p>
-      <div id="content" style="display: none"></div>
-      <pre id="test"></pre>
-      <a href="example.com" id="link">Link</a>
-      <button id="button">I am a button</button>
-      <label for="password_input">Secret Password</label><input id="password_input" type="password"></input>
-      <label for="checkbox_unchecked">checkboxtext</label><input id="checkbox_unchecked" type="checkbox"></input>
-      <label for="checkbox_checked">some more checkbox text</label><input id="checkbox_checked" type="checkbox" checked></input>
-      <label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input>
-      <label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input>
-      <div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I ain't pressed</div>
-      <div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div>
-      <ol id="ordered_list">
-        <li id="ol_li_one">ol item 1</li>
-        <li id="ol_li_two">ol item 2</li>
-        <li id="ol_li_three">ol item 3</li>
-        <li id="ol_li_three">ol item 4</li>
-      </ol>
-      <ul id="unordered_list">
-        <li id="ul_li_one">ul item 1</li>
-        <li id="ul_li_two">ul item 2</li>
-        <li id="ul_li_three">ul item 3</li>
-        <li id="ul_li_three">ul item 4</li>
-      </ul>
-      <textarea id="textarea" cols="80" rows="5">
-        Here lies treasure.
-      </textarea>
-      <label>First name: <input id="textentry" value="Mario"></label>
-      <label>Points: <input id="range" type="range" name="points" min="1" max="10" value="3"></label>
-   </div>
-  </body>
-</html>
--- a/accessible/tests/mochitest/jsat/test_landmarks.html
+++ b/accessible/tests/mochitest/jsat/test_landmarks.html
@@ -70,19 +70,17 @@
           "another main area", "main"]],
         expectedBraille: [["main", "another main area"], ["another main area",
           "main"]]
       }, {
         accOrElmOrID: "complementary",
         expectedUtterance: [["list 1 item", "complementary", "First item",
           "A complementary"], ["A complementary", "First item",
           "complementary", "list 1 item"]],
-        // XXX: The '*' should probably come before all of the context
-        // utterance.
-        expectedBraille: [["complementary", "*", "A complementary"], ["*",
+        expectedBraille: [["*", "complementary", "A complementary"], ["*",
           "A complementary", "complementary"]]
       }, {
         accOrElmOrID: "parent_main",
         expectedUtterance: [["main", "a parent main", "complementary",
           "a child complementary"], ["a parent main", "a child complementary",
           "complementary", "main"]],
         expectedBraille: [["main", "a parent main", "complementary",
           "a child complementary"], ["a parent main", "a child complementary",
@@ -146,9 +144,9 @@
       </li>
     </ul>
     <main id="parent_main">
       a parent main
       <p id="child_complementary" role="complementary">a child complementary</article>
     </main>
   </div>
 </body>
-</html>
\ No newline at end of file
+</html>
rename from accessible/tests/mochitest/jsat/test_utterance_order.html
rename to accessible/tests/mochitest/jsat/test_output.html
--- a/accessible/tests/mochitest/jsat/test_utterance_order.html
+++ b/accessible/tests/mochitest/jsat/test_output.html
@@ -17,222 +17,346 @@ https://bugzilla.mozilla.org/show_bug.cg
     <script type="application/javascript">
 
       function doTest() {
         // Test the following accOrElmOrID (with optional old accOrElmOrID).
         // Note: each accOrElmOrID entry maps to a unique object utterance
         // generator function within the UtteranceGenerator.
         var tests = [{
           accOrElmOrID: "anchor",
-          expected: [["link", "title"], ["title", "link"]]
+          expectedUtterance: [["link", "title"], ["title", "link"]],
+          expectedBraille: [["lnk", "title"], ["title", "lnk"]]
         }, {
           accOrElmOrID: "anchor_titleandtext",
-          expected: [[
-            "link", "goes to the tests -", "Tests"
-          ], [
-            "Tests", "- goes to the tests", "link"
-          ]]
+          expectedUtterance: [["link", "goes to the tests -", "Tests"],
+                             ["Tests", "- goes to the tests", "link"]],
+          expectedBraille:   [["lnk", "goes to the tests -", "Tests"],
+                             ["Tests", "- goes to the tests", "lnk"]],
         }, {
           accOrElmOrID: "anchor_duplicatedtitleandtext",
-          expected: [["link", "Tests"], ["Tests", "link"]]
+          expectedUtterance: [["link", "Tests"], ["Tests", "link"]],
+          expectedBraille: [["lnk", "Tests"], ["Tests", "lnk"]]
         }, {
           accOrElmOrID: "anchor_arialabelandtext",
-          expected: [[
-            "link", "goes to the tests - Tests"
-          ], [
-            "Tests - goes to the tests", "link"
-          ]]
+          expectedUtterance: [["link", "goes to the tests - Tests"],
+                              ["Tests - goes to the tests", "link"]],
+          expectedBraille: [["lnk", "goes to the tests - Tests"],
+                              ["Tests - goes to the tests", "lnk"]],
         }, {
           accOrElmOrID: "textarea",
-          expected: [[
+          expectedUtterance: [[
             "text area", "This is the text area text."
           ], [
             "This is the text area text.", "text area"
-          ]]
+          ],],
+          expectedBraille: [[
+            "txtarea", "This is the text area text."
+          ], [
+            "This is the text area text.", "txtarea"
+          ],],
         }, {
           accOrElmOrID: "heading",
-          expected: [
+          expectedUtterance: [
             ["heading level 1", "Test heading"],
             ["Test heading", "heading level 1"]
+          ],
+          expectedBraille: [
+            ["heading", "Test heading"],
+            ["Test heading", "heading"]
           ]
         }, {
           accOrElmOrID: "list",
-          expected: [
+          expectedUtterance: [
             ["list 1 item", "First item", "1.", "list one"],
             ["1.", "list one", "First item", "list 1 item"]
+          ],
+          expectedBraille: [
+            ["list", "list one"],
+            ["list one", "list"]
           ]
         }, {
           accOrElmOrID: "dlist",
-          expected: [
+          expectedUtterance: [
             ["definition list 0.5 items", "dd one"],
             ["dd one", "definition list 0.5 items"]
+          ],
+          expectedBraille: [
+            ["definition list", "dd one"],
+            ["dd one", "definition list"]
           ]
         }, {
           accOrElmOrID: "li_one",
-          expected: [
+          expectedUtterance: [
             ["list 1 item", "First item", "1.", "list one"],
             ["1.", "list one", "First item", "list 1 item"]
+          ],
+          expectedBraille: [
+            ["1.", "list one"],
+            ["1.", "list one"]
           ]
         }, {
           accOrElmOrID: "cell",
-          expected: [[
+          expectedUtterance: [[
             "table with 1 column and 1 row", "Fruits and vegetables",
             "Column 1 Row 1", "list 4 items", "First item", "link", "Apples",
             "link", "Bananas", "link", "Peaches", "Last item", "link", "Plums"
           ], [
             "Apples", "link", "First item", "Bananas", "link", "Peaches",
             "link", "Plums", "link", "Last item", "list 4 items",
             "Column 1 Row 1", "Fruits and vegetables",
             "table with 1 column and 1 row"
+          ]],
+          expectedBraille: [[
+            "c1r1", "list", "lnk", "Apples", "lnk", "Bananas", "lnk",
+            "Peaches", "lnk", "Plums"
+          ], [
+            "Apples", "lnk", "Bananas", "lnk", "Peaches", "lnk", "Plums",
+            "lnk", "list", "c1r1"
           ]]
         }, {
           accOrElmOrID: "date",
-          expected: [["date entry", "2011-09-29"], ["2011-09-29", "date entry"]]
+          expectedUtterance: [["date entry", "2011-09-29"], ["2011-09-29", "date entry"]],
+          expectedBraille: [["date entry", "2011-09-29"], ["2011-09-29", "date entry"]]
         }, {
           accOrElmOrID: "email",
-          expected: [
+          expectedUtterance: [
+            ["e-mail entry", "test@example.com"],
+            ["test@example.com", "e-mail entry"]
+          ],
+          expectedBraille: [
             ["e-mail entry", "test@example.com"],
             ["test@example.com", "e-mail entry"]
           ]
         }, {
           accOrElmOrID: "search",
-          expected: [
+          expectedUtterance: [
+            ["search entry", "This is a search"],
+            ["This is a search", "search entry"]
+          ],
+          expectedBraille: [
             ["search entry", "This is a search"],
             ["This is a search", "search entry"]
           ]
         }, {
           accOrElmOrID: "tel",
-          expected: [
+          expectedUtterance: [
+            ["telephone entry", "555-5555"], ["555-5555", "telephone entry"]
+          ],
+          expectedBraille: [
             ["telephone entry", "555-5555"], ["555-5555", "telephone entry"]
           ]
         }, {
           accOrElmOrID: "url",
-          expected: [
+          expectedUtterance: [
+            ["URL entry", "http://example.com"],
+            ["http://example.com", "URL entry"]
+          ],
+          expectedBraille: [
             ["URL entry", "http://example.com"],
             ["http://example.com", "URL entry"]
           ]
         }, {
           accOrElmOrID: "textInput",
-          expected: [["entry", "This is text."], ["This is text.", "entry"]]
+          expectedUtterance: [["entry", "This is text."], ["This is text.", "entry"]],
+          expectedBraille: [["entry", "This is text."], ["This is text.", "entry"]]
         }, {
           // Test pivot to list from li_one.
           accOrElmOrID: "list",
           oldAccOrElmOrID: "li_one",
-          expected: [
+          expectedUtterance: [
             ["list 1 item", "First item", "1.", "list one"],
             ["1.", "list one", "First item", "list 1 item"]
+          ],
+          expectedBraille: [
+            ["list", "list one"],
+            ["list one", "list"]
           ]
         }, {
           // Test pivot to "apples" link from the table cell.
           accOrElmOrID: "apples",
           oldAccOrElmOrID: "cell",
-          expected: [
+          expectedUtterance: [
             ["list 4 items", "First item", "link", "Apples"],
             ["Apples", "link", "First item", "list 4 items"]
+          ],
+          expectedBraille: [
+            ["*", "lnk", "Apples"],
+            ["*", "Apples", "lnk"]
           ]
         }, {
           // Test pivot to 'bananas' link from 'apples' link.
           accOrElmOrID: "bananas",
           oldAccOrElmOrID: "apples",
-          expected: [["link", "Bananas"], ["Bananas", "link"]]
+          expectedUtterance: [["link", "Bananas"], ["Bananas", "link"]],
+          expectedBraille: [["*", "lnk", "Bananas"], ["*", "Bananas", "lnk"]]
         }, {
           // test unavailable state utterance
           accOrElmOrID: 'unavailableButton',
-          expected: [["unavailable button", "I am unavailable"],
-            ["I am unavailable", "unavailable button"]]
+          expectedUtterance: [["unavailable button", "I am unavailable"],
+            ["I am unavailable", "unavailable button"]],
+          expectedBraille: [["btn", "I am unavailable"],
+            ["I am unavailable", "btn"]]
         }, {
           // test expanded state utterance
           accOrElmOrID: 'expandedButton',
-          expected: [["expanded button", "I am expanded"],
-            ["I am expanded", "expanded button"]]
+          expectedUtterance: [["expanded button", "I am expanded"],
+            ["I am expanded", "expanded button"]],
+          expectedBraille: [["btn", "I am expanded"],
+            ["I am expanded", "btn"]]
         }, {
           // test collapsed state utterance
           accOrElmOrID: 'collapsedButton',
-          expected: [["collapsed button", "I am collapsed"],
-            ["I am collapsed", "collapsed button"]]
+          expectedUtterance: [["collapsed button", "I am collapsed"],
+            ["I am collapsed", "collapsed button"]],
+          expectedBraille: [["btn", "I am collapsed"],
+            ["I am collapsed", "btn"]]
         }, {
           // test required state utterance
           accOrElmOrID: 'requiredInput',
-          expected: [["required entry", "I am required"],
-            ["I am required", "required entry"]]
+          expectedUtterance: [["required entry", "I am required"],
+            ["I am required", "required entry"]],
+          expectedBraille: [["entry", "I am required"],
+            ["I am required", "entry"]]
         }, {
           // test has popup state utterance
           accOrElmOrID: 'hasPopupButton',
-          expected: [["has pop up button menu", "I have a popup"],
-            ["I have a popup", "has pop up button menu"]]
+          expectedUtterance: [["has pop up button menu", "I have a popup"],
+            ["I have a popup", "has pop up button menu"]],
+          expectedBraille: [["button menu", "I have a popup"],
+            ["I have a popup", "button menu"]]
         }, {
           // Test selected tab
           accOrElmOrID: 'tab1',
-          expected: [['tab list', 'selected tab 1 of 2', 'Account'],
-            ['Account', 'selected tab 1 of 2', 'tab list']]
+          expectedUtterance: [['tab list', 'selected tab 1 of 2', 'Account'],
+            ['Account', 'selected tab 1 of 2', 'tab list']],
+          expectedBraille: [['tab 1 of 2', 'Account'],
+            ['Account', 'tab 1 of 2']]
         }, {
           // Test unselected tab
           accOrElmOrID: 'tab2',
-          expected: [['tab list', 'tab 2 of 2', 'Advanced'],
-            ['Advanced', 'tab 2 of 2', 'tab list']]
+          expectedUtterance: [['tab list', 'tab 2 of 2', 'Advanced'],
+            ['Advanced', 'tab 2 of 2', 'tab list']],
+          expectedBraille: [['tab 2 of 2', 'Advanced'],
+            ['Advanced', 'tab 2 of 2']]
         },
 
         {
           // Landing on this label should mimic landing on the checkbox.
           accOrElmOrID: "label1",
-          expected: [['not checked check button', 'Orange'],
-                     ['Orange', 'not checked check button']]
+          expectedUtterance: [['not checked check button', 'Orange'],
+                     ['Orange', 'not checked check button']],
+          expectedBraille: [['( )', 'Orange'],
+                     ['Orange', '( )']]
         },
         {
           // Here we get a top-level view of the form.
           accOrElmOrID: "form1",
-          expected: [['label', 'not checked check button', 'Orange', 'Orange',
+          expectedUtterance: [['label', 'not checked check button', 'Orange', 'Orange',
                       'not checked check button', 'Blue', 'label', 'Blue'],
                      ['Orange', 'not checked check button', 'Orange', 'label',
-                      'Blue', 'not checked check button', 'Blue', 'label']]
+                      'Blue', 'not checked check button', 'Blue', 'label']],
+          expectedBraille: [['label', '( )', 'Orange', 'Orange',
+                      '( )', 'Blue', 'label', 'Blue'],
+                     ['Orange', '( )', 'Orange', 'label',
+                      'Blue', '( )', 'Blue', 'label']]
         },
         {
           // This is a non-nesting label.
           accOrElmOrID: "label2",
-          expected: [['label', 'Blue'], ['Blue', 'label']]
+          expectedUtterance: [['label', 'Blue'], ['Blue', 'label']],
+          expectedBraille: [['label', 'Blue'], ['Blue', 'label']]
         },
         {
           // This is a distinct control.
           accOrElmOrID: "input2",
-          expected: [['not checked check button', 'Blue'],
-                     ['Blue', 'not checked check button']]
+          expectedUtterance: [['not checked check button', 'Blue'],
+                     ['Blue', 'not checked check button']],
+          expectedBraille: [['( )', 'Blue'],
+                     ['Blue', '( )']]
         },
         {
           // This is a nested control.
           accOrElmOrID: "input1",
-          expected: [['not checked check button', 'Orange'],
-                     ['Orange', 'not checked check button']]
+          expectedUtterance: [['not checked check button', 'Orange'],
+                     ['Orange', 'not checked check button']],
+          expectedBraille: [['( )', 'Orange'],
+                     ['Orange', '( )']]
         },
         {
           // Landing on this label should mimic landing on the entry.
           accOrElmOrID: "label3",
-          expected: [['entry', 'Joe', 'First name:'],
+          expectedUtterance: [['entry', 'Joe', 'First name:'],
+                     ['First name:', 'Joe', 'entry']],
+          expectedBraille: [['entry', 'Joe', 'First name:'],
                      ['First name:', 'Joe', 'entry']]
         },
         {
           // This is a nested control with a value.
           accOrElmOrID: "input3",
-          expected: [['entry', 'Joe', 'First name:'],
+          expectedUtterance: [['entry', 'Joe', 'First name:'],
+                     ['First name:', 'Joe', 'entry']],
+          expectedBraille: [['entry', 'Joe', 'First name:'],
                      ['First name:', 'Joe', 'entry']]
         },
         {
           // This is a nested control with a value.
           accOrElmOrID: "input4",
-          expected: [['slider', '3', 'Points:'],
+          expectedUtterance: [['slider', '3', 'Points:'],
+                     ['Points:', '3', 'slider']],
+          expectedBraille: [['slider', '3', 'Points:'],
                      ['Points:', '3', 'slider']]
-        }];
+        },{
+          accOrElmOrID: "password",
+          expectedUtterance: [["password text", "Secret Password"],
+                              ["Secret Password", "password text"]],
+          expectedBraille: [["passwdtxt", "Secret Password"],
+                            ["Secret Password", "passwdtxt"]]
+        },{
+          accOrElmOrID: "input5",
+          expectedUtterance: [["checked check button", "Boring label"],
+                              ["Boring label", "checked check button"]],
+          expectedBraille: [["(x)", "Boring label"],
+                            ["Boring label", "(x)"]]
+        },{
+          accOrElmOrID: "radio_unselected",
+          expectedUtterance: [["not checked radio button", "any old radio button"],
+                              ["any old radio button", "not checked radio button"]],
+          expectedBraille: [["( )", "any old radio button"],
+                            ["any old radio button", "( )"]]
+        },{
+          accOrElmOrID: "radio_selected",
+          expectedUtterance: [["checked radio button", "a unique radio button"],
+                              ["a unique radio button", "checked radio button"]],
+          expectedBraille: [["(x)", "a unique radio button"],
+                            ["a unique radio button", "(x)"]]
+        },{
+          accOrElmOrID: "togglebutton_notpressed",
+          expectedUtterance: [["not checked toggle button", "I ain't pressed"],
+                              ["I ain't pressed", "not checked toggle button"]],
+          expectedBraille: [["( )", "I ain't pressed"],
+                            ["I ain't pressed", "( )"]]
+        },{
+          accOrElmOrID: "togglebutton_pressed",
+          expectedUtterance: [["not checked toggle button", "I am pressed!"],
+                              ["I am pressed!", "not checked toggle button"]],
+          expectedBraille: [["(x)", "I am pressed!"],
+                            ["I am pressed!", "(x)"]]
+        }
+        ];
 
         // Test all possible utterance order preference values.
         tests.forEach(function run(test) {
           var utteranceOrderValues = [0, 1];
           utteranceOrderValues.forEach(
             function testUtteranceOrder(utteranceOrder) {
               SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, utteranceOrder);
-              var expected = test.expected[utteranceOrder];
-              testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1);
+              testOutput(test.expectedUtterance[utteranceOrder],
+                         test.accOrElmOrID, test.oldAccOrElmOrID, 1);
+              testOutput(test.expectedBraille[utteranceOrder],
+                         test.accOrElmOrID, test.oldAccOrElmOrID, 0);
             }
           );
         });
 
         // If there was an original utterance order preference, revert to it.
         SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER);
         SimpleTest.finish();
       }
@@ -247,16 +371,28 @@ https://bugzilla.mozilla.org/show_bug.cg
       <a target="_blank"
          href="https://bugzilla.mozilla.org/show_bug.cgi?id=753984"
          title="[AccessFu] utterance order test">
          Mozilla Bug 753984</a>
       <a target="_blank"
          href="https://bugzilla.mozilla.org/show_bug.cgi?id=758675"
          title="[AccessFu] Add support for accDescription">
          Mozilla Bug 758675</a>
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=876475"
+         title="[AccessFu] Make braille output less verbose">
+         Mozilla Bug 876475</a>
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=924284"
+         title="[AccessFu] Output accessible values">
+         Mozilla Bug 924284</a>
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=925845"
+         title="[AccessFu] Unify output tests">
+         Mozilla Bug 925845</a>
       <p id="display"></p>
       <div id="content" style="display: none"></div>
       <pre id="test"></pre>
       <a id="anchor" href="#test" title="title"></a>
       <a id="anchor_titleandtext" href="#test" title="goes to the tests">Tests</a>
       <a id="anchor_duplicatedtitleandtext" href="#test" title="Tests">Tests</a>
       <a id="anchor_arialabelandtext" href="#test" aria-label="Tests" title="goes to the tests">Tests</a>
       <textarea id="textarea" cols="80" rows="5">
@@ -300,17 +436,24 @@ https://bugzilla.mozilla.org/show_bug.cg
       <form id="form1">
         <label id="label1"><input id="input1" type="checkbox">Orange</label>
         <input id="input2" type="checkbox"><label id="label2" for="input2">Blue</label>
       </form>
       <label id="label3">First name: <input id="input3" value="Joe"></label>
       <label id="label4">Points:
         <input id="input4" type="range" name="points" min="1" max="10" value="3">
       </label>
+      <label for="input5">Boring label</label><input id="input5" type="checkbox" checked></input>
+      <label for="password">Secret Password</label><input id="password" type="password"></input>
+      <label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input>
+      <label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input>
       <input id="date" type="date" value="2011-09-29" />
       <input id="email" type="email" value="test@example.com" />
       <input id="search" type="search" value="This is a search" />
       <input id="tel" type="tel" value="555-5555" />
       <input id="url" type="url" value="http://example.com" />
       <input id="textInput" type="text" value="This is text." />
+      <label>Points: <input id="range" type="range" name="points" min="1" max="10" value="3"></label>
+      <div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I ain't pressed</div>
+      <div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div>
     </div>
   </body>
 </html>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -522,16 +522,18 @@
 @BINPATH@/components/HealthReportService.js
 #endif
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
+@BINPATH@/components/TelemetryStartup.js
+@BINPATH@/components/TelemetryStartup.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 @BINPATH@/components/PushServiceLauncher.js
 
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -9,16 +9,19 @@
  * events.
  */
 let CustomizationHandler = {
   handleEvent: function(aEvent) {
     switch(aEvent.type) {
       case "customizationstarting":
         this._customizationStarting();
         break;
+      case "customizationchange":
+        this._customizationChange();
+        break;
       case "customizationending":
         this._customizationEnding(aEvent.detail);
         break;
     }
   },
 
   isCustomizing: function() {
     return document.documentElement.hasAttribute("customizing");
@@ -48,16 +51,22 @@ let CustomizationHandler = {
     // can cause the customize tab to get clipped.
     let tabContainer = gBrowser.tabContainer;
     if (tabContainer.getAttribute("overflow") == "true") {
       let tabstrip = tabContainer.mTabstrip;
       tabstrip.ensureElementIsVisible(gBrowser.selectedTab, true);
     }
   },
 
+  _customizationChange: function() {
+    gHomeButton.updatePersonalToolbarStyle();
+    BookmarkingUI.customizeChange();
+    PlacesToolbarHelper.customizeChange();
+  },
+
   _customizationEnding: function(aDetails) {
     // Update global UI elements that may have been added or removed
     if (aDetails.changed) {
       gURLBar = document.getElementById("urlbar");
 
       gProxyFavIcon = document.getElementById("page-proxy-favicon");
       gHomeButton.updateTooltip();
       gIdentityHandler._cacheElements();
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -858,16 +858,20 @@ var PlacesMenuDNDHandler = {
  */
 let PlacesToolbarHelper = {
   _place: "place:folder=TOOLBAR",
 
   get _viewElt() {
     return document.getElementById("PlacesToolbar");
   },
 
+  get _placeholder() {
+    return document.getElementById("bookmarks-toolbar-placeholder");
+  },
+
   init: function PTH_init(forceToolbarOverflowCheck) {
     let viewElt = this._viewElt;
     if (!viewElt || viewElt._placesView)
       return;
 
     // If the bookmarks toolbar item is:
     // - not in a toolbar, or;
     // - the toolbar is collapsed, or;
@@ -878,33 +882,59 @@ let PlacesToolbarHelper = {
     if (!toolbar || toolbar.collapsed || this._isCustomizing ||
         getComputedStyle(toolbar, "").display == "none")
       return;
 
     new PlacesToolbar(this._place);
     if (forceToolbarOverflowCheck) {
       viewElt._placesView.updateOverflowStatus();
     }
+    this.customizeChange();
   },
 
   customizeStart: function PTH_customizeStart() {
     try {
       let viewElt = this._viewElt;
       if (viewElt && viewElt._placesView)
         viewElt._placesView.uninit();
     } finally {
       this._isCustomizing = true;
     }
+    this._shouldWrap = this._getShouldWrap();
+  },
+
+  customizeChange: function PTH_customizeChange() {
+    let placeholder = this._placeholder;
+    if (!placeholder) {
+      return;
+    }
+    let shouldWrapNow = this._getShouldWrap();
+    if (this._shouldWrap != shouldWrapNow) {
+      if (shouldWrapNow) {
+        placeholder.setAttribute("wrap", "true");
+      } else {
+        placeholder.removeAttribute("wrap");
+      }
+      placeholder.classList.toggle("toolbarbutton-1", shouldWrapNow);
+      this._shouldWrap = shouldWrapNow;
+    }
   },
 
   customizeDone: function PTH_customizeDone() {
     this._isCustomizing = false;
     this.init(true);
   },
 
+  _getShouldWrap: function PTH_getShouldWrap() {
+    let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
+    let area = placement && placement.area;
+    let areaType = area && CustomizableUI.getAreaType(area);
+    return !area || CustomizableUI.TYPE_MENU_PANEL == areaType;
+  },
+
   onPlaceholderCommand: function () {
     let widgetGroup = CustomizableUI.getWidget("personal-bookmarks");
     let widget = widgetGroup.forWindow(window);
     if (widget.overflowed ||
         widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
       PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar");
     }
   },
@@ -1077,18 +1107,24 @@ let BookmarkingUI = {
     this._updateToolbarStyle();
   },
 
   customizeDone: function BUI_customizeDone() {
     this.onToolbarVisibilityChange();
     this._updateToolbarStyle();
   },
 
+  init: function() {
+    CustomizableUI.addListener(this);
+  },
+
   _hasBookmarksObserver: false,
   uninit: function BUI_uninit() {
+    CustomizableUI.removeListener(this);
+
     this._uninitView();
 
     if (this._hasBookmarksObserver) {
       PlacesUtils.removeLazyBookmarkObserver(this);
     }
 
     if (this._pendingStmt) {
       this._pendingStmt.cancel();
@@ -1274,12 +1310,47 @@ let BookmarkingUI = {
   },
 
   onBeginUpdateBatch: function () {},
   onEndUpdateBatch: function () {},
   onBeforeItemRemoved: function () {},
   onItemVisited: function () {},
   onItemMoved: function () {},
 
+  // CustomizableUI events:
+  _starButtonLabel: null,
+  _starButtonOverflowedLabel: null,
+  onWidgetOverflow: function(aNode, aContainer) {
+    let win = aNode.ownerDocument.defaultView;
+    if (aNode.id != "bookmarks-menu-button" || win != window)
+      return;
+
+    if (!this._starButtonOverflowedLabel) {
+      this._starButtonOverflowedLabel = gNavigatorBundle.getString(
+                                        "starButtonOverflowed.label");
+    }
+
+    let currentLabel = aNode.getAttribute("label");
+    if (!this._starButtonLabel)
+      this._starButtonLabel = currentLabel;
+
+    if (currentLabel == this._starButtonLabel)
+      aNode.setAttribute("label", this._starButtonOverflowedLabel);
+  },
+
+  onWidgetUnderflow: function(aNode, aContainer) {
+    let win = aNode.ownerDocument.defaultView;
+    if (aNode.id != "bookmarks-menu-button" || win != window)
+      return;
+
+    // If the button hasn't been in the overflow panel before, we may ignore
+    // this event.
+    if (!this._starButtonOverflowedLabel || !this._starButtonLabel)
+      return;
+
+    if (aNode.getAttribute("label") == this._starButtonOverflowedLabel)
+      aNode.setAttribute("label", this._starButtonLabel);
+  },
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsINavBookmarkObserver
   ])
 };
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -742,16 +742,24 @@ toolbarbutton[type="socialmark"] > .tool
   max-width: 16px;
   max-height: 16px;
 }
 toolbarpaletteitem[place="palette"] > toolbarbutton[type="badged"] > .toolbarbutton-badge-container > .toolbarbutton-icon {
   max-width: 32px;
   max-height: 32px;
 }
 
+toolbarbutton[sdk-button="true"] > .toolbarbutton-icon {
+  max-height: 32px;
+}
+
+toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
+  max-height: 18px;
+}
+
 panelview > .social-panel-frame {
   width: auto;
   height: auto;
 }
 
 /* Note the chatbox 'width' values are duplicated in socialchat.xml */
 chatbox {
   -moz-binding: url("chrome://browser/content/socialchat.xml#chatbox");
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1039,29 +1039,27 @@ var gBrowserInit = {
       sidebar.setAttribute("src", sidebarBox.getAttribute("src"));
     }
 
     UpdateUrlbarSearchSplitterState();
 
     if (!isLoadingBlank || !focusAndSelectUrlBar())
       gBrowser.selectedBrowser.focus();
 
-    gNavToolbox.customizeDone = BrowserToolboxCustomizeDone;
-    gNavToolbox.customizeChange = BrowserToolboxCustomizeChange;
-
     // Set up Sanitize Item
     this._initializeSanitizer();
 
     // Enable/Disable auto-hide tabbar
     gBrowser.tabContainer.updateVisibility();
 
+    BookmarkingUI.init();
+
     gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
 
     var homeButton = document.getElementById("home-button");
-    gHomeButton.init();
     gHomeButton.updateTooltip(homeButton);
     gHomeButton.updatePersonalToolbarStyle(homeButton);
 
     // BiDi UI
     gBidiUI = isBidiEnabled();
     if (gBidiUI) {
       document.getElementById("documentDirection-separator").hidden = false;
       document.getElementById("documentDirection-swap").hidden = false;
@@ -1146,16 +1144,17 @@ var gBrowserInit = {
 
     // Add Devtools menuitems and listeners
     gDevToolsBrowser.registerBrowserWindow(window);
 
     window.addEventListener("mousemove", MousePosTracker, false);
     window.addEventListener("dragover", MousePosTracker, false);
 
     gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
+    gNavToolbox.addEventListener("customizationchange", CustomizationHandler);
     gNavToolbox.addEventListener("customizationending", CustomizationHandler);
 
     // End startup crash tracking after a delay to catch crashes while restoring
     // tabs and to postpone saving the pref to disk.
     try {
       const startupCrashEndDelay = 30 * 1000;
       setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay);
     } catch (ex) {
@@ -1243,17 +1242,16 @@ var gBrowserInit = {
 
     try {
       gBrowser.removeProgressListener(window.XULBrowserWindow);
       gBrowser.removeTabsProgressListener(window.TabsProgressListener);
     } catch (ex) {
     }
 
     BookmarkingUI.uninit();
-    gHomeButton.uninit();
 
     TabsInTitlebar.uninit();
 
     var enumerator = Services.wm.getEnumerator(null);
     enumerator.getNext();
     if (!enumerator.hasMoreElements()) {
       document.persist("sidebar-box", "sidebarcommand");
       document.persist("sidebar-box", "width");
@@ -3273,30 +3271,21 @@ function OpenBrowserWindow(options)
   else // forget about the charset information.
   {
     win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs);
   }
 
   return win;
 }
 
-//XXXunf Are these still useful to keep around?
+// Only here for backwards compat, we should remove this soon
 function BrowserCustomizeToolbar() {
   gCustomizeMode.enter();
 }
 
-function BrowserToolboxCustomizeDone(aToolboxChanged) {
-  gCustomizeMode.exit(aToolboxChanged);
-}
-
-function BrowserToolboxCustomizeChange(aType) {
-  gHomeButton.updatePersonalToolbarStyle();
-  BookmarksMenuButton.customizeChange();
-}
-
 /**
  * Update the global flag that tracks whether or not any edit UI (the Edit menu,
  * edit-related items in the context menu, and edit-related toolbar buttons
  * is visible, then update the edit commands' enabled state accordingly.  We use
  * this flag to skip updating the edit commands on focus or selection changes
  * when no UI is visible to improve performance (including pageload performance,
  * since focus changes when you load a new page).
  *
@@ -4757,26 +4746,16 @@ function fireSidebarFocusedEvent() {
   var sidebar = document.getElementById("sidebar");
   var event = document.createEvent("Events");
   event.initEvent("SidebarFocused", true, false);
   sidebar.contentWindow.dispatchEvent(event);
 }
 
 
 var gHomeButton = {
-  init: function() {
-    gNavToolbox.addEventListener("customizationchange",
-                                 this.onCustomizationChange);
-  },
-
-  uninit: function() {
-    gNavToolbox.removeEventListener("customizationchange",
-                                    this.onCustomizationChange);
-  },
-
   prefDomain: "browser.startup.homepage",
   observe: function (aSubject, aTopic, aPrefName)
   {
     if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
       return;
 
     this.updateTooltip();
   },
@@ -4819,20 +4798,16 @@ var gHomeButton = {
     if (!homeButton)
       homeButton = document.getElementById("home-button");
     if (homeButton)
       homeButton.className = homeButton.parentNode.id == "PersonalToolbar"
                                || homeButton.parentNode.parentNode.id == "PersonalToolbar" ?
                              homeButton.className.replace("toolbarbutton-1", "bookmark-item") :
                              homeButton.className.replace("bookmark-item", "toolbarbutton-1");
   },
-
-  onCustomizationChange: function(aEvent) {
-    gHomeButton.updatePersonalToolbarStyle();
-  },
 };
 
 /**
  * Gets the selected text in the active browser. Leading and trailing
  * whitespace is removed, and consecutive whitespace is replaced by a single
  * space. A maximum of 150 characters will be returned, regardless of the value
  * of aCharLen.
  *
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -853,17 +853,16 @@
              collapsed="true"
              customizable="true">
       <toolbaritem id="personal-bookmarks"
                    flex="1"
                    title="&bookmarksToolbarItem.label;"
                    cui-areatype="toolbar"
                    removable="true">
         <toolbarbutton id="bookmarks-toolbar-placeholder"
-                       type="wrap"
                        mousethrough="never"
                        label="&bookmarksToolbarItem.label;"
                        oncommand="PlacesToolbarHelper.onPlaceholderCommand();"/>
         <hbox flex="1"
               id="PlacesToolbar"
               context="placesContext"
               onclick="BookmarksEventHandler.onClick(event, this._placesView);"
               oncommand="BookmarksEventHandler.onCommand(event, this._placesView);"
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -6,28 +6,30 @@
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        noautofocus="true">
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
     <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
       <vbox id="PanelUI-contents-scroller">
-        <vbox id="PanelUI-contents"/>
+        <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;" tabindex="0"
                        oncommand="gCustomizeMode.toggle();"/>
+        <toolbarseparator/>
         <toolbarbutton id="PanelUI-help" label="&helpMenu.label;" tabindex="0"
                        tooltiptext="&helpMenu.label;"
                        oncommand="PanelUI.showHelpView(this.parentNode);"/>
+        <toolbarseparator/>
         <toolbarbutton id="PanelUI-quit" tabindex="0"
 #ifdef XP_WIN
                        label="&quitApplicationCmdWin.label;"
                        tooltiptext="&quitApplicationCmdWin.label;"
 #else
                        label="&quitApplicationCmd.label;"
                        tooltiptext="&quitApplicationCmd.label;"
 #endif
@@ -173,11 +175,11 @@
 </panel>
 
 <panel id="widget-overflow"
        role="group"
        type="arrow"
        level="top"
        hidden="true">
   <vbox id="widget-overflow-scroller">
-    <vbox id="widget-overflow-list"/>
+    <vbox id="widget-overflow-list" class="widget-overflow-list"/>
   </vbox>
 </panel>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -202,16 +202,18 @@ const PanelUI = {
    *
    * @return a Promise that resolves once the panel is ready to roll.
    */
   ensureReady: function(aCustomizing=false) {
     if (this._readyPromise) {
       return this._readyPromise;
     }
     this._readyPromise = Task.spawn(function() {
+      this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
+                                   getLocale());
       if (!this._scrollWidth) {
         // In order to properly center the contents of the panel, while ensuring
         // that we have enough space on either side to show a scrollbar, we have to
         // do a bit of hackery. In particular, we calculate a new width for the
         // scroller, based on the system scrollbar width.
         this._scrollWidth =
           (yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
         let cstyle = window.getComputedStyle(this.scroller);
@@ -413,8 +415,30 @@ const PanelUI = {
 
     this.addEventListener("command", PanelUI.onCommandHandler);
   },
 
   _onHelpViewHide: function(aEvent) {
     this.removeEventListener("command", PanelUI.onCommandHandler);
   }
 };
+
+/**
+ * Gets the currently selected locale for display.
+ * @return  the selected locale or "en-US" if none is selected
+ */
+function getLocale() {
+  try {
+    let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
+                                                Ci.nsIPrefLocalizedString);
+    if (locale)
+      return locale;
+  }
+  catch (e) { }
+
+  try {
+    return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
+  }
+  catch (e) { }
+
+  return "en-US";
+}
+
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -474,19 +474,17 @@ let CustomizableUIInternal = {
           if (!widget.showInPrivateBrowsing && inPrivateWindow) {
             continue;
           }
         }
 
         this.ensureButtonContextMenu(node, aAreaNode);
         if (node.localName == "toolbarbutton" && aArea == CustomizableUI.AREA_PANEL) {
           node.setAttribute("tabindex", "0");
-          if (!node.hasAttribute("type")) {
-            node.setAttribute("type", "wrap");
-          }
+          node.setAttribute("wrap", "true");
         }
 
         this.insertWidgetBefore(node, currentNode, container, aArea);
         if (gResetting) {
           this.notifyListeners("onWidgetReset", node, container);
         }
       }
 
@@ -653,19 +651,17 @@ let CustomizableUIInternal = {
       if (child.localName != "toolbarbutton") {
         if (child.localName == "toolbaritem") {
           this.ensureButtonContextMenu(child, aPanel);
         }
         continue;
       }
       this.ensureButtonContextMenu(child, aPanel);
       child.setAttribute("tabindex", "0");
-      if (!child.hasAttribute("type")) {
-        child.setAttribute("type", "wrap");
-      }
+      child.setAttribute("wrap", "true");
     }
 
     this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanel);
   },
 
   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
     this.insertNode(aWidgetId, aArea, aPosition, true);
   },
@@ -704,19 +700,17 @@ let CustomizableUIInternal = {
 
       // We remove location attributes here to make sure they're gone too when a
       // widget is removed from a toolbar to the palette. See bug 930950.
       this.removeLocationAttributes(widgetNode);
       if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
         container.removeChild(widgetNode);
       } else {
         widgetNode.removeAttribute("tabindex");
-        if (widgetNode.getAttribute("type") == "wrap") {
-          widgetNode.removeAttribute("type");
-        }
+        widgetNode.removeAttribute("wrap");
         areaNode.toolbox.palette.appendChild(widgetNode);
       }
       this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
 
       if (isToolbar) {
         areaNode.setAttribute("currentset", gPlacements.get(aArea).join(','));
       }
 
@@ -856,19 +850,17 @@ let CustomizableUIInternal = {
       return;
     }
 
     let areaId = aAreaNode.id;
     if (isNew) {
       this.ensureButtonContextMenu(widgetNode, aAreaNode);
       if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) {
         widgetNode.setAttribute("tabindex", "0");
-        if (!widgetNode.hasAttribute("type")) {
-          widgetNode.setAttribute("type", "wrap");
-        }
+        widgetNode.setAttribute("wrap", "true");
       }
     }
 
     let container = aAreaNode.customizationTarget;
     let [insertionContainer, nextNode] = this.findInsertionPoints(widgetNode, aNextNodeId, aAreaNode);
     this.insertWidgetBefore(widgetNode, nextNode, insertionContainer, areaId);
 
     if (gAreas.get(areaId).get("type") == CustomizableUI.TYPE_TOOLBAR) {
@@ -1195,29 +1187,102 @@ let CustomizableUIInternal = {
 
   /*
    * If people put things in the panel which need more than single-click interaction,
    * we don't want to close it. Right now we check for text inputs and menu buttons.
    * We also check for being outside of any toolbaritem/toolbarbutton, ie on a blank
    * part of the menu.
    */
   _isOnInteractiveElement: function(aEvent) {
+    function getMenuPopupForDescendant(aNode) {
+      let lastPopup = null;
+      while (aNode && aNode.parentNode &&
+             aNode.parentNode.localName.startsWith("menu")) {
+        lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup;
+        aNode = aNode.parentNode;
+      }
+      return lastPopup;
+    }
+
     let target = aEvent.originalTarget;
     let panel = this._getPanelForNode(aEvent.currentTarget);
+    // We keep track of:
+    // whether we're in an input container (text field)
     let inInput = false;
+    // whether we're in a popup/context menu
     let inMenu = false;
+    // whether we're in a toolbarbutton/toolbaritem
     let inItem = false;
-    while (!inInput && !inMenu && !inItem && target != panel) {
+    // whether the current menuitem has a valid closemenu attribute
+    let menuitemCloseMenu = "auto";
+    // whether the toolbarbutton/item has a valid closemenu attribute.
+    let closemenu = "auto";
+
+    // While keeping track of that, we go from the original target back up,
+    // to the panel if we have to. We bail as soon as we find an input,
+    // a toolbarbutton/item, or the panel:
+    while (true) {
       let tagName = target.localName;
-      inInput = tagName == "input";
-      inMenu = target.type == "menu";
+      inInput = tagName == "input" || tagName == "textbox";
       inItem = tagName == "toolbaritem" || tagName == "toolbarbutton";
-      target = target.parentNode;
+      let isMenuItem = tagName == "menuitem";
+      inMenu = inMenu || isMenuItem;
+      if (inItem && target.hasAttribute("closemenu")) {
+        let closemenuVal = target.getAttribute("closemenu");
+        closemenu = (closemenuVal == "single" || closemenuVal == "none") ?
+                    closemenuVal : "auto";
+      }
+
+      if (isMenuItem && target.hasAttribute("closemenu")) {
+        let closemenuVal = target.getAttribute("closemenu");
+        menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
+                            closemenuVal : "auto";
+      }
+      // This isn't in the loop condition because we want to break before
+      // changing |target| if any of these conditions are true
+      if (inInput || inItem || target == panel) {
+        break;
+      }
+      // We need specific code for popups: the item on which they were invoked
+      // isn't necessarily in their parentNode chain:
+      if (isMenuItem) {
+        let topmostMenuPopup = getMenuPopupForDescendant(target);
+        target = (topmostMenuPopup && topmostMenuPopup.triggerNode) ||
+                 target.parentNode;
+      } else {
+        target = target.parentNode;
+      }
     }
-    return inMenu || inInput || !inItem;
+    // If the user clicked a menu item...
+    if (inMenu) {
+      // We care if we're in an input also,
+      // or if the user specified closemenu!="auto":
+      if (inInput || menuitemCloseMenu != "auto") {
+        return true;
+      }
+      // Otherwise, we're probably fine to close the panel
+      return false;
+    }
+    // If we're not in a menu, and we *are* in a type="menu" toolbarbutton,
+    // we'll now interact with the menu
+    if (inItem && target.getAttribute("type") == "menu") {
+      return true;
+    }
+    // If we're not in a menu, and we *are* in a type="menu-button" toolbarbutton,
+    // it depends whether we're in the dropmarker or the 'real' button:
+    if (inItem && target.getAttribute("type") == "menu-button") {
+      // 'real' button (which has a single action):
+      if (target.getAttribute("anonid") == "button") {
+        return closemenu != "none";
+      }
+      // otherwise, this is the outer button, and the user will now
+      // interact with the menu:
+      return true;
+    }
+    return inInput || !inItem;
   },
 
   hidePanelForNode: function(aNode) {
     let panel = this._getPanelForNode(aNode);
     if (panel) {
       panel.hidePopup();
     }
   },
@@ -2223,16 +2288,23 @@ this.CustomizableUI = {
    *     different area. aArea will be the area the item is dragged to, or
    *     undefined after the measurements have been done and the node has been
    *     moved back to its 'regular' area.
    *
    *   - onCustomizeStart(aWindow)
    *     Fired when opening customize mode in aWindow.
    *   - onCustomizeEnd(aWindow)
    *     Fired when exiting customize mode in aWindow.
+   *
+   *   - onWidgetOverflow(aNode, aContainer)
+   *     Fired when a widget's DOM node is overflowing its container, a toolbar,
+   *     and will be displayed in the overflow panel.
+   *   - onWidgetUnderflow(aNode, aContainer)
+   *     Fired when a widget's DOM node is *not* overflowing its container, a
+   *     toolbar, anymore.
    */
   addListener: function(aListener) {
     CustomizableUIInternal.addListener(aListener);
   },
   /**
    * Remove a listener added with addListener
    * @param aListener the listener object to remove
    */
@@ -3230,16 +3302,17 @@ OverflowableToolbar.prototype = {
 
     while (child && this._target.scrollLeftMax > 0) {
       let prevChild = child.previousSibling;
 
       if (child.getAttribute("overflows") != "false") {
         this._collapsed.set(child.id, this._target.clientWidth);
         child.classList.add("overflowedItem");
         child.setAttribute("cui-anchorid", this._chevron.id);
+        CustomizableUIInternal.notifyListeners("onWidgetOverflow", child, this._target);
 
         this._list.insertBefore(child, this._list.firstChild);
         if (!this._toolbar.hasAttribute("overflowing")) {
           CustomizableUI.addListener(this);
         }
         this._toolbar.setAttribute("overflowing", "true");
       }
       child = prevChild;
@@ -3286,16 +3359,17 @@ OverflowableToolbar.prototype = {
           break;
         }
       }
       if (!inserted) {
         this._target.appendChild(child);
       }
       child.removeAttribute("cui-anchorid");
       child.classList.remove("overflowedItem");
+      CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target);
     }
 
     let win = this._target.ownerDocument.defaultView;
     win.UpdateUrlbarSearchSplitterState();
 
     if (!this._collapsed.size) {
       this._toolbar.removeAttribute("overflowing");
       CustomizableUI.removeListener(this);
@@ -3360,16 +3434,17 @@ OverflowableToolbar.prototype = {
       if (nowOverflowed) {
         // NB: we're guaranteed that it has a previousSibling, because if it didn't,
         // we would have added it to the toolbar instead. See getOverflowedNextNode.
         let prevId = aNode.previousSibling.id;
         let minSize = this._collapsed.get(prevId);
         this._collapsed.set(aNode.id, minSize);
         aNode.setAttribute("cui-anchorid", this._chevron.id);
         aNode.classList.add("overflowedItem");
+        CustomizableUIInternal.notifyListeners("onWidgetOverflow", aNode, this._target);
       }
       // If it is not overflowed and not in the toolbar, and was not overflowed
       // either, it moved out of the toolbar. That means there's now space in there!
       // Let's try to move stuff back:
       else if (!nowInBar) {
         this._moveItemsBackToTheirOrigin(true);
       }
       // If it's in the toolbar now, then we don't care. An overflow event may
@@ -3377,16 +3452,17 @@ OverflowableToolbar.prototype = {
     }
     // If it used to be overflowed...
     else {
       // ... and isn't anymore, let's remove our bookkeeping:
       if (!nowOverflowed) {
         this._collapsed.delete(aNode.id);
         aNode.removeAttribute("cui-anchorid");
         aNode.classList.remove("overflowedItem");
+        CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target);
 
         if (!this._collapsed.size) {
           this._toolbar.removeAttribute("overflowing");
           CustomizableUI.removeListener(this);
         }
       }
       // but if it still is, it must have changed places. Bookkeep:
       else {
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -46,16 +46,17 @@ skip-if = os == "mac"
 [browser_946320_tabs_from_other_computers.js]
 skip-if = os == "linux"
 
 [browser_934951_zoom_in_toolbar.js]
 [browser_938980_navbar_collapsed.js]
 [browser_938995_indefaultstate_nonremovable.js]
 [browser_940013_registerToolbarNode_calls_registerArea.js]
 [browser_940107_home_button_in_bookmarks_toolbar.js]
+[browser_940307_panel_click_closure_handling.js]
 [browser_940946_removable_from_navbar_customizemode.js]
 [browser_941083_invalidate_wrapper_cache_createWidget.js]
 [browser_942581_unregisterArea_keeps_placements.js]
 [browser_943683_migration_test.js]
 [browser_944887_destroyWidget_should_destroy_in_palette.js]
 [browser_945739_showInPrivateBrowsing_customize_mode.js]
 [browser_947987_removable_default.js]
 [browser_948985_non_removable_defaultArea.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
@@ -0,0 +1,108 @@
+/* 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";
+
+let button, menuButton;
+/* Clicking a button should close the panel */
+add_task(function() {
+  button = document.createElement("toolbarbutton");
+  button.id = "browser_940307_button";
+  button.setAttribute("label", "Button");
+  PanelUI.contents.appendChild(button);
+  yield PanelUI.show();
+  let hiddenAgain = promisePanelHidden(window);
+  EventUtils.synthesizeMouseAtCenter(button, {});
+  yield hiddenAgain;
+  button.remove();
+});
+
+/* Clicking a menu button should close the panel, opening the popup shouldn't.  */
+add_task(function() {
+  menuButton = document.createElement("toolbarbutton");
+  menuButton.setAttribute("type", "menu-button");
+  menuButton.id = "browser_940307_menubutton";
+  menuButton.setAttribute("label", "Menu button");
+
+  let menuPopup = document.createElement("menupopup");
+  menuPopup.id = "browser_940307_menupopup";
+
+  let menuItem = document.createElement("menuitem");
+  menuItem.setAttribute("label", "Menu item");
+  menuItem.id = "browser_940307_menuitem";
+
+  menuPopup.appendChild(menuItem);
+  menuButton.appendChild(menuPopup);
+  PanelUI.contents.appendChild(menuButton);
+
+  yield PanelUI.show();
+  let hiddenAgain = promisePanelHidden(window);
+  let innerButton = document.getAnonymousElementByAttribute(menuButton, "anonid", "button");
+  EventUtils.synthesizeMouseAtCenter(innerButton, {});
+  yield hiddenAgain;
+
+  // Now click the dropmarker to show the menu
+  yield PanelUI.show();
+  hiddenAgain = promisePanelHidden(window);
+  let menuShown = promisePanelElementShown(window, menuPopup);
+  let dropmarker = document.getAnonymousElementByAttribute(menuButton, "type", "menu-button");
+  EventUtils.synthesizeMouseAtCenter(dropmarker, {});
+  yield menuShown;
+  // Panel should stay open:
+  ok(isPanelUIOpen(), "Panel should still be open");
+  let menuHidden = promisePanelElementHidden(window, menuPopup);
+  // Then click the menu item to close all the things
+  EventUtils.synthesizeMouseAtCenter(menuItem, {});
+  yield menuHidden;
+  yield hiddenAgain;
+  menuButton.remove();
+});
+
+add_task(function() {
+  let searchbar = document.getElementById("searchbar");
+  gCustomizeMode.addToPanel(searchbar);
+  let placement = CustomizableUI.getPlacementOfWidget("search-container");
+  is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
+  yield PanelUI.show();
+  yield waitForCondition(() => "value" in searchbar && searchbar.value === "");
+
+  searchbar.value = "foo";
+  searchbar.focus();
+  // Reaching into this context menu is pretty evil, but hey... it's a test.
+  let textbox = document.getAnonymousElementByAttribute(searchbar.textbox, "anonid", "textbox-input-box");
+  let contextmenu = document.getAnonymousElementByAttribute(textbox, "anonid", "input-box-contextmenu");
+  let contextMenuShown = promisePanelElementShown(window, contextmenu);
+  EventUtils.synthesizeMouseAtCenter(searchbar, {type: "contextmenu", button: 2});
+  yield contextMenuShown;
+
+  ok(isPanelUIOpen(), "Panel should still be open");
+
+  let selectAll = contextmenu.querySelector("[cmd='cmd_selectAll']");
+  let contextMenuHidden = promisePanelElementHidden(window, contextmenu);
+  EventUtils.synthesizeMouseAtCenter(selectAll, {});
+  yield contextMenuHidden;
+
+  ok(isPanelUIOpen(), "Panel should still be open");
+
+  let hiddenPanelPromise = promisePanelHidden(window);
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield hiddenPanelPromise;
+  ok(!isPanelUIOpen(), "Panel should no longer be open");
+});
+
+registerCleanupFunction(function() {
+  if (button && button.parentNode) {
+    button.remove();
+  }
+  if (menuButton && menuButton.parentNode) {
+    menuButton.remove();
+  }
+  // Sadly this isn't task.jsm-enabled, so we can't wait for this to happen. But we should
+  // definitely close it here and hope it won't interfere with other tests.
+  // Of course, all the tests are meant to do this themselves, but if they fail...
+  if (isPanelUIOpen()) {
+    PanelUI.hide();
+  }
+});
+
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -245,16 +245,20 @@ function promisePanelElementHidden(win, 
     aPanel.removeEventListener("popuphidden", onPanelClose);
     win.clearTimeout(timeoutId);
     deferred.resolve();
   }
   aPanel.addEventListener("popuphidden", onPanelClose);
   return deferred.promise;
 }
 
+function isPanelUIOpen() {
+  return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing";
+}
+
 function subviewShown(aSubview) {
   let deferred = Promise.defer();
   let win = aSubview.ownerDocument.defaultView;
   let timeoutId = win.setTimeout(() => {
     deferred.reject("Subview (" + aSubview.id + ") did not show within 20 seconds.");
   }, 20000);
   function onViewShowing(e) {
     aSubview.removeEventListener("ViewShowing", onViewShowing);
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -109,11 +109,13 @@
 
   <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"/>
+      <xul:label class="toolbarbutton-multiline-text" flex="1"
+                 xbl:inherits="xbl:text=label,accesskey"/>
     </content>
   </binding>
 </bindings>
--- a/browser/components/tabview/tabitems.js
+++ b/browser/components/tabview/tabitems.js
@@ -841,16 +841,22 @@ let TabItems = {
   },
 
   // Function: _isComplete
   // Checks whether the xul:tab has fully loaded and calls a callback with a 
   // boolean indicates whether the tab is loaded or not.
   _isComplete: function TabItems__isComplete(tab, callback) {
     Utils.assertThrow(tab, "tab");
 
+    // A pending tab can't be complete, yet.
+    if (tab.hasAttribute("pending")) {
+      setTimeout(() => callback(false));
+      return;
+    }
+
     let mm = tab.linkedBrowser.messageManager;
     let message = "Panorama:isDocumentLoaded";
 
     mm.addMessageListener(message, function onMessage(cx) {
       mm.removeMessageListener(cx.name, onMessage);
       callback(cx.json.isLoaded);
     });
     mm.sendAsyncMessage(message);
--- a/browser/components/tabview/test/browser.ini
+++ b/browser/components/tabview/test/browser.ini
@@ -159,15 +159,16 @@ skip-if = true # Bug 736425
 [browser_tabview_dragdrop.js]
 [browser_tabview_exit_button.js]
 [browser_tabview_expander.js]
 [browser_tabview_firstrun_pref.js]
 [browser_tabview_group.js]
 skip-if = os == "mac" || os == "win" # Bug 945687
 [browser_tabview_launch.js]
 [browser_tabview_multiwindow_search.js]
+[browser_tabview_pending_tabs.js]
 [browser_tabview_privatebrowsing_perwindowpb.js]
 skip-if = os == 'linux' # Bug 944300
 [browser_tabview_rtl.js]
 [browser_tabview_search.js]
 [browser_tabview_snapping.js]
 [browser_tabview_startup_transitions.js]
 [browser_tabview_undo_group.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/tabview/test/browser_tabview_pending_tabs.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const STATE = {
+  windows: [{
+    tabs: [{
+      entries: [{ url: "about:mozilla" }],
+      hidden: true,
+      extData: {"tabview-tab": '{"url":"about:mozilla","groupID":1}'}
+    },{
+      entries: [{ url: "about:robots" }],
+      hidden: false,
+      extData: {"tabview-tab": '{"url":"about:robots","groupID":1}'},
+    }],
+    selected: 1,
+    extData: {
+      "tabview-groups": '{"nextID":2,"activeGroupId":1, "totalNumber":1}',
+      "tabview-group":
+        '{"1":{"bounds":{"left":15,"top":5,"width":280,"height":232},"id":1}}'
+    }
+  }]
+};
+
+/**
+ * Make sure that tabs are restored on demand as otherwise the tab will start
+ * loading immediately and we can't check whether it shows cached data.
+ */
+add_task(function setup() {
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+  });
+});
+
+/**
+ * Ensure that a pending tab shows cached data.
+ */
+add_task(function () {
+  // Open a new window.
+  let win = OpenBrowserWindow();
+  yield promiseDelayedStartupFinished(win);
+
+  // Set the window to a specific state.
+  let ss = Cc["@mozilla.org/browser/sessionstore;1"]
+             .getService(Ci.nsISessionStore)
+             .setWindowState(win, JSON.stringify(STATE), true);
+
+  // Open Panorama.
+  yield promiseTabViewShown(win);
+
+  let [tab1, tab2] = win.gBrowser.tabs;
+  let cw = win.TabView.getContentWindow();
+
+  // Update the two tabs in reverse order. Panorama will first try to update
+  // the second tab but will put it back onto the queue once it detects that
+  // it hasn't loaded yet. It will then try to update the first tab.
+  cw.TabItems.update(tab2);
+  cw.TabItems.update(tab1);
+
+  let tabItem1 = tab1._tabViewTabItem;
+  let tabItem2 = tab2._tabViewTabItem;
+
+  // Wait for the first tabItem to be updated. Calling update() on the second
+  // tabItem won't send a notification as that is pushed back onto the queue.
+  yield promiseTabItemUpdated(tabItem1);
+
+  // Check that the first tab doesn't show cached data, the second one does.
+  ok(!tabItem1.isShowingCachedData(), "doesn't show cached data");
+  ok(tabItem2.isShowingCachedData(), "shows cached data");
+
+  // Cleanup.
+  yield promiseWindowClosed(win);
+});
+
+function promiseTabItemUpdated(tabItem) {
+  let deferred = Promise.defer();
+
+  tabItem.addSubscriber("updated", function onUpdated() {
+    tabItem.removeSubscriber("updated", onUpdated);
+    deferred.resolve();
+  });
+
+  return deferred.promise;
+}
+
+function promiseAllTabItemsUpdated(win) {
+  let deferred = Promise.defer();
+  afterAllTabItemsUpdated(deferred.resolve, win);
+  return deferred.promise;
+}
+
+function promiseDelayedStartupFinished(win) {
+  let deferred = Promise.defer();
+  whenDelayedStartupFinished(win, deferred.resolve);
+  return deferred.promise;
+}
+
+function promiseTabViewShown(win) {
+  let deferred = Promise.defer();
+  showTabView(deferred.resolve, win);
+  return deferred.promise;
+}
+
+function promiseWindowClosed(win) {
+  let deferred = Promise.defer();
+
+  Services.obs.addObserver(function obs(subject, topic) {
+    if (subject == win) {
+      Services.obs.removeObserver(obs, topic);
+      deferred.resolve();
+    }
+  }, "domwindowclosed", false);
+
+  win.close();
+  return deferred.promise;
+}
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -32,16 +32,20 @@ window.addEventListener("DOMContentLoade
   if (host) {
     document.getElementById("host").value = host;
   }
 
   if (port) {
     document.getElementById("port").value = port;
   }
 
+  let form = document.querySelector("#connection-form form");
+  form.addEventListener("submit", function() {
+    window.submit();
+  });
 }, true);
 
 /**
  * Called when the "connect" button is clicked.
  */
 function submit() {
   // Show the "connecting" screen
   document.body.classList.add("connecting");
--- a/browser/devtools/framework/connect/connect.xhtml
+++ b/browser/devtools/framework/connect/connect.xhtml
@@ -14,17 +14,17 @@
     <title>&title;</title>
     <link rel="stylesheet" href="chrome://browser/skin/devtools/dark-theme.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://browser/content/devtools/connect.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="connect.js"></script>
   </head>
   <body>
     <h1>&header;</h1>
     <section id="connection-form">
-      <form validate="validate" onsubmit="window.submit()" action="#">
+      <form validate="validate" action="#">
         <label>
           <span>&host;</span>
           <input required="required" class="devtools-textinput" id="host" type="text"></input>
         </label>
         <label>
           <span>&port;</span>
           <input required="required" class="devtools-textinput" id="port" type="number" pattern="\d+"></input>
         </label>
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -507,16 +507,18 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
 @BINPATH@/components/servicesComponents.manifest
 @BINPATH@/components/cryptoComponents.manifest
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
+@BINPATH@/components/TelemetryStartup.js
+@BINPATH@/components/TelemetryStartup.manifest
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/SettingsService.js
 @BINPATH@/components/SettingsService.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -224,16 +224,17 @@ pasteAndGo.label=Paste & Go
 refreshBlocked.goButton=Allow
 refreshBlocked.goButton.accesskey=A
 refreshBlocked.refreshLabel=%S prevented this page from automatically reloading.
 refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page.
 
 # Star button
 starButtonOn.tooltip=Edit this bookmark
 starButtonOff.tooltip=Bookmark this page
+starButtonOverflowed.label=Bookmark This Page
 
 # Offline web applications
 offlineApps.available=This website (%S) is asking to store data on your computer for offline use.
 offlineApps.allow=Allow
 offlineApps.allowAccessKey=A
 offlineApps.never=Never for This Site
 offlineApps.neverAccessKey=e
 offlineApps.notNow=Not Now
deleted file mode 100644
--- a/browser/metro/base/content/config.js
+++ /dev/null
@@ -1,407 +0,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/. */
-
-let Ci = Components.interfaces;
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-var ViewConfig = {
-  get _main() {
-    delete this._main;
-    return this._main = document.getElementById("main-container");
-  },
-
-  get _container() {
-    delete this._container;
-    return this._container = document.getElementById("prefs-container");
-  },
-
-  get _editor() {
-    delete this._editor;
-    return this._editor = document.getElementById("editor");
-  },
-
-  init: function init() {
-    this._main.addEventListener("click", this, false);
-    window.addEventListener("resize", this, false);
-    window.addEventListener("prefchange", this, false);
-    window.addEventListener("prefnew", this, false);
-
-    this._handleWindowResize();
-    this.filter("");
-
-    document.getElementById("textbox").focus();
-  },
-
-  uninit: function uninit() {
-    this._main.removeEventListener("click", this, false);
-    window.removeEventListener("resize", this, false);
-    window.removeEventListener("prefchange", this, false);
-    window.removeEventListener("prefnew", this, false);
-  },
-
-  filter: function filter(aValue) {
-    let row = document.getElementById("editor-row");
-
-    let container = this._container;
-    container.scrollBoxObject.scrollTo(0, 0);
-    // Clear the list by replacing with a shallow copy
-    let empty = container.cloneNode(false);
-    empty.appendChild(row);
-    container.parentNode.replaceChild(empty, container);
-    this._container = empty;
-
-    let result = Utils.getPrefs(aValue);
-    this._container.setItems(result.map(this._createItem, this));
-  },
-
-  open: function open(aType) {
-    let buttons = document.getElementById("editor-buttons-add");
-    buttons.setAttribute("hidden", "true");
-
-    let shouldFocus = false;
-    let setting = document.getElementById("editor-setting");
-    switch (aType) {
-      case Ci.nsIPrefBranch.PREF_INT:
-        setting.setAttribute("type", "integer");
-        setting.setAttribute("min", -Infinity);
-        break;
-      case Ci.nsIPrefBranch.PREF_BOOL:
-        setting.setAttribute("type", "bool");
-        break;
-      case Ci.nsIPrefBranch.PREF_STRING:
-        setting.setAttribute("type", "string");
-        break;
-    }
-
-    setting.removeAttribute("title");
-    setting.removeAttribute("pref");
-    if (setting.input)
-      setting.input.value = "";
-
-    document.getElementById("editor-container").appendChild(this._editor);
-    let nameField = document.getElementById("editor-name");
-    nameField.value = "";
-
-    this._editor.setAttribute("hidden", "false");
-    this._currentItem = null;
-    nameField.focus();
-  },
-
-  close: function close(aValid) {
-    this._editor.setAttribute("hidden", "true");
-    let buttons = document.getElementById("editor-buttons-add");
-    buttons.setAttribute("hidden", "false");
-
-    if (aValid) {
-      let name = document.getElementById("editor-name").inputField.value;
-      if (name != "") {
-        let setting = document.getElementById("editor-setting");
-        setting.setAttribute("pref", name);
-        setting.valueToPreference();
-      }
-    }
-
-    document.getElementById("editor-container").appendChild(this._editor);
-  },
-
-  _currentItem: null,
-
-  delayEdit: function(aItem) {
-    setTimeout(this.edit.bind(this), 0, aItem);
-  },
-
-  edit: function(aItem) {
-    if (!aItem)
-      return;
-
-    let pref = Utils.getPref(aItem.getAttribute("name"));
-    if (pref.lock || !pref.name || aItem == this._currentItem)
-      return;
-
-    this.close(false);
-    this._currentItem = aItem;
-
-    let setting = document.getElementById("editor-setting");
-    let shouldFocus = false;
-    switch (pref.type) {
-      case Ci.nsIPrefBranch.PREF_BOOL:
-        setting.setAttribute("type", "bool");
-        break;
-
-      case Ci.nsIPrefBranch.PREF_INT:
-        setting.setAttribute("type", "integer");
-        setting.setAttribute("increment", this.getIncrementForValue(pref.value));
-        setting.setAttribute("min", -Infinity);
-        shouldFocus = true;
-        break;
-
-      case Ci.nsIPrefBranch.PREF_STRING:
-        setting.setAttribute("type", "string");
-        shouldFocus = true;
-        break;
-    }
-
-    setting.setAttribute("title", pref.name);
-    setting.setAttribute("pref", pref.name);
-
-    this._container.insertBefore(this._editor, aItem);
-
-    let resetButton = document.getElementById("editor-reset");
-    resetButton.setAttribute("disabled", pref.default);
-
-    this._editor.setAttribute("default", pref.default);
-    this._editor.setAttribute("hidden", "false");
-
-    if (shouldFocus && setting.input)
-      setting.input.focus();
-  },
-
-  reset: function reset(aItem) {
-    let setting = document.getElementById("editor-setting");
-    let pref = Utils.getPref(setting.getAttribute("pref"));
-    if (!pref.default)
-      Utils.resetPref(pref.name);
-  },
-
-  handleEvent: function handleEvent(aEvent) {
-    switch (aEvent.type) {
-      case "resize":
-        this._handleWindowResize();
-        break;
-
-      case "prefchange":
-      case "prefnew":
-        this._handlePrefChange(aEvent.detail, aEvent.type == "prefnew");
-        break;
-
-      case "click":
-        this._onClick();
-        break;
-    }
-  },
-
-  _handleWindowResize: function _handleWindowResize() {
-    let mainBox = document.getElementById("main-container");
-    let textbox = document.getElementById("textbox");
-    let height = window.innerHeight - textbox.getBoundingClientRect().height;
-
-    mainBox.setAttribute("height", height);
-  },
-
-  _onClick: function () {
-    // Blur the search box when tapping anywhere else in the content
-    // in order to close the soft keyboard.
-    document.getElementById("textbox").blur();
-  },
-
-  _handlePrefChange: function _handlePrefChange(aIndex, aNew) {
-    let isEditing = !this._editor.hidden;
-    let shouldUpdateEditor = false;
-    if (isEditing) {
-      let setting = document.getElementById("editor-setting");
-      let editorIndex = Utils.getPrefIndex(setting.getAttribute("pref"));
-      shouldUpdateEditor = (aIndex == editorIndex);
-      if(shouldUpdateEditor || aIndex > editorIndex)
-        aIndex += 1;
-    }
-
-    // XXX An item display value will probably fail if a pref is changed in the
-    //     background while there is a filter on the pref
-    let item = shouldUpdateEditor ? this._editor.nextSibling
-                                  : this._container.childNodes[aIndex + 1];// add 1 because of the new pref row
-    if (!item) // the pref is not viewable
-      return;
-
-    if (aNew) {
-      let pref = Utils.getPrefByIndex(aIndex);
-      let row = this._createItem(pref);
-      this._container.insertBefore(row, item);
-      return;
-    }
-
-    let pref = Utils.getPref(item.getAttribute("name"));
-    if (shouldUpdateEditor) {
-      this._editor.setAttribute("default", pref.default);
-
-      let resetButton = document.getElementById("editor-reset");
-      resetButton.disabled = pref.default;
-    }
-
-    item.setAttribute("default", pref.default);
-    item.lastChild.setAttribute("value", pref.value);
-  },
-
-  _createItem: function _createItem(aPref) {
-    let row = document.createElement("richlistitem");
-
-    row.setAttribute("name", aPref.name);
-    row.setAttribute("type", aPref.type);
-    row.setAttribute("role", "button");
-    row.setAttribute("default", aPref.default);
-
-    let label = document.createElement("label");
-    label.setAttribute("class", "preferences-title");
-    label.setAttribute("value", aPref.name);
-    label.setAttribute("crop", "end");
-    row.appendChild(label);
-
-    label = document.createElement("label");
-    label.setAttribute("class", "preferences-value");
-    label.setAttribute("value", aPref.value);
-    label.setAttribute("crop", "end");
-    row.appendChild(label);
-
-    return row;
-  },
-
-  getIncrementForValue: function getIncrementForValue(aValue) {
-    let count = 1;
-    while (aValue >= 100) {
-      aValue /= 10;
-      count *= 10;
-    }
-    return count;
-  }
-};
-
-var Utils = {
-  QueryInterface: function(aIID) {
-    if (!aIID.equals(Ci.nsIObserver) && !aIID.equals(Ci.nsISupportsWeakReference))
-      throw Components.results.NS_ERROR_NO_INTERFACE;
-    return this;
-  },
-
-  get _branch() {
-    delete this._branch;
-    this._branch = Services.prefs.getBranch(null);
-    this._branch.addObserver("", this, true);
-    return this._branch;
-  },
-
-  get _preferences() {
-    delete this._preferences;
-    let list = this._branch.getChildList("", {}).filter(function(element) {
-      return !(/^capability\./.test(element));
-    });
-    return this._preferences = list.sort().map(this.getPref, this);
-  },
-
-  getPrefs: function getPrefs(aValue) {
-    let result = this._preferences.slice();;
-    if (aValue != "") {
-      let reg = this._generateRegexp(aValue);
-      if (!reg)
-        return [];
-
-      result = this._preferences.filter(function(element, index, array) {
-        return reg.test(element.name + ";" + element.value);
-      });
-    }
-
-    return result;
-  },
-
-  getPref: function getPref(aPrefName) {
-    let branch = this._branch;
-    let pref = {
-                 name: aPrefName,
-                 value:  "",
-                 default: !branch.prefHasUserValue(aPrefName),
-                 lock: branch.prefIsLocked(aPrefName),
-                 type: branch.getPrefType(aPrefName)
-               };
-
-    try {
-      switch (pref.type) {
-        case Ci.nsIPrefBranch.PREF_BOOL:
-          pref.value = branch.getBoolPref(aPrefName).toString();
-          break;
-        case Ci.nsIPrefBranch.PREF_INT:
-          pref.value = branch.getIntPref(aPrefName).toString();
-          break;
-        default:
-        case Ci.nsIPrefBranch.PREF_STRING:
-          pref.value = branch.getComplexValue(aPrefName, Ci.nsISupportsString).data;
-          // Try in case it's a localized string (will throw an exception if not)
-          if (pref.default && /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.value))
-            pref.value = branch.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
-          break;
-      }
-    } catch (e) {}
-
-    return pref;
-  },
-
-  getPrefByIndex: function getPrefByIndex(aIndex) {
-    return this._preferences[aIndex];
-  },
-
-  getPrefIndex: function getPrefIndex(aPrefName) {
-    let prefs = this._preferences;
-    let high = prefs.length - 1;
-    let low = 0, middle, element;
-
-    while (low <= high) {
-      middle = parseInt((low + high) / 2);
-      element = prefs[middle];
-
-      if (element.name > aPrefName)
-        high = middle - 1;
-      else if (element.name < aPrefName)
-        low = middle + 1;
-      else
-        return middle;
-    }
-
-    return -1;
-  },
-
-  resetPref: function resetPref(aPrefName) {
-    this._branch.clearUserPref(aPrefName);
-  },
-
-  observe: function observe(aSubject, aTopic, aPrefName) {
-    if (aTopic != "nsPref:changed" || /^capability\./.test(aPrefName)) // avoid displaying "private" preferences
-      return;
-
-    let type = "prefchange";
-    let index = this.getPrefIndex(aPrefName);
-    if (index != - 1) {
-      // update the inner array
-      let pref = this.getPref(aPrefName);
-      this._preferences[index].value = pref.value;
-    }
-    else {
-      // XXX we could do better here
-      let list = this._branch.getChildList("", {}).filter(function(element, index, array) {
-        return !(/^capability\./.test(element));
-      });
-      this._preferences = list.sort().map(this.getPref, this);
-
-      type = "prefnew";
-      index = this.getPrefIndex(aPrefName);
-    }
-
-    let evt = document.createEvent("UIEvents");
-    evt.initUIEvent(type, true, true, window, index);
-    window.dispatchEvent(evt);
-  },
-
-  _generateRegexp: function _generateRegexp(aValue) {
-    if (aValue.charAt(0) == "/") {
-      try {
-        let rv = aValue.match(/^\/(.*)\/(i?)$/);
-        return RegExp(rv[1], rv[2]);
-      }
-      catch (e) {
-        return null; // Do nothing on incomplete or bad RegExp
-      }
-    }
-
-    return RegExp(aValue.replace(/([^* \w])/g, "\\$1").replace(/^\*+/, "")
-                        .replace(/\*+/g, ".*"), "i");
-  }
-};
-
deleted file mode 100644
--- a/browser/metro/base/content/config.xul
+++ /dev/null
@@ -1,65 +0,0 @@
-<?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://browser/skin/platform.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/skin/config.css" type="text/css"?>
-
-<!DOCTYPE window [
-<!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd">
-%configDTD;
-]>
-
-<window id="about:config"
-        onload="ViewConfig.init();"
-        onunload="ViewConfig.uninit();"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <script type="application/x-javascript" src="chrome://browser/content/config.js"/>
-
-  <vbox class="panel-dark" flex="1">
-    <textbox id="textbox"
-             oncommand="ViewConfig.filter(this.value)"
-             type="search"
-             timeout="400"
-             emptytext="&empty.label;"/>
-
-    <hbox id="main-container" class="panel-dark">
-      <richlistbox id="prefs-container" flex="1" onselect="ViewConfig.delayEdit(this.selectedItem)" batch="25">
-        <richlistitem id="editor-row">
-          <vbox id="editor-container" flex="1">
-
-            <hbox align="center" flex="1">
-              <label value="&newpref.label;" flex="1"/>
-              <spacer flex="1" />
-              <hbox id="editor-buttons-add">
-                <button label="&integer.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_INT)"/>
-                <button label="&boolean.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_BOOL)"/>
-                <button label="&string.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_STRING)"/>
-              </hbox>
-            </hbox>
-
-            <vbox id="editor" hidden="true">
-              <hbox align="center">
-                <textbox id="editor-name" emptytext="&addpref.name;" flex="1"/>
-                <setting id="editor-setting" emptytext="&addpref.value;" onlabel="true" offlabel="false" flex="1"/>
-              </hbox>
-              <hbox id="editor-buttons">
-                <button id="editor-cancel" label="&cancel.label;" oncommand="ViewConfig.close(false)"/>
-                <spacer flex="1"/>
-                <button id="editor-reset" label="&reset.label;" oncommand="ViewConfig.reset(this.parentNode.parentNode.nextSibling)"/>
-                <button id="editor-done" label="&done.label;" oncommand="ViewConfig.close(true)"/>
-              </hbox>
-            </vbox>
-
-          </vbox>
-        </richlistitem>
-      </richlistbox>
-    </hbox>
-  </vbox>
-</window>
-
copy from mobile/android/chrome/content/config.js
copy to browser/metro/base/content/pages/config.js
--- a/mobile/android/chrome/content/config.js
+++ b/browser/metro/base/content/pages/config.js
@@ -1,19 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {classes: Cc, interfaces: Ci, manager: Cm, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 
-const VKB_ENTER_KEY = 13;   // User press of VKB enter key
+const PRIVATE_PREF_PREFIX = "capability.";   // Tag to prevent exposing private preferences
 const INITIAL_PAGE_DELAY = 500;   // Initial pause on program start for scroll alignment
-const PREFS_BUFFER_MAX = 30;   // Max prefs buffer size for getPrefsBuffer()
+const PREFS_BUFFER_MAX = 100;   // Max prefs buffer size for getPrefsBuffer()
 const PAGE_SCROLL_TRIGGER = 200;     // Triggers additional getPrefsBuffer() on user scroll-to-bottom
 const FILTER_CHANGE_TRIGGER = 200;     // Delay between responses to filterInput changes
 const INNERHTML_VALUE_DELAY = 100;    // Delay before providing prefs innerHTML value
 
 let gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties");
 let gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
 
 
@@ -81,19 +81,19 @@ var NewPrefDialog = {
   // As new pref name is initially displayed, re-focused, or modifed during user input
   _updatePositiveButton: function AC_updatePositiveButton(aPrefName) {
     this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton");
     this._positiveButton.setAttribute("disabled", true);
     if (aPrefName == "") {
       return;
     }
 
-    // Avoid "private" preferences
-    if (/^capability\./.test(aPrefName)) {
-      this._positiveButton.textContent = "Private";
+    // Prevent addition of new "private" preferences
+    if (aPrefName.startsWith(PRIVATE_PREF_PREFIX)) {
+      this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.privateButton");
       return;
     }
 
     // If item already in list, it's being changed, else added
     let item = document.querySelector(".pref-item[name=" + aPrefName.quote() + "]");
     if (item) {
       this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton");
     } else {
@@ -135,17 +135,17 @@ var NewPrefDialog = {
     this._prefsShield.removeAttribute("shown");
 
     window.removeEventListener("keypress", this.handleKeypress, false);
   },
 
   // Watch user key input so we can provide Enter key action, commit input values
   handleKeypress: function AC_handleKeypress(aEvent) {
     // Close our VKB on new pref enter key press
-    if (aEvent.keyCode == VKB_ENTER_KEY)
+    if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
       aEvent.target.blur();
   },
 
   // New prefs create dialog only allows creating a non-existing preference, doesn't allow for
   // Changing an existing one on-the-fly, tap existing/displayed line item pref for that
   create: function AC_create(aEvent) {
     if (this._positiveButton.getAttribute("disabled") == "true") {
       return;
@@ -188,33 +188,32 @@ var NewPrefDialog = {
  *
  * Main AboutConfig object and methods
  *
  * Implements User Interfaces for maintenance of a list of Preference settings
  *
  */
 var AboutConfig = {
 
-  contextMenuLINode: null,
   filterInput: null,
   _filterPrevInput: null,
   _filterChangeTimer: null,
   _prefsContainer: null,
   _loadingContainer: null,
   _list: null,
 
   // Init the main AboutConfig dialog
   init: function AC_init() {
     this.filterInput = document.getElementById("filter-input");
     this._prefsContainer = document.getElementById("prefs-container");
     this._loadingContainer = document.getElementById("loading-container");
 
     let list = Services.prefs.getChildList("", {}).filter(function(aElement) {
-      // Avoid "private" preferences
-      return !(/^capability\./.test(aElement));
+      // Prevent display of "private" preferences
+      return !aElement.startsWith(PRIVATE_PREF_PREFIX);
     });
     this._list = list.sort().map( function AC_getMapPref(aPref) {
       return new Pref(aPref);
     }, this);
 
     // Display the current prefs list (retains searchFilter value)
     this.bufferFilterInput();
 
@@ -253,33 +252,33 @@ var AboutConfig = {
     this.filterInput.setAttribute("value", this.filterInput.value);
 
     // Don't start new filter search if same as last
     if (this.filterInput.value == this._filterPrevInput) {
       return;
     }
     this._filterPrevInput = this.filterInput.value;
 
-    // Clear list item selection / context menu, prefs list, get first buffer, set scrolling on
+    // Clear list item selection and prefs list, get first buffer, set scrolling on
     this.selected = "";
     this._clearPrefsContainer();
     this._addMorePrefsToContainer();
     window.onscroll = this.onScroll.bind(this);
 
     // Pause for screen to settle, then ensure at top
     setTimeout((function() {
       window.scrollTo(0, 0);
     }).bind(this), INITIAL_PAGE_DELAY);
   },
 
   // Clear the displayed preferences list
   _clearPrefsContainer: function AC_clearPrefsContainer() {
     // Quick clear the prefsContainer list
     let empty = this._prefsContainer.cloneNode(false);
-    this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer); 
+    this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer);
     this._prefsContainer = empty;
 
     // Quick clear the prefs li.HTML list
     this._list.forEach(function(item) {
       delete item.li;
     });
   },
 
@@ -325,17 +324,16 @@ var AboutConfig = {
   onScroll: function AC_onScroll(aEvent) {
     if (this._prefsContainer.scrollHeight - (window.pageYOffset + window.innerHeight) < PAGE_SCROLL_TRIGGER) {
       if (!this._filterChangeTimer) {
         this._addMorePrefsToContainer();
       }
     }
   },
 
-
   // Return currently selected list item node
   get selected() {
     return document.querySelector(".pref-item.selected");
   },
 
   // Set list item node as selected
   set selected(aSelection) {
     let currentSelection = this.selected;
@@ -353,17 +351,17 @@ var AboutConfig = {
     if (aSelection) {
       aSelection.classList.add("selected");
       aSelection.addEventListener("keypress", this.handleKeypress, false);
     }
   },
 
   // Watch user key input so we can provide Enter key action, commit input values
   handleKeypress: function AC_handleKeypress(aEvent) {
-    if (aEvent.keyCode == VKB_ENTER_KEY)
+    if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
       aEvent.target.blur();
   },
 
   // Return the target list item node of an action event
   getLINodeForEvent: function AC_getLINodeForEvent(aEvent) {
     let node = aEvent.target;
     while (node && node.nodeName != "li") {
       node = node.parentNode;
@@ -458,18 +456,18 @@ var AboutConfig = {
 
     pref.value += aInt;
   },
 
   // Observe preference changes
   observe: function AC_observe(aSubject, aTopic, aPrefName) {
     let pref = new Pref(aPrefName);
 
-    // Ignore uninteresting changes, and avoid "private" preferences
-    if ((aTopic != "nsPref:changed") || /^capability\./.test(pref.name)) {
+    // Ignore uninteresting preference changes, and external changes to "private" preferences
+    if ((aTopic != "nsPref:changed") || pref.name.startsWith(PRIVATE_PREF_PREFIX)) {
       return;
     }
 
     // If pref type invalid, refresh display as user reset/removed an item from the list
     if (pref.type == Services.prefs.PREF_INVALID) {
       document.location.reload();
       return;
     }
@@ -485,26 +483,16 @@ var AboutConfig = {
     item.setAttribute("value", pref.value);
     let input = item.querySelector("input");
     input.setAttribute("value", pref.value);
     input.value = pref.value;
 
     pref.default ?
       item.querySelector(".reset").setAttribute("disabled", "true") :
       item.querySelector(".reset").removeAttribute("disabled");
-  },
-
-  // Quick context menu helpers for about:config
-  clipboardCopy: function AC_clipboardCopy(aField) {
-    let pref = this._getPrefForNode(this.contextMenuLINode);
-    if (aField == 'name') {
-      gClipboardHelper.copyString(pref.name);
-    } else {
-      gClipboardHelper.copyString(pref.value);
-    }
   }
 }
 
 
 /* ============================== Pref ==============================
  *
  * Individual Preference object / methods
  *
@@ -528,16 +516,17 @@ Pref.prototype = {
       case Services.prefs.PREF_INT:
         return Services.prefs.getIntPref(this.name);
       case Services.prefs.PREF_STRING:
       default:
         return Services.prefs.getCharPref(this.name);
     }
 
   },
+
   set value(aPrefValue) {
     switch (this.type) {
       case Services.prefs.PREF_BOOL:
         Services.prefs.setBoolPref(this.name, aPrefValue);
         break;
       case Services.prefs.PREF_INT:
         Services.prefs.setIntPref(this.name, aPrefValue);
         break;
@@ -574,26 +563,16 @@ Pref.prototype = {
       // Click callback to ensure list item selected even on no-action tap events
       this.li.addEventListener("click",
         function(aEvent) {
           AboutConfig.selected = AboutConfig.getLINodeForEvent(aEvent);
         },
         false
       );
 
-      // Contextmenu callback to identify selected list item
-      this.li.addEventListener("contextmenu",
-        function(aEvent) {
-          AboutConfig.contextMenuLINode = AboutConfig.getLINodeForEvent(aEvent);
-        },
-        false
-      );
-
-      this.li.setAttribute("contextmenu", "prefs-context-menu");
-
       // Create list item outline, bind to object actions
       this.li.innerHTML =
         "<div class='pref-name' " +
             "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" +
             this.name +
         "</div>" +
         "<div class='pref-item-line'>" +
           "<input class='pref-value' value='' " +
@@ -656,9 +635,8 @@ Pref.prototype = {
     }
 
     if (this.locked) {
       valDiv.setAttribute("disabled", this.locked);
       this.li.querySelector(".pref-name").setAttribute("locked", true);
     }
   }
 }
-
copy from mobile/android/chrome/content/config.xhtml
copy to browser/metro/base/content/pages/config.xhtml
--- a/mobile/android/chrome/content/config.xhtml
+++ b/browser/metro/base/content/pages/config.xhtml
@@ -1,86 +1,81 @@
 <?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/. -->
 
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
-<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
+<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
 %globalDTD;
 <!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd">
 %configDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 
 <head>
-    <meta name="viewport" content="width=device-width; user-scalable=0" />
+    <meta name="viewport" content="width=device-width; user-scalable=false" />
 
     <link rel="stylesheet" href="chrome://browser/skin/config.css" type="text/css"/>
-    <script type="text/javascript;version=1.8" src="chrome://browser/content/config.js"></script>
+    <script type="text/javascript;version=1.8" src="chrome://browser/content/pages/config.js"/>
 </head>
 
 <body dir="&locale.dir;" onload="NewPrefDialog.init(); AboutConfig.init();"
                          onunload="AboutConfig.uninit();">
 
     <div class="toolbar">
         <div class="toolbar-container">
             <div id="new-pref-toggle-button" onclick="NewPrefDialog.toggleShowHide();"/>
 
             <div class="toolbar-item" id="filter-container">
                 <div id="filter-search-button"/>
-                <input id="filter-input" type="search" placeholder="&toolbar.searchPlaceholder;" value=""
+                <input id="filter-input" type="search" placeholder="&empty.label;" value=""
                     oninput="AboutConfig.bufferFilterInput();"/>
                 <div id="filter-input-clear-button" onclick="AboutConfig.clearFilterInput();"/>
             </div>
         </div>
     </div>
 
     <div id="content" ontouchstart="AboutConfig.filterInput.blur();">
 
         <div id="new-pref-container">
             <li class="pref-item" id="new-pref-item">
                 <div class="pref-item-line">
-                    <input class="pref-name" id="new-pref-name" type="text" placeholder="&newPref.namePlaceholder;"
+                    <input class="pref-name" id="new-pref-name" type="text" placeholder="&addpref.name;"
                         onfocus="NewPrefDialog.focusName(event);"
                         oninput="NewPrefDialog.updateName(event);"/>
                     <select class="pref-value" id="new-pref-type" onchange="NewPrefDialog.type = event.target.value;">
-                        <option value="boolean">&newPref.valueBoolean;</option>
-                        <option value="string">&newPref.valueString;</option>
-                        <option value="int">&newPref.valueInteger;</option>
+                        <option value="boolean">&boolean.label;</option>
+                        <option value="string">&string.label;</option>
+                        <option value="int">&integer.label;</option>
                     </select>
                 </div>
 
                 <div class="pref-item-line" id="new-pref-line-boolean">
                     <input class="pref-value" id="new-pref-value-boolean" disabled="disabled"/>
-                    <div class="pref-button toggle" onclick="NewPrefDialog.toggleBoolValue();">&newPref.toggleButton;</div>
+                    <div class="pref-button toggle" onclick="NewPrefDialog.toggleBoolValue();">&toggle.label;</div>
                 </div>
 
                 <div class="pref-item-line">
-                    <input class="pref-value" id="new-pref-value-string" placeholder="&newPref.stringPlaceholder;"/>
-                    <input class="pref-value" id="new-pref-value-int" placeholder="&newPref.numberPlaceholder;" type="number"/>
+                    <input class="pref-value" id="new-pref-value-string" placeholder="&string.placeholder;"/>
+                    <input class="pref-value" id="new-pref-value-int" placeholder="&number.placeholder;" type="number"/>
                 </div>
 
                 <div class="pref-item-line">
-                    <div class="pref-button cancel" id="negative-button" onclick="NewPrefDialog.hide();">&newPref.cancelButton;</div>
+                    <div class="pref-button cancel" id="negative-button" onclick="NewPrefDialog.hide();">&cancel.label;</div>
                     <div class="pref-button create" id="positive-button" onclick="NewPrefDialog.create(event);"></div>
                 </div>
             </li>
         </div>
 
         <div id="prefs-shield"></div>
 
         <ul id="prefs-container"/>
 
         <ul id="loading-container"><li></li></ul>
 
     </div>
 
-    <menu type="context" id="prefs-context-menu">
-        <menuitem label="&contextMenu.copyPrefName;" onclick="AboutConfig.clipboardCopy('name');"></menuitem>
-        <menuitem label="&contextMenu.copyPrefValue;" onclick="AboutConfig.clipboardCopy('value');"></menuitem>
-    </menu>
-
 </body>
 </html>
--- a/browser/metro/base/jar.mn
+++ b/browser/metro/base/jar.mn
@@ -1,21 +1,22 @@
 #filter substitution
 # 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/.
 
 chrome.jar:
 % content browser %content/
 
+  content/aboutAddons.xhtml                    (content/pages/aboutAddons.xhtml)
   content/aboutCertError.xhtml                 (content/pages/aboutCertError.xhtml)
   content/aboutRights.xhtml                    (content/pages/aboutRights.xhtml)
   content/blockedSite.xhtml                    (content/pages/blockedSite.xhtml)
+  content/config.xhtml                         (content/pages/config.xhtml)
   content/netError.xhtml                       (content/pages/netError.xhtml)
-  content/aboutAddons.xhtml                    (content/pages/aboutAddons.xhtml)
 #ifdef MOZ_CRASHREPORTER
   content/crashprompt.xhtml                    (content/pages/crashprompt.xhtml)
 #endif
 
   content/bindings/bindings.xml                (content/bindings/bindings.xml)
   content/bindings/tabs.xml                    (content/bindings/tabs.xml)
   content/bindings/toggleswitch.xml            (content/bindings/toggleswitch.xml)
   content/bindings/browser.xml                 (content/bindings/browser.xml)
@@ -59,18 +60,17 @@ chrome.jar:
   content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js)
   content/contenthandlers/Content.js           (content/contenthandlers/Content.js)
 
   content/library/SelectionPrototype.js        (content/library/SelectionPrototype.js)
 
   content/ContentAreaObserver.js               (content/ContentAreaObserver.js)
   content/BrowserTouchHandler.js               (content/BrowserTouchHandler.js)
 * content/WebProgress.js                       (content/WebProgress.js)
-  content/config.xul                           (content/config.xul)
-  content/config.js                            (content/config.js)
+  content/pages/config.js                      (content/pages/config.js)
 * content/browser.xul                          (content/browser.xul)
   content/browser.js                           (content/browser.js)
 * content/browser-ui.js                        (content/browser-ui.js)
 * content/browser-scripts.js                   (content/browser-scripts.js)
   content/ContextCommands.js                   (content/ContextCommands.js)
   content/commandUtil.js                       (content/commandUtil.js)
   content/appbar.js                            (content/appbar.js)
   content/shell.xul                            (content/jsshell/shell.xul)
@@ -98,11 +98,11 @@ chrome.jar:
   content/BookmarksView.js                     (content/startui/BookmarksView.js)
   content/HistoryView.js                       (content/startui/HistoryView.js)
   content/TopSitesView.js                      (content/startui/TopSitesView.js)
   content/FirstRunOverlay.xul                  (content/startui/FirstRunOverlay.xul)
 #ifdef MOZ_SERVICES_SYNC
   content/RemoteTabsView.js                    (content/startui/RemoteTabsView.js)
 #endif
 
-% override chrome://global/content/config.xul chrome://browser/content/config.xul
+% override chrome://global/content/config.xul chrome://browser/content/config.xhtml
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
 % override chrome://mozapps/content/extensions/extensions.xul chrome://browser/content/aboutAddons.xhtml
--- a/browser/metro/locales/en-US/chrome/config.dtd
+++ b/browser/metro/locales/en-US/chrome/config.dtd
@@ -1,16 +1,16 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY empty.label            "Search">
-<!ENTITY newpref.label          "Add a New Preference">
 <!ENTITY addpref.name           "Name">
-<!ENTITY addpref.value          "Value">
 
 <!ENTITY cancel.label           "Cancel">
-<!ENTITY reset.label            "Reset">
-<!ENTITY done.label             "Done">
+<!ENTITY toggle.label           "Toggle">
 
 <!ENTITY integer.label          "Integer">
 <!ENTITY string.label           "String">
 <!ENTITY boolean.label          "Boolean">
+
+<!ENTITY string.placeholder     "Enter a string">
+<!ENTITY number.placeholder     "Enter a number">
new file mode 100644
--- /dev/null
+++ b/browser/metro/locales/en-US/chrome/config.properties
@@ -0,0 +1,10 @@
+# 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/.
+
+newPref.createButton=Create
+newPref.privateButton=Private
+newPref.changeButton=Change
+
+pref.toggleButton=Toggle
+pref.resetButton=Reset
--- a/browser/metro/locales/jar.mn
+++ b/browser/metro/locales/jar.mn
@@ -7,18 +7,19 @@
 # Metro jar resources
 #
 
 @AB_CD@.jar:
 % locale browser @AB_CD@ %locale/browser/
   locale/browser/aboutCertError.dtd       (%chrome/aboutCertError.dtd)
   locale/browser/browser.dtd              (%chrome/browser.dtd)
   locale/browser/browser.properties       (%chrome/browser.properties)
+  locale/browser/config.dtd               (%chrome/config.dtd)
+  locale/browser/config.properties        (%chrome/config.properties)
   locale/browser/region.properties        (%chrome/region.properties)
-  locale/browser/config.dtd               (%chrome/config.dtd)
   locale/browser/preferences.dtd          (%chrome/preferences.dtd)
   locale/browser/aboutPanel.dtd           (%chrome/aboutPanel.dtd)
   locale/browser/searchPanel.dtd          (%chrome/searchPanel.dtd)
   locale/browser/checkbox.dtd             (%chrome/checkbox.dtd)
   locale/browser/sync.dtd                 (%chrome/sync.dtd)
   locale/browser/sync.properties          (%chrome/sync.properties)
   locale/browser/passwordmgr.properties   (%chrome/passwordmgr.properties)
   locale/browser/phishing.dtd             (%chrome/phishing.dtd)
--- a/browser/metro/theme/config.css
+++ b/browser/metro/theme/config.css
@@ -1,97 +1,345 @@
 /* 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/. */
 
-@media (max-width: 499px) {
-  #editor-container > hbox {
-    -moz-box-orient: vertical;
-  }
+html,
+body {
+    margin: 0;
+    padding: 0;
+    background-color: #ced7de;
+    -moz-user-select: none;
+    font-family: "Segoe UI", sans-serif;
+    -moz-text-size-adjust: none;
 }
 
-richlistitem {
-  -moz-box-align: center;
+.toolbar {
+    width: 100%;
+    height: 3em;
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 10;
+    box-shadow: 0 0 3px #444;
+    background-color: #ced7de;
+    color: #000000;
+    font-weight: bold;
+    border-bottom: 2px solid;
+    -moz-border-bottom-colors: #ff9100 #f27900;
+}
+
+.toolbar-container {
+    max-width: 40em;
+    margin-left: auto;
+    margin-right: auto;
 }
 
-richlistitem .preferences-title {
-  pointer-events: none;
-  min-width: 200px;
-  -moz-box-flex: 1;
-  margin-right: 8px;
+#filter-container {
+    margin-top: 0.5em;
+    margin-bottom: 0.5em;
+    margin-right: 0.5em;
+    height: 2em;
+    border: 1px solid transparent;
+    border-image-source: url("chrome://browser/skin/images/textfield.png");
+    border-image-slice: 1 1 3 1;
+    border-image-width: 1px 1px 3px 1px;
+    overflow: hidden;
+    display: flex;
+    flex-direction: row;
 }
 
-/* XXX look  + sync */
-richlistitem[default="false"] .preferences-title {
-  font-weight: bold;
+#filter-input {
+    -moz-appearance: none;
+    border: none;
+    background-image: none;
+    background-color: transparent;
+    display: inline-block;
+    width: 12em;
+    min-width: 0;
+    color: #000000;
+    opacity: 1;
+    flex: 1 1 auto;
+}
+
+#filter-input:-moz-placeholder {
+    color: rgba(255,255,255,0.5);
+}
+
+.toolbar input {
+    display: inline-block;
+    height: 100%;
+    min-width: 3em;
+    -moz-box-sizing: border-box;
+    opacity: 0.75;
+}
+
+#new-pref-toggle-button {
+    background-position: center center;
+    background-image: url("chrome://browser/skin/images/reader-plus-icon-xhdpi.png");
+    background-size: 48px 48px;
+    height: 48px;
+    width: 48px;
+    display: inline-block;
+    outline-style: none;
 }
 
-richlistitem .preferences-value {
-  min-width: 200px;
-  pointer-events: none;
-  -moz-box-flex: 4;
-  text-align: end;
-  color: grey;
+#filter-search-button {
+    background-image: url("chrome://browser/skin/images/search.png");
+    background-size: 32px 32px;
+    height: 32px;
+    width: 32px;
+    display: inline-block;
+    outline-style: none;
+}
+
+#filter-input-clear-button {
+    background-image: url("chrome://browser/skin/images/search-clear-30.png");
+    background-size: 32px 32px;
+    height: 32px;
+    width: 32px;
+    display: inline-block;
+    outline-style: none;
+}
+
+#filter-input[value=""] + #filter-input-clear-button {
+    display: none;
+}
+
+.toolbar-item {
+    display: inline-block;
+    height: 3em;
+    min-width: 3em;
+    float: right;
+}
+
+#content {
+    position: relative;
+    margin: 0;
+    margin-left: auto;
+    margin-right: auto;
+    padding-top: 3em;
+    padding-left: 0;
+    padding-right: 0;
+    min-height: 100%;
+    max-width: 40em;
 }
 
-/* Editor */
-#editor-row {
-  padding: 0;
-  background: #E9E9E9;
+ul {
+    list-style-position: inside;
+    border: 1px solid #808080;
+    background-color: #ffffff;
+    min-height: 100%;
+    width: 100%;
+    padding-top: 0;
+    margin: 0;
+    padding-left: 0;
+    -moz-box-sizing: border-box;
+    box-shadow: 0 0 5px #000000;
+    overflow-x: hidden;
 }
 
-#editor {
-  border-bottom: 1px solid rgb(207,207,207);
+#new-pref-container {
+    width: 100%;
+    margin: 0;
+    background-color: #ffffff;
+    -moz-box-sizing: border-box;
+    box-shadow: 0 0 5px #000000;
+    overflow-x: hidden;
+    max-width: 40em;
+    max-height: 100%;
+    position: fixed;
+    top: 3em;
+    left: auto;
+    display: none;
+    z-index: 5;
 }
 
-#editor > hbox > #editor-name,
-#editor > hbox > #editor-cancel,
-#editor > hbox > #editor-done {
-  display: none;
+#new-pref-container input,
+#new-pref-container select {
+    border: none;
+    background-image: none;
+}
+
+#new-pref-container.show {
+    display: block;
+}
+
+li {
+    list-style-type: none;
+    border-bottom: 1px solid #d3d3d3;
+    opacity: 1;
+    background-color: #ffffff;
+    cursor: pointer;
 }
 
-#editor-container > #editor > hbox > #editor-name,
-#editor-container > #editor > hbox > #editor-cancel,
-#editor-container > #editor > hbox > #editor-done {
-  display: -moz-box;
+#new-pref-line-boolean,
+#new-pref-value-string,
+#new-pref-value-int {
+    display: none;
+}
+#new-pref-item[typestyle="boolean"] #new-pref-line-boolean,
+#new-pref-item[typestyle="string"] #new-pref-value-string,
+#new-pref-item[typestyle="int"] #new-pref-value-int {
+    display: block;
+}
+
+.pref-name,
+.pref-value {
+    padding: 15px 10px;
+    text-align: left;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    background-image: none;
 }
 
-#editor-container > #editor > hbox > #editor-reset {
-  display: none;
+.pref-value {
+    color: rgba(0,0,0,0.5);
+    flex: 1 1 auto;
+    border: none;
+    -moz-appearance: none;
+    background-image: none;
+    background-color: transparent;
+}
+
+.pref-name[locked] {
+    padding-right: 20px;
+    background-image: url("chrome://browser/skin/images/lock.png");
+    background-repeat: no-repeat;
+    background-position: right 50%;
+    background-size: auto 60%;
+}
+
+#new-pref-name {
+    width: 30em;
 }
 
-#editor-container > hbox > label {
-  pointer-events: none;
-  color: black;
+#new-pref-type {
+    display: inline-block !important;
+    border-left: 1px solid #d3d3d3;
+    width: 10em;
+    text-align: right;
+}
+
+.pref-item-line {
+    border-top: 1px solid rgba(0,0,0,0.05);
+    color: rgba(0,0,0,0.5);
+    display: flex;
+    flex-direction: row;
+}
+
+#new-pref-value-boolean {
+    flex: 1 1 auto;
 }
 
-#editor + richlistitem {
-  display: none;
+/* Disable newPref dialog spinbuttons, use custom version from Android */
+/* Filed Bug 962359 to enhance the default spinbutton style to be touch-friendly */
+#new-pref-value-int {
+    -moz-appearance: textfield;
 }
 
-#editor[default="false"] .preferences-title {
-  font-weight: bold;
+#new-pref-container .pref-button.toggle {
+    display: inline-block;
+    opacity: 1;
+    flex: 0 1 auto;
+    float: right;
+}
+
+#new-pref-container .pref-button.cancel,
+#new-pref-container .pref-button.create {
+    display: inline-block;
+    opacity: 1;
+    flex: 1 1 auto;
+}
+
+.pref-item-line {
+    pointer-events: none;
 }
 
-#editor-setting setting {
-  border-color: transparent !important;
+#new-pref-container .pref-item-line,
+.pref-item.selected .pref-item-line,
+.pref-item:not(.selected) .pref-button.reset {
+    pointer-events: auto;
+}
+
+#new-pref-container .pref-button.create[disabled] {
+    color: #d3d3d3;
+}
+
+.pref-item.selected {
+    background-color: rgba(0,0,255,0.05);
 }
 
-#editor-setting[type="string"] .setting-input {
-  -moz-box-flex: 4;
+.pref-button {
+    display: inline-block;
+    -moz-box-sizing: border-box;
+    text-align: center;
+    padding: 10px 1em;
+    border-left: 1px solid rgba(0,0,0,0.1);
+    opacity: 0;
+    transition-property: opacity;
+    transition-duration: 500ms;
 }
 
-#editor-setting[type="string"] .setting-input > textbox {
-  -moz-box-flex: 1;
+.pref-item.selected .pref-item-line .pref-button {
+    opacity: 1;
+}
+
+.pref-item:not(.selected) .pref-item-line .pref-button:not(.reset) {
+    display: none;
+}
+
+.pref-item:not(.selected) .pref-button.reset {
+    opacity: 1;
+}
+
+/* Disable detail list item spinbuttons, use custom version from Android */
+/* Filed Bug 962359 to enhance the default spinbutton style to be touch-friendly */
+.pref-item input[type="number"] {
+    -moz-appearance: textfield;
 }
 
-/* bug 647650: keep 'text-align: right' here instead of using start/end since 
- * the field should looks like ltr as much as possible
- */
-#editor-setting[type="string"] .setting-input > textbox:-moz-locale-dir(rtl) {
-  direction: ltr;
-  text-align: right;
+.pref-button:active {
+    background-color: rgba(0,0,255,0.2);
+}
+
+.pref-button[disabled] {
+    display: none;
+}
+
+.pref-button.up {
+    background-image: url("chrome://browser/skin/images/arrowup-16.png");
+    background-position: center center;
+    background-repeat: no-repeat;
+}
+
+.pref-button.down {
+    background-image: url("chrome://browser/skin/images/arrowdown-16.png");
+    background-position: center center;
+    background-repeat: no-repeat;
 }
 
-#editor-buttons {
-  margin: 2px;
+#prefs-shield {
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0,0,0,0.5);
+    position: fixed;
+    top: 0;
+    left: 0;
+    opacity: 0;
+    transition-property: opacity;
+    transition-duration: 500ms;
+    display: none;
 }
 
+#prefs-shield[shown] {
+    display: block;
+    opacity: 1;
+}
+
+#loading-container > li {
+    background-image: url(chrome://global/skin/media/throbber.png);
+    background-position: center center;
+    background-repeat: no-repeat;
+    padding-left: 40px;
+    height: 3em;
+    width: 100%;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5ff3654d317252ce95b4d62b168cf9893dfe9487
GIT binary patch
literal 636
zc%17D@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE)4(M`_JqL@;D1TB8!2v
z2N=7Z%(epwmK8Xr!}Kz=|C%$Gfq}8b)5S5w<M`dH8}lv)h_F3)f6Fhbv%`hQK+*6y
zr<O#dkx1uU_6Lkc3LDGUgk1Y#C=g_>7PlaHnqQ(5$4c+O&K%<#TV;-$zdR7smvYkT
z`H%ho>;EmzmaA#`_oZfY)S;KPjI;WME9ZKN8cxtTq!W0G*XE#pw&S7~`ERW4HG1am
z*s`VM<&g+QlQ-WCIK0ls&d^e+c(ZtWT9}h+ih8q^*s?rb8S8BY*;yCw?>MV4wK}tn
z-_G;n(MvODhVS#7eb7ykMdsltOCip4u~%>EUjL<PbNEzmLay$PjfXDV3PqhRF89l5
zyA`r(-DSH5Eld5-+2OY3!TCmaLthz5Nc?^&z}(L>mE)hp1A)mux0bdvhP>N)eqC?D
zlj_xGhpONA>@B?bZBh2)fHWo7v`Z^xPb_H*<Xo^~YI{`i?K@dYNAns^HDB11f9+72
z*|t;P`~EX5us(A3@8#&boEORxt*c^QFg^L9+sp98?e1@d*EN5xcE9@8?cwofh4uf?
zE$3FfZ?4@Za-`0P-{c>Yk4OB<{aX&S0V77W#5JNMC9x#cD!C{XNHG{07#ivtnCKcB
zh8S2{85&ucTIw2@TNxNszW-^7q9HdwB{QuOw+4<)j;%lqk{}Ji`DrEPiAAXljw$&`
gsS2LCiRr09sfj6-g(p*OfQlGAUHx3vIVCg!08Rt~{{R30
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..abfdbec4b9a6a76418e2d311d739d9d1781436aa
GIT binary patch
literal 274
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUto1QL?Ar-gY-caN_<RIYoP)R^Q
zNRL6T$wkRPhN*ppV^NEio&(d$Lkk=aFnUyRu{2dP9M%u;y=;=Wxn#2Xyg7G&p6Tj6
zqw|6R1RQJ`x+`YpFG<T<G)qfsWm&_{D(&B8JiG-#(^d-$JqTiW!30*vP|y5g(mbhf
z9c>05?Q?u{m`gI&#GSG!E#Tdu_4&qSQ-$DX#@8FxrXM(Kx?pWs*78eSwp`P0h0pN~
znV&8KG2sVe0iW!KbGr56y%QL8r>+Rey%ON6`f)EGM4II$yO!~@G#kdxq9CrPtDnm{
Hr-UW|1HD{P
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8bab39a08d350a1915c2fd1ae871333be1c9dd72
GIT binary patch
literal 476
zc$@*;0VDp2P)<h;3K|Lk000e1NJLTq0015U0015c1^@s6J20-I0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzen~_@RCwCtm%nbpFc8KAVqmI52b3(G
zSfdUMEmB{hec1LDDzzOLD%MOaq!|EZVru$XGLmaue0TXH1}uFNN}T)o&Uf}Ps_Qzs
z#27~x8k6&nhsP&wZVaW+9J=w|RZt1-zCPcN67c98>Hs)_7SK%S%mK==fTAtfY(oa!
zwT$K#+F${{nmwLEs}9h}e!GH0Qz_UyTSB)z<dCtk1bi$3XFWk(c*cQ~j^S+#JG@TF
za<XILe}Uw9bxQ6fWSyC7Y!{TrF;_CKVa+=`W>`#2fm3R>zj6xTKMO=ArNHy1rH0@N
z=s$*gfIM><!iAH|0c+z;eMy82OQ4-@Ckmj2wgO9%OAD^L?6DADQQ%SoHQ|+%nm0-c
zywgB6bxtu{@X=o4>cc?@Ke*kApIrHX<&<+B$o&8})E3@;bLF&w_B}%P)+SrWrbk-$
z1UEL;RNvKffSjNY+px6`udxB>DcDyxcy9LPXNdN8A}~}~4b#wH&3~sq0R{jkX|XC4
S*loiA0000<MNUMnLSTZjHO+_s
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2369d03f336bb909b9c6d19d39b114737bdca59a
GIT binary patch
literal 858
zc%17D@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE)4(M`_JqL@;D1TB8!2v
z2N=7Z%(epwmK8Xr!}Kz=|C%!wq|MXCF~sBe*{kPQhlYx<e>i{d_g%J6ZoEE*Cdz9$
z)|zuXUDe3p$SN3Qa-~u5riz7y_Z$;WOJ>n4^^X)ko|zc+rCHpgS)iV^X>!4Y8J89u
z-240u+w++FFZWf?KOea6<jwTCd-uIQUcR>U$9`3xAhEf{KUElxdWf@1I)<IrW^7G;
zw$f13eU`$iWzH>zpI2-<cKhDfMYnjl_nC`ay{vF(#`ja~em7Q>`F~pTVAbKeNy}UX
zt)DL`I%0WH@%7s8b9(!3JaLy_z2x|n1s+<z&+dNW%->=Z%yN@kui~a(xJ#*}<bsS>
zdynZ}H2eNFM3?*I0qu3aj?NPe(wP0cdS3aNT3hC=I;(lV-1)Ng?!A;CsgHpydzx8!
zG^Z-(i|#uZt+ws%iVjn=-LKETJ1%6Dm;CyPQd#7RCI9!7|IAdH$#&t|gK1mMOd7?!
z?+Ql0zaGY*G0WlnW92g@yOLfWV~oiYj%_^lz<hRYvAh3THxb@1f8WgcyU1qNB+p)v
ztKWVcEPcQGZdrZE>)G4S*-cliv~)S0<2db3;bC<<)7SdUo45VG<hi-YT&F_AgzvZ5
zapf00m*)hRon+dSaIY}^%+8fX7sVfU_VgDAamH=G?!D=?-Y;3{n)L34lFMuF-IbUu
z?iE%p=H>J@yqL|2`{GUhx$ok(tni<vx>EjkV9v_Zaa)6@g_K3Sj!tx9xx7U6=a%z4
zs?N6YmRc)MZ48^2c5ln&#PqWgt3KQ^l3IRx(Z3hZno7%NeC&7rml1YOi@{3ZV&(ZC
zw-2USo8H~fC1vBK!tvImMO8{Zy4mg4*$JwiId!ICrw>I~PG^`e^i%xGi+vlocCk+c
zCOg#<*NBpo#FA92<f2p{#b9J$XsByosB2^qVq|1xVs2$>tZQIlWni$xujnL-hTQy=
z%(P0}8m<bTDg|ng1ZfD)Pb(=;EJ|f?Ovz75Rq)JBOiv9;O-!jQJeg_(RK(!v>gTe~
HDWM4fVe@hd
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..436853fa1ea73eb25a3b42f2bd1744fe7f523a17
GIT binary patch
literal 166
zc%17D@N?(olHy`uVBq!ia0vp^%s|Y-!3HF~bz9j%oMI=>5Dp-y;YjHK@;M7UB8!3Q
zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt;-)E{-7_
zGks5N<Yi#sIK1J)fBTaYQ=JYiUaqU2#dxJneLDkpb~6uaBLBUgK;;acu6{1-oD!M<
D{>&{{
--- a/browser/metro/theme/jar.mn
+++ b/browser/metro/theme/jar.mn
@@ -145,8 +145,15 @@ chrome.jar:
   skin/images/autoscroll.png                (images/autoscroll.png)
 
   skin/images/arrow-top.png                 (images/arrow-top.png)
   skin/images/arrow-top@1.4x.png            (images/arrow-top@1.4x.png)
   skin/images/arrow-top@1.8x.png            (images/arrow-top@1.8x.png)
   skin/images/arrow-left.png                (images/arrow-left.png)
   skin/images/arrow-left@1.4x.png           (images/arrow-left@1.4x.png)
   skin/images/arrow-left@1.8x.png           (images/arrow-left@1.8x.png)
+
+# AboutConfig specific:
+  skin/images/textfield.png                 (images/textfield.png)
+  skin/images/reader-plus-icon-xhdpi.png    (images/reader-plus-icon-xhdpi.png)
+  skin/images/search.png                    (images/search.png)
+  skin/images/search-clear-30.png           (images/search-clear-30.png)
+  skin/images/lock.png                      (images/lock.png)
--- a/browser/themes/linux/customizableui/panelUIOverlay.css
+++ b/browser/themes/linux/customizableui/panelUIOverlay.css
@@ -2,8 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include ../../shared/customizableui/panelUIOverlay.inc.css
 
 #BMB_bookmarksPopup > menuitem[type="checkbox"] {
   -moz-appearance: none !important; /* important, to override toolkit rule */
 }
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+  -moz-appearance: none;
+  border: 0;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+  -moz-margin-start: 0;
+}
--- a/browser/themes/osx/customizableui/panelUIOverlay.css
+++ b/browser/themes/osx/customizableui/panelUIOverlay.css
@@ -40,26 +40,29 @@
 
   #PanelUI-customize:hover:active,
   #PanelUI-help:not([disabled]):hover:active,
   #PanelUI-quit:not([disabled]):hover:active {
     -moz-image-region: rect(0, 96px, 32px, 64px);
   }
 }
 
-.panel-wide-item[cui-areatype="menu-panel"] > toolbarbutton,
-toolbarbutton[cui-areatype="menu-panel"] {
+.panelUI-grid .toolbarbutton-1 {
   margin-right: 0;
   margin-left: 0;
   margin-bottom: 0;
 }
 
 #BMB_bookmarksPopup > menu,
 #BMB_bookmarksPopup > menuitem {
   padding-top: 5px;
   padding-bottom: 5px;
 }
 
 /* Override OSX-specific toolkit styles for the bookmarks panel */
 #BMB_bookmarksPopup > menuitem > .menu-accel-container,
 #BMB_bookmarksPopup > menu > .menu-right {
   -moz-margin-end: 0;
 }
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+  -moz-margin-start: 4px;
+}
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -1,13 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Customization mode */
+#main-window:-moz-any([customize-entering],[customize-exiting]) #tab-view-deck {
+  pointer-events: none;
+}
+
 #nav-bar[customize-entered] > #nav-bar-customization-target {
   margin: 1px 3px;
 }
 
 #nav-bar[customize-entered] > #nav-bar-customization-target,
 #PanelUI-contents > .panel-customization-placeholder {
   outline: 1px dashed transparent;
 }
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -63,24 +63,30 @@
   box-shadow: none;
 }
 
 #PanelUI-contents {
   padding: .5em 0;
 }
 
 toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > .toolbarbutton-text,
-#bookmarks-menu-button > toolbarbutton > .toolbarbutton-text,
-:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbaritem > toolbarbutton > .toolbarbutton-text,
-:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbaritem > toolbarbutton > .toolbarbutton-text,
-:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbarbutton > .toolbarbutton-text,
-:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarbutton > .toolbarbutton-text {
+.panelUI-grid .panel-combined-button > .toolbarbutton-text,
+.widget-overflow-list .toolbarbutton-menubutton-button > .toolbarbutton-text,
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-text {
   font-size: @panelTextSize@;
 }
 
+.panelUI-grid .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-multiline-text {
+  font-size: @panelTextSize@;
+  margin: 2px 0 0;
+  text-align: center;
+  -moz-hyphens: auto;
+}
+
 #wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls,
 #wrapper-zoom-controls:-moz-any([place="palette"],[place="panel"]) > #zoom-controls {
   -moz-margin-start: 0;
 }
 
 #PanelUI-contents,
 .panel-mainview:not([panelid="PanelUI-popup"]) {
   max-width: @menuPanelWidth@;
@@ -192,26 +198,36 @@ toolbarpaletteitem[place="palette"] > to
 #zoom-out-button > .toolbarbutton-text,
 #zoom-reset-button > .toolbarbutton-icon {
   display: none;
 }
 
 #PanelUI-footer {
   display: flex;
   background-color: rgba(0, 0, 0, 0.05);
-  border-top: 1px solid rgba(0,0,0,.1);
+  box-shadow: 0 -1px 0 rgba(0,0,0,.15);
   padding: 0;
   margin: 0;
   min-height: 4em;
 }
 
+#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-customize,
 #PanelUI-quit {
-  margin: -1px 0 0;
+  margin: 0;
   padding: 10px 0;
   -moz-appearance: none;
   box-shadow: none;
   background-image: none;
   border: 1px solid transparent;
   border-bottom-style: none;
   border-radius: 0;
   transition: background-color;
@@ -274,24 +290,24 @@ toolbarpaletteitem[place="palette"] > to
 #PanelUI-help[disabled],
 #PanelUI-quit[disabled] {
   opacity: 0.4;
 }
 
 #PanelUI-help:not([disabled]):hover,
 #PanelUI-customize:hover,
 #PanelUI-quit:not([disabled]):hover {
-  border-color: rgba(8,25,42,0.2);
-  border-top-color: rgba(8,25,42,0.1);
+  outline: 1px solid rgba(0,0,0,0.1);
   background-color: rgba(0,0,0,0.1);
   box-shadow: none;
 }
 
 #PanelUI-quit:not([disabled]):hover {
   background-color: #d94141;
+  outline-color: #c23a3a;
 }
 
 #PanelUI-quit:not([disabled]):hover:active {
   background-color: #ad3434;
 }
 
 #main-window[customize-entered] #PanelUI-customize {
   color: white;
@@ -300,16 +316,17 @@ toolbarpaletteitem[place="palette"] > to
   text-shadow: 0 1px 0 rgba(0,0,0,0.4);
 }
 
 #main-window[customize-entered] #PanelUI-customize:hover,
 #main-window[customize-entered] #PanelUI-customize:hover:active {
   background-image: linear-gradient(rgb(38,115,191), rgb(38,125,191));
 }
 
+#customization-palette .toolbarbutton-multiline-text,
 #customization-palette .toolbarbutton-text {
   display: none;
 }
 
 panelview toolbarbutton,
 #widget-overflow-list > toolbarbutton,
 .customizationmode-button,
 #edit-controls@inAnyPanel@ > toolbarbutton,
@@ -527,16 +544,30 @@ toolbarpaletteitem[place="palette"] > #s
   -moz-padding-start: .5em;
 }
 
 #widget-overflow-list > #edit-controls,
 #widget-overflow-list > #zoom-controls {
   min-height: 28px;
 }
 
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button::after {
+  content: "";
+  display: -moz-box;
+  width: 1px;
+  height: 18px;
+  -moz-margin-end: -1px;
+  background-image: linear-gradient(hsla(210,54%,20%,.2) 0, hsla(210,54%,20%,.2) 18px);
+  background-clip: padding-box;
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 1px 18px;
+  box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
+}
+
 #PanelUI-developerItems > toolbarbutton[checked="true"],
 #PanelUI-bookmarks > toolbarbutton[checked="true"],
 #PanelUI-history > toolbarbutton[checked="true"],
 .PanelUI-characterEncodingView-list > toolbarbutton[current] {
   -moz-padding-start: 4px;
 }
 
 #PanelUI-developerItems > toolbarbutton[checked="true"] > .toolbarbutton-text,
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1370,17 +1370,17 @@ toolbarbutton[type="socialmark"] > .tool
 #bookmarks-menu-button.bookmark-item[starred] {
   -moz-image-region: rect(0px 48px 16px 32px);
 }
 
 #bookmarks-menu-button.bookmark-item > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
   -moz-margin-start: 5px;
 }
 
-#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+#bookmarks-menu-button[cui-areatype="toolbar"]:not(.bookmark-item):not(.overflowedItem) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   padding-top: 2px;
   padding-bottom: 2px;
 }
 
 #BMB_bookmarksPopup[side="top"],
 #BMB_bookmarksPopup[side="bottom"] {
   margin-left: -20px;
   margin-right: -20px;
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -23,8 +23,24 @@
 #BMB_bookmarksPopup > menu > .menu-text,
 #BMB_bookmarksPopup > menuitem > .menu-text,
 #BMB_bookmarksPopup > menu > .menu-iconic-text,
 #BMB_bookmarksPopup > menuitem > .menu-iconic-text,
 #BMB_bookmarksPopup > menuseparator {
   padding-top: 0;
   padding-bottom: 0;
 }
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+  -moz-appearance: none;
+  border: 0;
+  -moz-margin-start: 3px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+  padding: 0 2px;
+  -moz-padding-start: 0;
+  height: 18px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+  padding: 0 6px;
+}
--- a/configure.in
+++ b/configure.in
@@ -1346,22 +1346,24 @@ if test "$GNU_CC"; then
                      AC_MSG_RESULT([no]))
     CFLAGS=$_SAVE_CFLAGS
 
     # Turn on GNU-specific warnings:
     # -Wall - turn on a lot of warnings
     # -Wpointer-arith - good to have
     # -Wdeclaration-after-statement - MSVC doesn't like these
     # -Werror=return-type - catches missing returns, zero false positives
+    # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size
     # -Wtype-limits - catches overflow bugs, few false positives
     # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives
     # -Wsign-compare - catches comparison of signed and unsigned types
     #
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wall -Wpointer-arith -Wdeclaration-after-statement"
     MOZ_C_SUPPORTS_WARNING(-W, error=return-type, ac_c_has_werror_return_type)
+    MOZ_C_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_c_has_werror_int_to_pointer_cast)
     MOZ_C_SUPPORTS_WARNING(-W, type-limits, ac_c_has_wtype_limits)
     MOZ_C_SUPPORTS_WARNING(-W, empty-body, ac_c_has_wempty_body)
     MOZ_C_SUPPORTS_WARNING(-W, sign-compare, ac_c_has_sign_compare)
 
     # Turn off the following warnings that -Wall turns on:
     # -Wno-unused - lots of violations in third-party code
     #
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wno-unused"
@@ -1410,22 +1412,24 @@ if test "$GNU_CXX"; then
     # FIXME: Let us build with strict aliasing. bug 414641.
     CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-strict-aliasing"
 
     # Turn on GNU-specific warnings:
     # -Wall - turn on a lot of warnings
     # -Wpointer-arith - good to have
     # -Woverloaded-virtual - ???
     # -Werror=return-type - catches missing returns, zero false positives
+    # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size
     # -Wtype-limits - catches overflow bugs, few false positives
     # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives
     # -Wsign-compare - catches comparison of signed and unsigned types
     #
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wall -Wpointer-arith -Woverloaded-virtual"
     MOZ_CXX_SUPPORTS_WARNING(-W, error=return-type, ac_cxx_has_werror_return_type)
+    MOZ_CXX_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_cxx_has_werror_int_to_pointer_cast)
     MOZ_CXX_SUPPORTS_WARNING(-W, type-limits, ac_cxx_has_wtype_limits)
     MOZ_CXX_SUPPORTS_WARNING(-W, empty-body, ac_cxx_has_wempty_body)
     MOZ_CXX_SUPPORTS_WARNING(-W, sign-compare, ac_cxx_has_sign_compare)
 
     # Turn off the following warnings that -Wall turns on:
     # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently
     #
     MOZ_CXX_SUPPORTS_WARNING(-Wno-, invalid-offsetof, ac_cxx_has_wno_invalid_offsetof)
@@ -7737,18 +7741,24 @@ else
   dnl Don't override this for MSVC
   if test -z "$_WIN32_MSVC"; then
     _USE_CPP_INCLUDE_FLAG=
     _DEFINES_CFLAGS='$(ACDEFINES) -D_MOZILLA_CONFIG_H_ -DMOZILLA_CLIENT'
     _DEFINES_CXXFLAGS='$(ACDEFINES) -D_MOZILLA_CONFIG_H_ -DMOZILLA_CLIENT'
   else
     echo '#include <stdio.h>' > dummy-hello.c
     changequote(,)
-    CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/^\([^:]*:[^:]*:\).*stdio.h$/\1/p'`
+    dnl This output is localized, split at the first double space or colon and space.
+    _CL_PREFIX_REGEX="^\([^:]*:.*[ :] \)\(.*stdio.h\)$"
+    CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\1/p'`
+    _CL_STDIO_PATH=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\2/p'`
     changequote([,])
+    if ! test -e "$_CL_STDIO_PATH"; then
+        AC_MSG_ERROR([Unable to parse cl -showIncludes prefix. This compiler's locale has an unsupported formatting.])
+    fi
     if test -z "$CL_INCLUDES_PREFIX"; then
         AC_MSG_ERROR([Cannot find cl -showIncludes prefix.])
     fi
     AC_SUBST(CL_INCLUDES_PREFIX)
     rm -f dummy-hello.c
   fi
 fi
 
--- a/content/canvas/src/WebGLQuery.cpp
+++ b/content/canvas/src/WebGLQuery.cpp
@@ -35,16 +35,16 @@ void WebGLQuery::Delete() {
 }
 
 bool WebGLQuery::IsActive() const
 {
     WebGLRefPtr<WebGLQuery>* targetSlot = mContext->GetQueryTargetSlot(mType, "WebGLQuery::IsActive()");
 
     MOZ_ASSERT(targetSlot, "unknown query object's type");
 
-    return *targetSlot == this;
+    return targetSlot && *targetSlot == this;
 }
 
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQuery)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLQuery, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLQuery, Release)
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -624,16 +624,35 @@ ManifestHelper.prototype = {
 
   get permissions() {
     if (this._manifest.permissions) {
       return this._manifest.permissions;
     }
     return {};
   },
 
+  get biggestIconURL() {
+    let icons = this._localeProp("icons");
+    if (!icons) {
+      return null;
+    }
+
+    let iconSizes = Object.keys(icons);
+    if (iconSizes.length == 0) {
+      return null;
+    }
+
+    iconSizes.sort((a, b) => a - b);
+    let biggestIconSize = iconSizes.pop();
+    let biggestIcon = icons[biggestIconSize];
+    let biggestIconURL = this._origin.resolve(biggestIcon);
+
+    return biggestIconURL;
+  },
+
   iconURLForSize: function(aSize) {
     let icons = this._localeProp("icons");
     if (!icons)
       return null;
     let dist = 100000;
     let icon = null;
     for (let size in icons) {
       let iSize = parseInt(size);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2541,17 +2541,18 @@ nsGlobalWindow::SetNewDocument(nsIDocume
       if (newInnerWindow->mDoc != aDocument) {
         newInnerWindow->mDoc = aDocument;
 
         // We're reusing the inner window for a new document. In this
         // case we don't clear the inner window's scope, but we must
         // make sure the cached document property gets updated.
 
         // XXXmarkh - tell other languages about this?
-        ::JS_DeleteProperty(cx, currentInner->mJSObject, "document");
+        JS::Rooted<JSObject*> obj(cx, currentInner->mJSObject);
+        ::JS_DeleteProperty(cx, obj, "document");
       }
     } else {
       newInnerWindow->InnerSetNewDocument(aDocument);
 
       // Initialize DOM classes etc on the inner window.
       JS::Rooted<JSObject*> obj(cx, newInnerWindow->mJSObject);
       rv = mContext->InitClasses(obj);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -10860,17 +10861,19 @@ nsGlobalWindow::Observe(nsISupports* aSu
     {
     case nsPIDOMStorage::SessionStorage:
     {
       bool check = false;
 
       nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
       if (storageManager) {
         rv = storageManager->CheckStorage(principal, changingStorage, &check);
-        NS_ENSURE_SUCCESS(rv, rv);
+        if (NS_FAILED(rv)) {
+          return rv;
+        }
       }
 
       if (!check) {
         // This storage event is not coming from our storage or is coming
         // from a different docshell, i.e. it is a clone, ignore this event.
         return NS_OK;
       }
 
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2345,12 +2345,42 @@ CreateGlobal(JSContext* aCx, T* aObject,
     return nullptr;
   }
 
   mozilla::HoldJSObjects(aObject);
 
   return global;
 }
 
+/*
+ * Holds a jsid that is initialized to an interned string, with conversion to
+ * Handle<jsid>.
+ */
+class InternedStringId
+{
+  jsid id;
+
+ public:
+  InternedStringId() : id(JSID_VOID) {}
+
+  bool init(JSContext *cx, const char *string) {
+    MOZ_ASSERT(id == JSID_VOID);
+    JSString* str = JS_InternString(cx, string);
+    if (!str)
+      return false;
+    id = INTERNED_STRING_TO_JSID(cx, str);
+    return true;
+  }
+
+  operator const jsid& () {
+    return id;
+  }
+
+  operator JS::Handle<jsid> () {
+    /* This is safe because we have interned the string. */
+    return JS::Handle<jsid>::fromMarkedLocation(&id);
+  }
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -33,17 +33,18 @@ DefineStaticJSVals(JSContext* cx)
 const char HandlerFamily = 0;
 
 js::DOMProxyShadowsResult
 DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id)
 {
   JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO);
   if (v.isObject()) {
     bool hasOwn;
-    if (!JS_AlreadyHasOwnPropertyById(cx, &v.toObject(), id, &hasOwn))
+    Rooted<JSObject*> object(cx, &v.toObject());
+    if (!JS_AlreadyHasOwnPropertyById(cx, object, id, &hasOwn))
       return js::ShadowCheckFailed;
 
     return hasOwn ? js::Shadows : js::DoesntShadow;
   }
 
   if (v.isUndefined()) {
     return js::DoesntShadow;
   }
--- a/dom/datastore/DataStoreChangeNotifier.jsm
+++ b/dom/datastore/DataStoreChangeNotifier.jsm
@@ -63,18 +63,22 @@ this.DataStoreChangeNotifier = {
         obj.mm.sendAsyncMessage(aMsgName, aData.message);
       }
     });
   },
 
   receiveMessage: function(aMessage) {
     debug("receiveMessage");
 
+    // No check has to be done when the message is 'child-process-shutdown'
+    // because at this point the target is already disconnected from
+    // nsFrameMessageManager, so that assertAppHasStatus will always fail.
     let prefName = 'dom.testing.datastore_enabled_for_hosted_apps';
-    if ((Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
+    if (aMessage.name != 'child-process-shutdown' &&
+        (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
          !Services.prefs.getBoolPref(prefName)) &&
         !aMessage.target.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) {
       return;
     }
 
     switch (aMessage.name) {
       case "DataStore:Changed":
         this.broadcastMessage("DataStore:Changed:Return:OK", aMessage.data);
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -706,17 +706,17 @@ nsJSObjWrapper::NP_InvokeDefault(NPObjec
                                  uint32_t argCount, NPVariant *result)
 {
   return doInvoke(npobj, NPIdentifier_VOID, args, argCount, false,
                   result);
 }
 
 // static
 bool
-nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier id)
+nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier npid)
 {
   NPP npp = NPPStack::Peek();
   JSContext *cx = GetJSContext(npp);
 
   if (!cx) {
     return false;
   }
 
@@ -728,21 +728,23 @@ nsJSObjWrapper::NP_HasProperty(NPObject 
   }
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
   bool found, ok = false;
 
   nsCxPusher pusher;
   pusher.Push(cx);
   AutoJSExceptionReporter reporter(cx);
-  JSAutoCompartment ac(cx, npjsobj->mJSObj);
-
-  NS_ASSERTION(NPIdentifierIsInt(id) || NPIdentifierIsString(id),
+  JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj);
+  JSAutoCompartment ac(cx, jsobj);
+
+  NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
                "id must be either string or int!\n");
-  ok = ::JS_HasPropertyById(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), &found);
+  JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
+  ok = ::JS_HasPropertyById(cx, jsobj, id, &found);
   return ok && found;
 }
 
 // static
 bool
 nsJSObjWrapper::NP_GetProperty(NPObject *npobj, NPIdentifier id,
                                NPVariant *result)
 {
@@ -805,17 +807,17 @@ nsJSObjWrapper::NP_SetProperty(NPObject 
                "id must be either string or int!\n");
   ok = ::JS_SetPropertyById(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), v);
 
   return ok;
 }
 
 // static
 bool
-nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier id)
+nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier npid)
 {
   NPP npp = NPPStack::Peek();
   JSContext *cx = GetJSContext(npp);
 
   if (!cx) {
     return false;
   }
 
@@ -828,27 +830,29 @@ nsJSObjWrapper::NP_RemoveProperty(NPObje
 
   nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
   bool ok = false;
 
   nsCxPusher pusher;
   pusher.Push(cx);
   AutoJSExceptionReporter reporter(cx);
   bool deleted = false;
-  JSAutoCompartment ac(cx, npjsobj->mJSObj);
-
-  NS_ASSERTION(NPIdentifierIsInt(id) || NPIdentifierIsString(id),
+  JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj);
+  JSAutoCompartment ac(cx, obj);
+
+  NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid),
                "id must be either string or int!\n");
-  ok = ::JS_DeletePropertyById2(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), &deleted);
+  JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid));
+  ok = ::JS_DeletePropertyById2(cx, obj, id, &deleted);
   if (ok && deleted) {
     // FIXME: See bug 425823, we shouldn't need to do this, and once
     // that bug is fixed we can remove this code.
 
     bool hasProp;
-    ok = ::JS_HasPropertyById(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), &hasProp);
+    ok = ::JS_HasPropertyById(cx, obj, id, &hasProp);
 
     if (ok && hasProp) {
       // The property might have been deleted, but it got
       // re-resolved, so no, it's not really deleted.
 
       deleted = false;
     }
   }
--- a/dom/src/storage/DOMStorageManager.cpp
+++ b/dom/src/storage/DOMStorageManager.cpp
@@ -452,17 +452,19 @@ DOMStorageManager::CheckStorage(nsIPrinc
   *aRetval = false;
 
   if (!aPrincipal) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsAutoCString scope;
   nsresult rv = CreateScopeKey(aPrincipal, scope);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   DOMStorageCache* cache = GetCache(scope);
   if (cache != pstorage->GetCache()) {
     return NS_OK;
   }
 
   if (!pstorage->PrincipalEquals(aPrincipal)) {
     return NS_OK;
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -23,16 +23,17 @@
 #endif // defined(XP_LINUX)
 
 #if defined(XP_MACOSX)
 #include "copyfile.h"
 #endif // defined(XP_MACOSX)
 
 #if defined(XP_WIN)
 #include <windows.h>
+#include <accctrl.h>
 #endif // defined(XP_WIN)
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "BindingUtils.h"
 
 // Used to provide information on the OS
 
@@ -675,16 +676,21 @@ static const dom::ConstantSpec gWinPrope
 
   // MoveFile flags
   INT_CONSTANT(MOVEFILE_COPY_ALLOWED),
   INT_CONSTANT(MOVEFILE_REPLACE_EXISTING),
 
   // GetFileAttributes error constant
   INT_CONSTANT(INVALID_FILE_ATTRIBUTES),
 
+  // GetNamedSecurityInfo and SetNamedSecurityInfo constants
+  INT_CONSTANT(UNPROTECTED_DACL_SECURITY_INFORMATION),
+  INT_CONSTANT(SE_FILE_OBJECT),
+  INT_CONSTANT(DACL_SECURITY_INFORMATION),
+
   // Errors
   INT_CONSTANT(ERROR_ACCESS_DENIED),
   INT_CONSTANT(ERROR_DIR_NOT_EMPTY),
   INT_CONSTANT(ERROR_FILE_EXISTS),
   INT_CONSTANT(ERROR_ALREADY_EXISTS),
   INT_CONSTANT(ERROR_FILE_NOT_FOUND),
   INT_CONSTANT(ERROR_NO_MORE_FILES),
   INT_CONSTANT(ERROR_PATH_NOT_FOUND),
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -315,17 +315,19 @@ APZCTreeManager::ReceiveInputEvent(const
 {
   nsEventStatus result = nsEventStatus_eIgnore;
   gfx3DMatrix transformToApzc;
   gfx3DMatrix transformToGecko;
   switch (aEvent.mInputType) {
     case MULTITOUCH_INPUT: {
       const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
       if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
-        mTouchCount++;
+        // MULTITOUCH_START input contains all active touches of the current
+        // session thus resetting mTouchCount.
+        mTouchCount = multiTouchInput.mTouches.Length();
         mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint));
         if (multiTouchInput.mTouches.Length() == 1) {
           // If we have one touch point, this might be the start of a pan.
           // Prepare for possible overscroll handoff.
           BuildOverscrollHandoffChain(mApzcForInputBlock);
         }
         for (size_t i = 1; i < multiTouchInput.mTouches.Length(); i++) {
           nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[i].mScreenPoint));
@@ -358,16 +360,17 @@ APZCTreeManager::ReceiveInputEvent(const
         for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
           ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
         }
         result = mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
       }
       if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL ||
           multiTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
         if (mTouchCount >= multiTouchInput.mTouches.Length()) {
+          // MULTITOUCH_END input contains only released touches thus decrementing.
           mTouchCount -= multiTouchInput.mTouches.Length();
         } else {
           NS_WARNING("Got an unexpected touchend/touchcancel");
           mTouchCount = 0;
         }
         // If we have an mApzcForInputBlock and it's the end of the touch sequence
         // then null it out so we don't keep a dangling reference and leak things.
         if (mTouchCount == 0) {
@@ -433,17 +436,19 @@ APZCTreeManager::ProcessTouchEvent(const
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsEventStatus ret = nsEventStatus_eIgnore;
   if (!aEvent.touches.Length()) {
     return ret;
   }
   if (aEvent.message == NS_TOUCH_START) {
-    mTouchCount++;
+    // NS_TOUCH_START event contains all active touches of the current
+    // session thus resetting mTouchCount.
+    mTouchCount = aEvent.touches.Length();
     mApzcForInputBlock = GetTouchInputBlockAPZC(aEvent);
     if (mApzcForInputBlock) {
       // Cache apz transform so it can be used for future events in this block.
       gfx3DMatrix transformToGecko;
       GetInputTransforms(mApzcForInputBlock, mCachedTransformToApzcForInputBlock, transformToGecko);
     } else {
       // Reset the cached apz transform
       mCachedTransformToApzcForInputBlock = gfx3DMatrix();
@@ -472,16 +477,17 @@ APZCTreeManager::ProcessTouchEvent(const
       ApplyTransform(&(aOutEvent->touches[i]->mRefPoint), outTransform);
     }
   }
   // If we have an mApzcForInputBlock and it's the end of the touch sequence
   // then null it out so we don't keep a dangling reference and leak things.
   if (aEvent.message == NS_TOUCH_CANCEL ||
       aEvent.message == NS_TOUCH_END) {
     if (mTouchCount >= aEvent.touches.Length()) {
+      // NS_TOUCH_END event contains only released touches thus decrementing.
       mTouchCount -= aEvent.touches.Length();
     } else {
       NS_WARNING("Got an unexpected touchend/touchcancel");
       mTouchCount = 0;
     }
     if (mTouchCount == 0) {
       mApzcForInputBlock = nullptr;
       mOverscrollHandoffChain.clear();
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -536,40 +536,62 @@ AsyncCompositionManager::ApplyAsyncConte
   }
 
   if (container->GetScrollbarDirection() != Layer::NONE) {
     ApplyAsyncTransformToScrollbar(container);
   }
   return appliedTransform;
 }
 
+static bool
+LayerHasNonContainerDescendants(ContainerLayer* aContainer)
+{
+  for (Layer* child = aContainer->GetFirstChild();
+       child; child = child->GetNextSibling()) {
+    ContainerLayer* container = child->AsContainerLayer();
+    if (!container || LayerHasNonContainerDescendants(container)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void
 AsyncCompositionManager::ApplyAsyncTransformToScrollbar(ContainerLayer* aLayer)
 {
   // If this layer corresponds to a scrollbar, then search backwards through the
   // siblings until we find the container layer with the right ViewID; this is
   // the content that this scrollbar is for. Pick up the transient async transform
   // from that layer and use it to update the scrollbar position.
   // Note that it is possible that the content layer is no longer there; in
   // this case we don't need to do anything because there can't be an async
   // transform on the content.
+  // We only apply the transform if the scroll-target layer has non-container
+  // children (i.e. when it has some possibly-visible content). This is to
+  // avoid moving scroll-bars in the situation that only a scroll information
+  // layer has been built for a scroll frame, as this would result in a
+  // disparity between scrollbars and visible content.
   for (Layer* scrollTarget = aLayer->GetPrevSibling();
        scrollTarget;
        scrollTarget = scrollTarget->GetPrevSibling()) {
     if (!scrollTarget->AsContainerLayer()) {
       continue;
     }
     AsyncPanZoomController* apzc = scrollTarget->AsContainerLayer()->GetAsyncPanZoomController();
     if (!apzc) {
       continue;
     }
     const FrameMetrics& metrics = scrollTarget->AsContainerLayer()->GetFrameMetrics();
     if (metrics.mScrollId != aLayer->GetScrollbarTargetContainerId()) {
       continue;
     }
+    if (!LayerHasNonContainerDescendants(scrollTarget->AsContainerLayer())) {
+      return;
+    }
 
     gfx3DMatrix asyncTransform = gfx3DMatrix(apzc->GetCurrentAsyncTransform());
     gfx3DMatrix nontransientTransform = apzc->GetNontransientAsyncTransform();
     gfx3DMatrix transientTransform = asyncTransform * nontransientTransform.Inverse();
 
     gfx3DMatrix scrollbarTransform;
     if (aLayer->GetScrollbarDirection() == Layer::VERTICAL) {
       float scale = metrics.CalculateCompositedRectInCssPixels().height / metrics.mScrollableRect.height;
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -1547,24 +1547,32 @@ CompositorOGL::NotifyLayersTransaction()
     mFPS->NotifyShadowTreeTransaction();
   }
 }
 
 void
 CompositorOGL::Pause()
 {
 #ifdef MOZ_WIDGET_ANDROID
+  if (!gl() || gl()->IsDestroyed())
+    return;
+
+  // ReleaseSurface internally calls MakeCurrent.
   gl()->ReleaseSurface();
 #endif
 }
 
 bool
 CompositorOGL::Resume()
 {
 #ifdef MOZ_WIDGET_ANDROID
+  if (!gl() || gl()->IsDestroyed())
+    return false;
+
+  // RenewSurface internally calls MakeCurrent.
   return gl()->RenewSurface();
 #endif
   return true;
 }
 
 TemporaryRef<DataTextureSource>
 CompositorOGL::CreateDataTextureSource(TextureFlags aFlags)
 {
--- a/intl/uconv/ucvcn/nsISO2022CNToUnicode.h
+++ b/intl/uconv/ucvcn/nsISO2022CNToUnicode.h
@@ -13,27 +13,26 @@
 #define PMASK       0xa0
 
 #define SI          0x0f 
 #define SO          0x0e
 #define ESC         0x1b
 #define SS2         0x4e
 #define SS3         0x4f
 
-using namespace mozilla;
-
 class nsISO2022CNToUnicode : public nsBasicDecoderSupport
 {
 public:
   nsISO2022CNToUnicode() : 
         mState(eState_ASCII), 
         mPlaneID(0),
         mRunLength(0)
   {
-    Telemetry::Accumulate(Telemetry::DECODER_INSTANTIATED_ISO2022CN, true);
+    mozilla::Telemetry::Accumulate(
+      mozilla::Telemetry::DECODER_INSTANTIATED_ISO2022CN, true);
   }
 
   virtual ~nsISO2022CNToUnicode() {}
 
   NS_IMETHOD Convert(const char *aSrc, int32_t * aSrcLength,
      char16_t * aDest, int32_t * aDestLength) ;
 
   NS_IMETHOD GetMaxLength(const char * aSrc, int32_t aSrcLength,
--- a/intl/uconv/ucvja/nsJapaneseToUnicode.h
+++ b/intl/uconv/ucvja/nsJapaneseToUnicode.h
@@ -2,18 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsShiftJISToUnicode_h__
 #define nsShiftJISToUnicode_h__
 #include "nsUCSupport.h"
 #include "mozilla/Telemetry.h"
 
-using namespace mozilla;
-
 class nsShiftJISToUnicode : public nsBasicDecoderSupport
 {
 public:
 
  nsShiftJISToUnicode() 
      { 
          mState=0; mData=0; 
      }
@@ -80,17 +78,18 @@ public:
         mState = mState_ASCII;
         mLastLegalState = mState_ASCII;
         mData = 0;
         mRunLength = 0;
         G2charset = G2_unknown;
         mGB2312Decoder = nullptr;
         mEUCKRDecoder = nullptr;
         mISO88597Decoder = nullptr;
-        Telemetry::Accumulate(Telemetry::DECODER_INSTANTIATED_ISO2022JP, true);
+        mozilla::Telemetry::Accumulate(
+          mozilla::Telemetry::DECODER_INSTANTIATED_ISO2022JP, true);
      }
  virtual ~nsISO2022JPToUnicodeV2()
      {
         NS_IF_RELEASE(mGB2312Decoder);
         NS_IF_RELEASE(mEUCKRDecoder);
         NS_IF_RELEASE(mISO88597Decoder);
      }
 
--- a/intl/uconv/ucvko/nsISO2022KRToUnicode.h
+++ b/intl/uconv/ucvko/nsISO2022KRToUnicode.h
@@ -2,29 +2,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/. */
 #ifndef nsISO2022KRToUnicode_h__
 #define nsISO2022KRToUnicode_h__
 #include "nsUCSupport.h"
 #include "mozilla/Telemetry.h"
 
-using namespace mozilla;
- 
 class nsISO2022KRToUnicode : public nsBasicDecoderSupport
 {
 public:
   nsISO2022KRToUnicode()
   { 
     mState = mState_Init;
     mLastLegalState = mState_ASCII;
     mData = 0;
     mEUCKRDecoder = nullptr;
     mRunLength = 0;
-    Telemetry::Accumulate(Telemetry::DECODER_INSTANTIATED_ISO2022KR, true);
+    mozilla::Telemetry::Accumulate(
+      mozilla::Telemetry::DECODER_INSTANTIATED_ISO2022KR, true);
   }
 
   virtual ~nsISO2022KRToUnicode()
   {
     NS_IF_RELEASE(mEUCKRDecoder);
   }
 
   NS_IMETHOD Convert(const char * aSrc, int32_t * aSrcLength,
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -133,16 +133,17 @@ class HeapBase {};
  */
 struct NullPtr
 {
     static void * const constNullValue;
 };
 
 namespace gc {
 struct Cell;
+struct PersistentRootedMarker;
 } /* namespace gc */
 
 } /* namespace js */
 
 namespace JS {
 
 template <typename T> class Rooted;
 template <typename T> class PersistentRooted;
@@ -1093,17 +1094,16 @@ template <typename T>
 inline
 MutableHandle<T>::MutableHandle(PersistentRooted<T> *root)
 {
     static_assert(sizeof(MutableHandle<T>) == sizeof(T *),
                   "MutableHandle must be binary compatible with T*.");
     ptr = root->address();
 }
 
-
 /*
  * A copyable, assignable global GC root type with arbitrary lifetime, an
  * infallible constructor, and automatic unrooting on destruction.
  *
  * These roots can be used in heap-allocated data structures, so they are not
  * associated with any particular JSContext or stack. They are registered with
  * the JSRuntime itself, without locking, so they require a full JSContext to be
  * constructed, not one of its more restricted superclasses.
@@ -1127,19 +1127,20 @@ MutableHandle<T>::MutableHandle(Persiste
  * In the context of Firefox, this is a severe restriction: almost everything in
  * Firefox is owned by some JS object or another, so using PersistentRooted in
  * such objects would introduce leaks. For these kinds of edges, Heap<T> or
  * TenuredHeap<T> would be better types. It's up to the implementor of the type
  * containing Heap<T> or TenuredHeap<T> members to make sure their referents get
  * marked when the object itself is marked.
  */
 template<typename T>
-class PersistentRooted : public mozilla::LinkedListElement<PersistentRooted<T> > {
-    typedef mozilla::LinkedList<PersistentRooted> List;
-    typedef mozilla::LinkedListElement<PersistentRooted> Element;
+class PersistentRooted : private mozilla::LinkedListElement<PersistentRooted<T> > {
+    friend class mozilla::LinkedList<PersistentRooted>;
+    friend class mozilla::LinkedListElement<PersistentRooted>;
+    friend class js::gc::PersistentRootedMarker;
 
     void registerWithRuntime(JSRuntime *rt) {
         JS::shadow::Runtime *srt = JS::shadow::Runtime::asShadowRuntime(rt);
         srt->getPersistentRootedList<T>().insertBack(this);
     }
 
   public:
     PersistentRooted(JSContext *cx) : ptr(js::GCMethods<T>::initial())
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -1132,22 +1132,24 @@ if test "$GNU_CC"; then
                   AC_MSG_RESULT([no])
                   LDFLAGS=$_SAVE_LDFLAGS)
 
     # Turn on GNU-specific warnings:
     # -Wall - turn on a lot of warnings
     # -Wpointer-arith - good to have
     # -Wdeclaration-after-statement - MSVC doesn't like these
     # -Werror=return-type - catches missing returns, zero false positives
+    # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size
     # -Wtype-limits - catches overflow bugs, few false positives
     # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives
     # -Wsign-compare - catches comparison of signed and unsigned types
     #
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wall -Wpointer-arith -Wdeclaration-after-statement"
     MOZ_C_SUPPORTS_WARNING(-W, error=return-type, ac_c_has_werror_return_type)
+    MOZ_C_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_c_has_werror_int_to_pointer_cast)
     MOZ_C_SUPPORTS_WARNING(-W, empty-body, ac_c_has_wempty_body)
     MOZ_C_SUPPORTS_WARNING(-W, sign-compare, ac_c_has_sign_compare)
 
     # Turn off the following warnings that -Wall turns on:
     # -Wno-unused - lots of violations in third-party code
     #
     _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wno-unused"
 
@@ -1191,23 +1193,25 @@ else
 fi
 
 if test "$GNU_CXX"; then
     # Turn on GNU-specific warnings:
     # -Wall - turn on a lot of warnings
     # -Wpointer-arith - good to have
     # -Woverloaded-virtual - ???
     # -Werror=return-type - catches missing returns, zero false positives
+    # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size
     # -Wtype-limits - catches overflow bugs, few false positives
     # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives
     # -Werror=conversion-null - catches conversions between NULL and non-pointer types
     # -Wsign-compare - catches comparison of signed and unsigned types
     #
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wall -Wpointer-arith -Woverloaded-virtual"
     MOZ_CXX_SUPPORTS_WARNING(-W, error=return-type, ac_cxx_has_werror_return_type)
+    MOZ_CXX_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_cxx_has_werror_int_to_pointer_cast)
     MOZ_CXX_SUPPORTS_WARNING(-W, type-limits, ac_cxx_has_wtype_limits)
     MOZ_CXX_SUPPORTS_WARNING(-W, empty-body, ac_cxx_has_wempty_body)
     MOZ_CXX_SUPPORTS_WARNING(-W, error=conversion-null, ac_cxx_has_werror_conversion_null)
     MOZ_CXX_SUPPORTS_WARNING(-W, sign-compare, ac_cxx_has_sign_compare)
 
     # Turn off the following warnings that -Wall turns on:
     # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently
     #
@@ -3815,18 +3819,24 @@ else
   dnl Don't override this for MSVC
   if test -z "$_WIN32_MSVC"; then
     _USE_CPP_INCLUDE_FLAG=
     _DEFINES_CFLAGS='$(ACDEFINES) -D_JS_CONFDEFS_H_ -DMOZILLA_CLIENT'
     _DEFINES_CXXFLAGS='$(ACDEFINES) -D_JS_CONFDEFS_H_ -DMOZILLA_CLIENT'
   else
     echo '#include <stdio.h>' > dummy-hello.c
     changequote(,)
-    CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/^\([^:]*:[^:]*:\).*stdio.h$/\1/p'`
+    dnl This output is localized, split at the first double space or colon and space.
+    _CL_PREFIX_REGEX="^\([^:]*:.*[ :] \)\(.*stdio.h\)$"
+    CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\1/p'`
+    _CL_STDIO_PATH=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\2/p'`
     changequote([,])
+    if ! test -e "$_CL_STDIO_PATH"; then
+        AC_MSG_ERROR([Unable to parse cl -showIncludes prefix. This compiler's locale has an unsupported formatting.])
+    fi
     if test -z "$CL_INCLUDES_PREFIX"; then
         AC_MSG_ERROR([Cannot find cl -showIncludes prefix.])
     fi
     AC_SUBST(CL_INCLUDES_PREFIX)
     rm -f dummy-hello.c
   fi
 fi
 
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -613,52 +613,67 @@ JSPropertyDescriptor::trace(JSTracer *tr
     }
     if ((attrs & JSPROP_SETTER) && setter) {
         JSObject *tmp = JS_FUNC_TO_DATA_PTR(JSObject *, setter);
         MarkObjectRoot(trc, &tmp, "Descriptor::set");
         setter = JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, tmp);
     }
 }
 
-// Mark a chain of PersistentRooted pointers that might be null.
-template<typename Referent>
-static void
-MarkPersistentRootedChain(JSTracer *trc,
-                          mozilla::LinkedList<PersistentRooted<Referent *> > &list,
-                          void (*marker)(JSTracer *trc, Referent **ref, const char *name),
-                          const char *name)
+namespace js {
+namespace gc {
+struct PersistentRootedMarker
 {
-    for (PersistentRooted<Referent *> *r = list.getFirst();
-         r != nullptr;
-         r = r->getNext())
+    template<typename Referent>
+    static void
+    markChainIfNotNull(JSTracer *trc,
+                       mozilla::LinkedList<PersistentRooted<Referent *> > &list,
+                       void (*marker)(JSTracer *trc, Referent **ref, const char *name),
+                       const char *name)
     {
-        if (r->get())
+        for (PersistentRooted<Referent *> *r = list.getFirst(); r; r = r->getNext()) {
+            if (r->get())
+                marker(trc, r->address(), name);
+        }
+    }
+
+    template<typename Referent>
+    static void
+    markChain(JSTracer *trc,
+              mozilla::LinkedList<PersistentRooted<Referent> > &list,
+              void (*marker)(JSTracer *trc, Referent *ref, const char *name),
+              const char *name)
+    {
+        for (PersistentRooted<Referent> *r = list.getFirst(); r; r = r->getNext())
             marker(trc, r->address(), name);
     }
+};
+}
 }
 
 void
 js::gc::MarkPersistentRootedChains(JSTracer *trc)
 {
     JSRuntime *rt = trc->runtime;
 
-    MarkPersistentRootedChain(trc, rt->functionPersistentRooteds, &MarkObjectRoot,
-                              "PersistentRooted<JSFunction *>");
-    MarkPersistentRootedChain(trc, rt->objectPersistentRooteds, &MarkObjectRoot,
-                              "PersistentRooted<JSObject *>");
-    MarkPersistentRootedChain(trc, rt->scriptPersistentRooteds, &MarkScriptRoot,
-                              "PersistentRooted<JSScript *>");
-    MarkPersistentRootedChain(trc, rt->stringPersistentRooteds, &MarkStringRoot, 
-                              "PersistentRooted<JSString *>");
+    // Mark the PersistentRooted chains of types that may be null.
+    PersistentRootedMarker::markChainIfNotNull(trc, rt->functionPersistentRooteds, &MarkObjectRoot,
+                                               "PersistentRooted<JSFunction *>");
+    PersistentRootedMarker::markChainIfNotNull(trc, rt->objectPersistentRooteds, &MarkObjectRoot,
+                                               "PersistentRooted<JSObject *>");
+    PersistentRootedMarker::markChainIfNotNull(trc, rt->scriptPersistentRooteds, &MarkScriptRoot,
+                                               "PersistentRooted<JSScript *>");
+    PersistentRootedMarker::markChainIfNotNull(trc, rt->stringPersistentRooteds, &MarkStringRoot,
+                                               "PersistentRooted<JSString *>");
 
     // Mark the PersistentRooted chains of types that are never null.
-    for (JS::PersistentRootedId *r = rt->idPersistentRooteds.getFirst(); r != nullptr; r = r->getNext())
-        MarkIdRoot(trc, r->address(), "PersistentRooted<jsid>");
-    for (JS::PersistentRootedValue *r = rt->valuePersistentRooteds.getFirst(); r != nullptr; r = r->getNext())
-        MarkValueRoot(trc, r->address(), "PersistentRooted<Value>");
+    PersistentRootedMarker::markChain(trc, rt->idPersistentRooteds, &MarkIdRoot,
+                                      "PersistentRooted<jsid>");
+    PersistentRootedMarker::markChain(trc, rt->valuePersistentRooteds, &MarkValueRoot,
+                                      "PersistentRooted<Value>");
 }
 
 void
 js::gc::MarkRuntime(JSTracer *trc, bool useSavedRoots)
 {
     JSRuntime *rt = trc->runtime;
     JS_ASSERT(trc->callback != GCMarker::GrayCallback);
 
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -82,27 +82,23 @@ class StoreBuffer
     static const size_t MinAvailableSize = (size_t)(LifoAllocBlockSize * 1.0 / 8.0);
 
     /*
      * This buffer holds only a single type of edge. Using this buffer is more
      * efficient than the generic buffer when many writes will be to the same
      * type of edge: e.g. Value or Cell*.
      */
     template<typename T>
-    class MonoTypeBuffer
+    struct MonoTypeBuffer
     {
-        friend class StoreBuffer;
-
         LifoAlloc *storage_;
 
         explicit MonoTypeBuffer() : storage_(nullptr) {}
         ~MonoTypeBuffer() { js_delete(storage_); }
 
-        MonoTypeBuffer &operator=(const MonoTypeBuffer& other) MOZ_DELETE;
-
         bool init() {
             if (!storage_)
                 storage_ = js_new<LifoAlloc>(LifoAllocBlockSize);
             clear();
             return bool(storage_);
         }
 
         void clear() {
@@ -137,48 +133,45 @@ class StoreBuffer
                 compact(owner);
                 if (isAboutToOverflow())
                     owner->setAboutToOverflow();
             }
         }
 
         /* Mark the source of all edges in the store buffer. */
         void mark(StoreBuffer *owner, JSTracer *trc);
+
+      private:
+        MonoTypeBuffer &operator=(const MonoTypeBuffer& other) MOZ_DELETE;
     };
 
     /*
      * Overrides the MonoTypeBuffer to support pointers that may be moved in
      * memory outside of the GC's control.
      */
     template <typename T>
-    class RelocatableMonoTypeBuffer : public MonoTypeBuffer<T>
+    struct RelocatableMonoTypeBuffer : public MonoTypeBuffer<T>
     {
-        friend class StoreBuffer;
-
         /* Override compaction to filter out removed items. */
         void compactMoved(StoreBuffer *owner);
         virtual void compact(StoreBuffer *owner) MOZ_OVERRIDE;
 
         /* Record a removal from the buffer. */
         void unput(StoreBuffer *owner, const T &v) {
             MonoTypeBuffer<T>::put(owner, v.tagged());
         }
     };
 
-    class GenericBuffer
+    struct GenericBuffer
     {
-        friend class StoreBuffer;
-
         LifoAlloc *storage_;
 
         explicit GenericBuffer() : storage_(nullptr) {}
         ~GenericBuffer() { js_delete(storage_); }
 
-        GenericBuffer &operator=(const GenericBuffer& other) MOZ_DELETE;
-
         bool init() {
             if (!storage_)
                 storage_ = js_new<LifoAlloc>(LifoAllocBlockSize);
             clear();
             return bool(storage_);
         }
 
         void clear() {
@@ -210,24 +203,23 @@ class StoreBuffer
 
             T *tp = storage_->new_<T>(t);
             if (!tp)
                 CrashAtUnhandlableOOM("Failed to allocate for GenericBuffer::put.");
 
             if (isAboutToOverflow())
                 owner->setAboutToOverflow();
         }
+
+      private:
+        GenericBuffer &operator=(const GenericBuffer& other) MOZ_DELETE;
     };
 
-    class CellPtrEdge
+    struct CellPtrEdge
     {
-        friend class StoreBuffer;
-        friend class StoreBuffer::MonoTypeBuffer<CellPtrEdge>;
-        friend class StoreBuffer::RelocatableMonoTypeBuffer<CellPtrEdge>;
-
         Cell **edge;
 
         explicit CellPtrEdge(Cell **v) : edge(v) {}
         bool operator==(const CellPtrEdge &other) const { return edge == other.edge; }
         bool operator!=(const CellPtrEdge &other) const { return edge != other.edge; }
 
         void *location() const { return (void *)untagged().edge; }
 
@@ -241,22 +233,18 @@ class StoreBuffer
 
         void mark(JSTracer *trc);
 
         CellPtrEdge tagged() const { return CellPtrEdge((Cell **)(uintptr_t(edge) | 1)); }
         CellPtrEdge untagged() const { return CellPtrEdge((Cell **)(uintptr_t(edge) & ~1)); }
         bool isTagged() const { return bool(uintptr_t(edge) & 1); }
     };
 
-    class ValueEdge
+    struct ValueEdge
     {
-        friend class StoreBuffer;
-        friend class StoreBuffer::MonoTypeBuffer<ValueEdge>;
-        friend class StoreBuffer::RelocatableMonoTypeBuffer<ValueEdge>;
-
         JS::Value *edge;
 
         explicit ValueEdge(JS::Value *v) : edge(v) {}
         bool operator==(const ValueEdge &other) const { return edge == other.edge; }
         bool operator!=(const ValueEdge &other) const { return edge != other.edge; }
 
         void *deref() const { return edge->isGCThing() ? edge->toGCThing() : nullptr; }
         void *location() const { return (void *)untagged().edge; }
@@ -273,19 +261,16 @@ class StoreBuffer
 
         ValueEdge tagged() const { return ValueEdge((JS::Value *)(uintptr_t(edge) | 1)); }
         ValueEdge untagged() const { return ValueEdge((JS::Value *)(uintptr_t(edge) & ~1)); }
         bool isTagged() const { return bool(uintptr_t(edge) & 1); }
     };
 
     struct SlotEdge
     {
-        friend class StoreBuffer;
-        friend class StoreBuffer::MonoTypeBuffer<SlotEdge>;
-
         JSObject *object;
         uint32_t offset;
         int kind; // this is really just HeapSlot::Kind, but we can't see that type easily here
 
         SlotEdge(JSObject *object, int kind, uint32_t offset)
           : object(object), offset(offset), kind(kind)
         {}
 
@@ -302,21 +287,18 @@ class StoreBuffer
         JS_ALWAYS_INLINE void *deref() const;
         JS_ALWAYS_INLINE void *location() const;
         bool inRememberedSet(const Nursery &nursery) const;
         JS_ALWAYS_INLINE bool isNullEdge() const;
 
         void mark(JSTracer *trc);
     };
 
-    class WholeCellEdges
+    struct WholeCellEdges
     {
-        friend class StoreBuffer;
-        friend class StoreBuffer::MonoTypeBuffer<WholeCellEdges>;
-
         Cell *tenured;
 
         WholeCellEdges(Cell *cell) : tenured(cell) {
             JS_ASSERT(tenured->isTenured());
         }
 
         bool operator==(const WholeCellEdges &other) const { return tenured == other.tenured; }
         bool operator!=(const WholeCellEdges &other) const { return tenured != other.tenured; }
@@ -327,19 +309,18 @@ class StoreBuffer
         void *location() const { return (void *)tenured; }
 
         bool isNullEdge() const { return false; }
 
         void mark(JSTracer *trc);
     };
 
     template <typename Key>
-    class CallbackRef : public BufferableRef
+    struct CallbackRef : public BufferableRef
     {
-      public:
         typedef void (*MarkCallback)(JSTracer *trc, Key *key, void *data);
 
         CallbackRef(MarkCallback cb, Key *k, void *d) : callback(cb), key(k), data(d) {}
 
         virtual void mark(JSTracer *trc) {
             callback(trc, key, data);
         }
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -784,36 +784,61 @@ bool
 CodeGenerator::visitRegExpTest(LRegExpTest *lir)
 {
     pushArg(ToRegister(lir->string()));
     pushArg(ToRegister(lir->regexp()));
     return callVM(RegExpTestRawInfo, lir);
 }
 
 typedef JSString *(*RegExpReplaceFn)(JSContext *, HandleString, HandleObject, HandleString);
-static const VMFunction RegExpReplaceInfo = FunctionInfo<RegExpReplaceFn>(regexp_replace);
+static const VMFunction RegExpReplaceInfo = FunctionInfo<RegExpReplaceFn>(RegExpReplace);
 
 bool
 CodeGenerator::visitRegExpReplace(LRegExpReplace *lir)
 {
     if (lir->replacement()->isConstant())
         pushArg(ImmGCPtr(lir->replacement()->toConstant()->toString()));
     else
         pushArg(ToRegister(lir->replacement()));
 
-    pushArg(ToRegister(lir->regexp()));
+    pushArg(ToRegister(lir->pattern()));
 
     if (lir->string()->isConstant())
         pushArg(ImmGCPtr(lir->string()->toConstant()->toString()));
     else
         pushArg(ToRegister(lir->string()));
 
     return callVM(RegExpReplaceInfo, lir);
 }
 
+typedef JSString *(*StringReplaceFn)(JSContext *, HandleString, HandleString, HandleString);
+static const VMFunction StringReplaceInfo = FunctionInfo<StringReplaceFn>(StringReplace);
+
+bool
+CodeGenerator::visitStringReplace(LStringReplace *lir)
+{
+    if (lir->replacement()->isConstant())
+        pushArg(ImmGCPtr(lir->replacement()->toConstant()->toString()));
+    else
+        pushArg(ToRegister(lir->replacement()));
+
+    if (lir->pattern()->isConstant())
+        pushArg(ImmGCPtr(lir->pattern()->toConstant()->toString()));
+    else
+        pushArg(ToRegister(lir->pattern()));
+
+    if (lir->string()->isConstant())
+        pushArg(ImmGCPtr(lir->string()->toConstant()->toString()));
+    else
+        pushArg(ToRegister(lir->string()));
+
+    return callVM(StringReplaceInfo, lir);
+}
+
+
 typedef JSObject *(*LambdaFn)(JSContext *, HandleFunction, HandleObject);
 static const VMFunction LambdaInfo =
     FunctionInfo<LambdaFn>(js::Lambda);
 
 bool
 CodeGenerator::visitLambdaForSingleton(LLambdaForSingleton *lir)
 {
     pushArg(ToRegister(lir->scopeChain()));
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -88,16 +88,17 @@ class CodeGenerator : public CodeGenerat
     bool visitTypeObjectDispatch(LTypeObjectDispatch *lir);
     bool visitIntToString(LIntToString *lir);
     bool visitDoubleToString(LDoubleToString *lir);
     bool visitInteger(LInteger *lir);
     bool visitRegExp(LRegExp *lir);
     bool visitRegExpExec(LRegExpExec *lir);
     bool visitRegExpTest(LRegExpTest *lir);
     bool visitRegExpReplace(LRegExpReplace *lir);
+    bool visitStringReplace(LStringReplace *lir);
     bool visitLambda(LLambda *lir);
     bool visitLambdaForSingleton(LLambdaForSingleton *lir);
     bool visitLambdaPar(LLambdaPar *lir);
     bool visitPointer(LPointer *lir);
     bool visitSlots(LSlots *lir);
     bool visitStoreSlotV(LStoreSlotV *store);
     bool visitElements(LElements *lir);
     bool visitConvertElementsToDoubles(LConvertElementsToDoubles *lir);
--- a/js/src/jit/CompileInfo-inl.h
+++ b/js/src/jit/CompileInfo-inl.h
@@ -6,24 +6,27 @@
 
 #ifndef jit_CompileInfo_inl_h
 #define jit_CompileInfo_inl_h
 
 #include "jit/CompileInfo.h"
 
 #include "jsscriptinlines.h"
 
-using namespace js;
-using namespace jit;
+namespace js {
+namespace jit {
 
 inline RegExpObject *
 CompileInfo::getRegExp(jsbytecode *pc) const
 {
     return script_->getRegExp(pc);
 }
 
 inline JSFunction *
 CompileInfo::getFunction(jsbytecode *pc) const
 {
     return script_->getFunction(GET_UINT32_INDEX(pc));
 }
 
+} // namespace jit
+} // namespace js
+
 #endif /* jit_CompileInfo_inl_h */
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -624,17 +624,17 @@ class IonBuilder : public MIRGenerator
     InliningStatus inlineMathFunction(CallInfo &callInfo, MMathFunction::Function function);
 
     // String natives.
     InliningStatus inlineStringObject(CallInfo &callInfo);
     InliningStatus inlineStringSplit(CallInfo &callInfo);
     InliningStatus inlineStrCharCodeAt(CallInfo &callInfo);
     InliningStatus inlineStrFromCharCode(CallInfo &callInfo);
     InliningStatus inlineStrCharAt(CallInfo &callInfo);
-    InliningStatus inlineStrReplaceRegExp(CallInfo &callInfo);
+    InliningStatus inlineStrReplace(CallInfo &callInfo);
 
     // RegExp natives.
     InliningStatus inlineRegExpExec(CallInfo &callInfo);
     InliningStatus inlineRegExpTest(CallInfo &callInfo);
 
     // Array intrinsics.
     InliningStatus inlineUnsafePutElements(CallInfo &callInfo);
     bool inlineUnsafeSetDenseArrayElement(CallInfo &callInfo, uint32_t base);
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -3241,44 +3241,71 @@ class LRegExpTest : public LCallInstruct
         return getOperand(1);
     }
 
     const MRegExpTest *mir() const {
         return mir_->toRegExpTest();
     }
 };
 
-class LRegExpReplace : public LCallInstructionHelper<1, 3, 0>
-{
-  public:
-    LIR_HEADER(RegExpReplace)
-
-    LRegExpReplace(const LAllocation &string, const LAllocation &regexp,
+
+class LStrReplace : public LCallInstructionHelper<1, 3, 0>
+{
+  public:
+    LStrReplace(const LAllocation &string, const LAllocation &pattern,
                    const LAllocation &replacement)
     {
         setOperand(0, string);
-        setOperand(1, regexp);
+        setOperand(1, pattern);
         setOperand(2, replacement);
     }
 
     const LAllocation *string() {
         return getOperand(0);
     }
-    const LAllocation *regexp() {
+    const LAllocation *pattern() {
         return getOperand(1);
     }
     const LAllocation *replacement() {
         return getOperand(2);
     }
+};
+
+class LRegExpReplace: public LStrReplace
+{
+  public:
+    LIR_HEADER(RegExpReplace);
+
+    LRegExpReplace(const LAllocation &string, const LAllocation &pattern,
+                   const LAllocation &replacement)
+      : LStrReplace(string, pattern, replacement)
+    {
+    }
 
     const MRegExpReplace *mir() const {
         return mir_->toRegExpReplace();
     }
 };
 
+class LStringReplace: public LStrReplace
+{
+  public:
+    LIR_HEADER(StringReplace);
+
+    LStringReplace(const LAllocation &string, const LAllocation &pattern,
+                   const LAllocation &replacement)
+      : LStrReplace(string, pattern, replacement)
+    {
+    }
+
+    const MStringReplace *mir() const {
+        return mir_->toStringReplace();
+    }
+};
+
 class LLambdaForSingleton : public LCallInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(LambdaForSingleton)
 
     LLambdaForSingleton(const LAllocation &scopeChain)
     {
         setOperand(0, scopeChain);
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -147,16 +147,17 @@
     _(OsrValue)                     \
     _(OsrScopeChain)                \
     _(OsrReturnValue)               \
     _(OsrArgumentsObject)           \
     _(RegExp)                       \
     _(RegExpExec)                   \
     _(RegExpTest)                   \
     _(RegExpReplace)                \
+    _(StringReplace)                \
     _(Lambda)                       \
     _(LambdaForSingleton)           \
     _(LambdaPar)                    \
     _(ImplicitThis)                 \
     _(Slots)                        \
     _(Elements)                     \
     _(ConvertElementsToDoubles)     \
     _(MaybeToDoubleElement)         \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1934,22 +1934,35 @@ LIRGenerator::visitRegExpTest(MRegExpTes
     LRegExpTest *lir = new(alloc()) LRegExpTest(useRegisterAtStart(ins->regexp()),
                                                 useRegisterAtStart(ins->string()));
     return defineReturn(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitRegExpReplace(MRegExpReplace *ins)
 {
-    JS_ASSERT(ins->regexp()->type() == MIRType_Object);
+    JS_ASSERT(ins->pattern()->type() == MIRType_Object);
     JS_ASSERT(ins->string()->type() == MIRType_String);
     JS_ASSERT(ins->replacement()->type() == MIRType_String);
 
     LRegExpReplace *lir = new(alloc()) LRegExpReplace(useRegisterOrConstantAtStart(ins->string()),
-                                                      useRegisterAtStart(ins->regexp()),
+                                                      useRegisterAtStart(ins->pattern()),
+                                                      useRegisterOrConstantAtStart(ins->replacement()));
+    return defineReturn(lir, ins) && assignSafepoint(lir, ins);
+}
+
+bool
+LIRGenerator::visitStringReplace(MStringReplace *ins)
+{
+    JS_ASSERT(ins->pattern()->type() == MIRType_String);
+    JS_ASSERT(ins->string()->type() == MIRType_String);
+    JS_ASSERT(ins->replacement()->type() == MIRType_String);
+
+    LStringReplace *lir = new(alloc()) LStringReplace(useRegisterOrConstantAtStart(ins->string()),
+                                                      useRegisterAtStart(ins->pattern()),
                                                       useRegisterOrConstantAtStart(ins->replacement()));
     return defineReturn(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitLambda(MLambda *ins)
 {
     if (ins->info().singletonType || ins->info().useNewTypeForClone) {
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -143,16 +143,17 @@ class LIRGenerator : public LIRGenerator
     bool visitToFloat32(MToFloat32 *convert);
     bool visitToInt32(MToInt32 *convert);
     bool visitTruncateToInt32(MTruncateToInt32 *truncate);
     bool visitToString(MToString *convert);
     bool visitRegExp(MRegExp *ins);
     bool visitRegExpExec(MRegExpExec *ins);
     bool visitRegExpTest(MRegExpTest *ins);
     bool visitRegExpReplace(MRegExpReplace *ins);
+    bool visitStringReplace(MStringReplace *ins);
     bool visitLambda(MLambda *ins);
     bool visitLambdaPar(MLambdaPar *ins);
     bool visitImplicitThis(MImplicitThis *ins);
     bool visitSlots(MSlots *ins);
     bool visitElements(MElements *ins);
     bool visitConstantElements(MConstantElements *ins);
     bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins);
     bool visitMaybeToDoubleElement(MMaybeToDoubleElement *ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -115,17 +115,17 @@ IonBuilder::inlineNativeCall(CallInfo &c
         return inlineStringSplit(callInfo);
     if (native == js_str_charCodeAt)
         return inlineStrCharCodeAt(callInfo);
     if (native == js::str_fromCharCode)
         return inlineStrFromCharCode(callInfo);
     if (native == js_str_charAt)
         return inlineStrCharAt(callInfo);
     if (native == str_replace)
-        return inlineStrReplaceRegExp(callInfo);
+        return inlineStrReplace(callInfo);
 
     // RegExp natives.
     if (native == regexp_exec && CallResultEscapes(pc))
         return inlineRegExpExec(callInfo);
     if (native == regexp_exec && !CallResultEscapes(pc))
         return inlineRegExpTest(callInfo);
     if (native == regexp_test)
         return inlineRegExpTest(callInfo);
@@ -1173,48 +1173,53 @@ IonBuilder::inlineRegExpTest(CallInfo &c
     current->push(match);
     if (!resumeAfter(match))
         return InliningStatus_Error;
 
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
-IonBuilder::inlineStrReplaceRegExp(CallInfo &callInfo)
+IonBuilder::inlineStrReplace(CallInfo &callInfo)
 {
     if (callInfo.argc() != 2 || callInfo.constructing())
         return InliningStatus_NotInlined;
 
     // Return: String.
     if (getInlineReturnType() != MIRType_String)
         return InliningStatus_NotInlined;
 
     // This: String.
     if (callInfo.thisArg()->type() != MIRType_String)
         return InliningStatus_NotInlined;
 
     // Arg 0: RegExp.
     types::TemporaryTypeSet *arg0Type = callInfo.getArg(0)->resultTypeSet();
     const Class *clasp = arg0Type ? arg0Type->getKnownClass() : nullptr;
-    if (clasp != &RegExpObject::class_)
+    if (clasp != &RegExpObject::class_ && callInfo.getArg(0)->type() != MIRType_String)
         return InliningStatus_NotInlined;
 
     // Arg 1: String.
     if (callInfo.getArg(1)->type() != MIRType_String)
         return InliningStatus_NotInlined;
 
     callInfo.setImplicitlyUsedUnchecked();
 
-    MInstruction *cte = MRegExpReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0),
-                                            callInfo.getArg(1));
+    MInstruction *cte;
+    if (callInfo.getArg(0)->type() == MIRType_String) {
+        cte = MStringReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0),
+                                  callInfo.getArg(1));
+    } else {
+        cte = MRegExpReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0),
+                                  callInfo.getArg(1));
+    }
     current->add(cte);
     current->push(cte);
-    if (!resumeAfter(cte))
+    if (cte->isEffectful() && !resumeAfter(cte))
         return InliningStatus_Error;
-
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
 IonBuilder::inlineUnsafePutElements(CallInfo &callInfo)
 {
     uint32_t argc = callInfo.argc();
     if (argc < 3 || (argc % 3) != 0 || callInfo.constructing())
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4880,54 +4880,91 @@ class MRegExpTest
         return this;
     }
 
     bool possiblyCalls() const {
         return true;
     }
 };
 
-class MRegExpReplace
+template <class Policy1>
+class MStrReplace
   : public MTernaryInstruction,
-    public Mix3Policy<StringPolicy<0>, ObjectPolicy<1>, StringPolicy<2> >
-{
-  private:
-
-    MRegExpReplace(MDefinition *string, MDefinition *regexp, MDefinition *replacement)
-      : MTernaryInstruction(string, regexp, replacement)
-    {
+    public Mix3Policy<StringPolicy<0>, Policy1, StringPolicy<2> >
+{
+  protected:
+
+    MStrReplace(MDefinition *string, MDefinition *pattern, MDefinition *replacement)
+      : MTernaryInstruction(string, pattern, replacement)
+    {
+        setMovable();
         setResultType(MIRType_String);
     }
 
   public:
-    INSTRUCTION_HEADER(RegExpReplace)
-
-    static MRegExpReplace *New(TempAllocator &alloc, MDefinition *string, MDefinition *regexp, MDefinition *replacement) {
-        return new(alloc) MRegExpReplace(string, regexp, replacement);
-    }
 
     MDefinition *string() const {
         return getOperand(0);
     }
-    MDefinition *regexp() const {
+    MDefinition *pattern() const {
         return getOperand(1);
     }
     MDefinition *replacement() const {
         return getOperand(2);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
     bool possiblyCalls() const {
         return true;
     }
 };
 
+class MRegExpReplace
+    : public MStrReplace< ObjectPolicy<1> >
+{
+  private:
+
+    MRegExpReplace(MDefinition *string, MDefinition *pattern, MDefinition *replacement)
+      : MStrReplace< ObjectPolicy<1> >(string, pattern, replacement)
+    {
+    }
+
+  public:
+    INSTRUCTION_HEADER(RegExpReplace);
+
+    static MRegExpReplace *New(TempAllocator &alloc, MDefinition *string, MDefinition *pattern, MDefinition *replacement) {
+        return new(alloc) MRegExpReplace(string, pattern, replacement);
+    }
+};
+
+class MStringReplace
+    : public MStrReplace< StringPolicy<1> >
+{
+  private:
+
+    MStringReplace(MDefinition *string, MDefinition *pattern, MDefinition *replacement)
+      : MStrReplace< StringPolicy<1> >(string, pattern, replacement)
+    {
+    }
+
+  public:
+    INSTRUCTION_HEADER(StringReplace);
+
+    static MStringReplace *New(TempAllocator &alloc, MDefinition *string, MDefinition *pattern, MDefinition *replacement) {
+        return new(alloc) MStringReplace(string, pattern, replacement);
+    }
+
+    AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
+};
+
 struct LambdaFunctionInfo
 {
     // The functions used in lambdas are the canonical original function in
     // the script, and are immutable except for delazification. Record this
     // information while still on the main thread to avoid races.
     CompilerRootFunction fun;
     uint16_t flags;
     gc::Cell *scriptOrLazyScript;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -96,16 +96,17 @@ namespace jit {
     _(InitPropGetterSetter)                                                 \
     _(Start)                                                                \
     _(OsrEntry)                                                             \
     _(Nop)                                                                  \
     _(RegExp)                                                               \
     _(RegExpExec)                                                           \
     _(RegExpTest)                                                           \
     _(RegExpReplace)                                                        \
+    _(StringReplace)                                                        \
     _(Lambda)                                                               \
     _(ImplicitThis)                                                         \
     _(Slots)                                                                \
     _(Elements)                                                             \
     _(ConstantElements)                                                     \
     _(ConvertElementsToDoubles)                                             \
     _(MaybeToDoubleElement)                                                 \
     _(LoadSlot)                                                             \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -276,16 +276,17 @@ class ParallelSafetyVisitor : public MIn
     UNSAFE_OP(SetDOMProperty)
     UNSAFE_OP(NewStringObject)
     UNSAFE_OP(Random)
     UNSAFE_OP(Pow)
     UNSAFE_OP(PowHalf)
     UNSAFE_OP(RegExpTest)
     UNSAFE_OP(RegExpExec)
     UNSAFE_OP(RegExpReplace)
+    UNSAFE_OP(StringReplace)
     UNSAFE_OP(CallInstanceOf)
     UNSAFE_OP(FunctionBoundary)
     UNSAFE_OP(GuardString)
     UNSAFE_OP(NewDeclEnvObject)
     UNSAFE_OP(In)
     UNSAFE_OP(InArray)
     SAFE_OP(GuardThreadExclusive)
     SAFE_OP(CheckInterruptPar)
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -909,28 +909,42 @@ InitBaselineFrameForOsr(BaselineFrame *f
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type,
                                 HandleObject owner, int32_t offset)
 {
     return TypedObject::createDerived(cx, type, owner, offset);
 }
 
 JSString *
-regexp_replace(JSContext *cx, HandleString string, HandleObject regexp, HandleString repl)
+RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp, HandleString repl)
 {
-    JS_ASSERT(!!string);
-    JS_ASSERT(!!repl);
+    JS_ASSERT(string);
+    JS_ASSERT(repl);
 
     RootedValue rval(cx);
     if (!str_replace_regexp_raw(cx, string, regexp, repl, &rval))
         return nullptr;
 
     return rval.toString();
 }
 
+JSString *
+StringReplace(JSContext *cx, HandleString string, HandleString pattern, HandleString repl)
+{
+    JS_ASSERT(string);
+    JS_ASSERT(pattern);
+    JS_ASSERT(repl);
+
+    RootedValue rval(cx);
+    if (!str_replace_string_raw(cx, string, pattern, repl, &rval))
+        return nullptr;
+
+    return rval.toString();
+}
+
 bool
 Recompile(JSContext *cx)
 {
     JS_ASSERT(cx->currentlyRunningInJit());
     JitActivationIterator activations(cx->runtime());
     IonFrameIterator iter(activations);
 
     JS_ASSERT(iter.type() == IonFrame_Exit);
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -658,18 +658,20 @@ bool DebugLeaveBlock(JSContext *cx, Base
 
 bool InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame,
                              uint32_t numStackValues);
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type,
                                 HandleObject owner, int32_t offset);
 
 bool Recompile(JSContext *cx);
-JSString *regexp_replace(JSContext *cx, HandleString string, HandleObject regexp,
-                         HandleString repl);
+JSString *RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp,
+                        HandleString repl);
+JSString *StringReplace(JSContext *cx, HandleString string, HandleString pattern,
+                        HandleString repl);
 
 #ifdef DEBUG
 void AssertValidObjectPtr(JSContext *cx, JSObject *obj);
 void AssertValidStringPtr(JSContext *cx, JSString *str);
 void AssertValidValue(JSContext *cx, Value *v);
 #endif
 
 } // namespace jit
--- a/js/src/jit/arm/Bailouts-arm.cpp
+++ b/js/src/jit/arm/Bailouts-arm.cpp
@@ -71,21 +71,22 @@ class BailoutStack
     // inserts their own value int lr, which is then placed onto the stack along
     // with frameClassId_ above.  This should be migrated to ip.
   public:
     union {
         uintptr_t frameSize_;
         uintptr_t tableOffset_;
     };
 
-  private:
+  protected: // Silence Clang warning about unused private fields.
     mozilla::Array<double, FloatRegisters::Total> fpregs_;
     mozilla::Array<uintptr_t, Registers::Total> regs_;
 
     uintptr_t snapshotOffset_;
+    uintptr_t padding_;
 
   public:
     FrameSizeClass frameClass() const {
         return FrameSizeClass::FromClass(frameClassId_);
     }
     uintptr_t tableOffset() const {
         JS_ASSERT(frameClass() != FrameSizeClass::None());
         return tableOffset_;
@@ -104,16 +105,19 @@ class BailoutStack
     }
     uint8_t *parentStackPointer() const {
         if (frameClass() == FrameSizeClass::None())
             return (uint8_t *)this + sizeof(BailoutStack);
         return (uint8_t *)this + offsetof(BailoutStack, snapshotOffset_);
     }
 };
 
+// Make sure the compiler doesn't add extra padding.
+static_assert((sizeof(BailoutStack) % 8) == 0, "BailoutStack should be 8-byte aligned.");
+
 } // namespace jit
 } // namespace js
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
                                        BailoutStack *bailout)
   : IonFrameIterator(activations),
     machine_(bailout->machine())
 {
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -276,18 +276,18 @@ CodeGeneratorARM::bailout(LSnapshot *sna
     masm.ma_b(&label);
     return bailoutFrom(&label, snapshot);
 }
 
 bool
 CodeGeneratorARM::visitOutOfLineBailout(OutOfLineBailout *ool)
 {
     masm.ma_mov(Imm32(ool->snapshot()->snapshotOffset()), ScratchRegister);
-    masm.ma_push(ScratchRegister);
-    masm.ma_push(ScratchRegister);
+    masm.ma_push(ScratchRegister); // BailoutStack::padding_
+    masm.ma_push(ScratchRegister); // BailoutStack::snapshotOffset_
     masm.ma_b(&deoptLabel_);
     return true;
 }
 
 bool
 CodeGeneratorARM::visitMinMaxD(LMinMaxD *ins)
 {
     FloatRegister first = ToFloatRegister(ins->first());
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -760,19 +760,20 @@ JitRuntime::generateVMWrapper(JSContext 
     if (outReg != InvalidReg)
         masm.passABIArg(outReg);
 
     masm.callWithABI(f.wrapped);
 
     // Test for failure.
     switch (f.failType()) {
       case Type_Object:
+        masm.branchTestPtr(Assembler::Zero, r0, r0, masm.failureLabel(f.executionMode));
+        break;
       case Type_Bool:
-        // Called functions return bools, which are 0/false and non-zero/true
-        masm.branch32(Assembler::Equal, r0, Imm32(0), masm.failureLabel(f.executionMode));
+        masm.branchIfFalseBool(r0, masm.failureLabel(f.executionMode));
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("unknown failure kind");
     }
 
     // Load the outparam and free any allocated stack.
     switch (f.outParam) {
       case Type_Handle:
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2809,60 +2809,59 @@ JS_LookupPropertyWithFlags(JSContext *cx
     if (!atom)
         return false;
 
     RootedId id(cx, AtomToId(atom));
     return JS_LookupPropertyWithFlagsById(cx, obj, id, flags, &obj2, vp);
 }
 
 JS_PUBLIC_API(bool)
-JS_HasPropertyById(JSContext *cx, JSObject *objArg, jsid idArg, bool *foundp)
-{
-    RootedObject obj(cx, objArg);
-    RootedId id(cx, idArg);
+JS_HasPropertyById(JSContext *cx, HandleObject obj, HandleId id, bool *foundp)
+{
     RootedObject obj2(cx);
     RootedShape prop(cx);
     bool ok = LookupPropertyById(cx, obj, id, 0, &obj2, &prop);
     *foundp = (prop != nullptr);
     return ok;
 }
 
 JS_PUBLIC_API(bool)
-JS_HasElement(JSContext *cx, JSObject *objArg, uint32_t index, bool *foundp)
-{
-    RootedObject obj(cx, objArg);
+JS_HasElement(JSContext *cx, HandleObject obj, uint32_t index, bool *foundp)
+{
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
         return false;
     return JS_HasPropertyById(cx, obj, id, foundp);
 }
 
 JS_PUBLIC_API(bool)
-JS_HasProperty(JSContext *cx, JSObject *objArg, const char *name, bool *foundp)
-{
-    RootedObject obj(cx, objArg);
+JS_HasProperty(JSContext *cx, HandleObject obj, const char *name, bool *foundp)
+{
     JSAtom *atom = Atomize(cx, name, strlen(name));
-    return atom && JS_HasPropertyById(cx, obj, AtomToId(atom), foundp);
+    if (!atom)
+        return false;
+    RootedId id(cx, AtomToId(atom));
+    return JS_HasPropertyById(cx, obj, id, foundp);
 }
 
 JS_PUBLIC_API(bool)
-JS_HasUCProperty(JSContext *cx, JSObject *objArg, const jschar *name, size_t namelen, bool *foundp)
-{
-    RootedObject obj(cx, objArg);
+JS_HasUCProperty(JSContext *cx, HandleObject obj, const jschar *name, size_t namelen, bool *foundp)
+{
     JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen));
-    return atom && JS_HasPropertyById(cx, obj, AtomToId(atom), foundp);
+    if (!atom)
+        return false;
+    RootedId id(cx, AtomToId(atom));
+    return JS_HasPropertyById(cx, obj, id, foundp);
 }
 
 JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnPropertyById(JSContext *cx, JSObject *objArg, jsid id_, bool *foundp)
-{
-    RootedObject obj(cx, objArg);
-    RootedId id(cx, id_);
+JS_AlreadyHasOwnPropertyById(JSContext *cx, HandleObject obj, HandleId id, bool *foundp)
+{
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, id);
 
     if (!obj->isNative()) {
         RootedObject obj2(cx);
         RootedShape prop(cx);
 
@@ -2877,42 +2876,45 @@ JS_AlreadyHasOwnPropertyById(JSContext *
         return true;
     }
 
     *foundp = obj->nativeContains(cx, id);
     return true;
 }
 
 JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnElement(JSContext *cx, JSObject *objArg, uint32_t index, bool *foundp)
-{
-    RootedObject obj(cx, objArg);
+JS_AlreadyHasOwnElement(JSContext *cx, HandleObject obj, uint32_t index, bool *foundp)
+{
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
         return false;
     return JS_AlreadyHasOwnPropertyById(cx, obj, id, foundp);
 }
 
 JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnProperty(JSContext *cx, JSObject *objArg, const char *name, bool *foundp)
-{
-    RootedObject obj(cx, objArg);
+JS_AlreadyHasOwnProperty(JSContext *cx, HandleObject obj, const char *name, bool *foundp)
+{
     JSAtom *atom = Atomize(cx, name, strlen(name));
-    return atom && JS_AlreadyHasOwnPropertyById(cx, obj, AtomToId(atom), foundp);
+    if (!atom)
+        return false;
+    RootedId id(cx, AtomToId(atom));
+    return JS_AlreadyHasOwnPropertyById(cx, obj, id, foundp);
 }
 
 JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnUCProperty(JSContext *cx, JSObject *objArg, const jschar *name, size_t namelen,
+JS_AlreadyHasOwnUCProperty(JSContext *cx, HandleObject obj, const jschar *name, size_t namelen,
                            bool *foundp)
 {
-    RootedObject obj(cx, objArg);
     JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen));
-    return atom && JS_AlreadyHasOwnPropertyById(cx, obj, AtomToId(atom), foundp);
+    if (!atom)
+        return false;
+    RootedId id(cx, AtomToId(atom));
+    return JS_AlreadyHasOwnPropertyById(cx, obj, id, foundp);
 }
 
 /* Wrapper functions to create wrappers with no corresponding JSJitInfo from API
  * function arguments.
  */
 static JSPropertyOpWrapper
 GetterWrapper(JSPropertyOp getter)
 {
@@ -3452,47 +3454,44 @@ JS_SetUCProperty(JSContext *cx, JSObject
                  HandleValue v)
 {
     RootedObject obj(cx, objArg);
     JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen));
     return atom && JS_SetPropertyById(cx, obj, AtomToId(atom), v);
 }
 
 JS_PUBLIC_API(bool)
-JS_DeletePropertyById2(JSContext *cx, JSObject *objArg, jsid id, bool *result)
-{
-    RootedObject obj(cx, objArg);
+JS_DeletePropertyById2(JSContext *cx, HandleObject obj, HandleId id, bool *result)
+{
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, id);
     JSAutoResolveFlags rf(cx, 0);
 
     if (JSID_IS_SPECIAL(id)) {
         Rooted<SpecialId> sid(cx, JSID_TO_SPECIALID(id));
         return JSObject::deleteSpecial(cx, obj, sid, result);
     }
     return JSObject::deleteByValue(cx, obj, IdToValue(id), result);
 }
 
 JS_PUBLIC_API(bool)
-JS_DeleteElement2(JSContext *cx, JSObject *objArg, uint32_t index, bool *result)
-{
-    RootedObject obj(cx, objArg);
+JS_DeleteElement2(JSContext *cx, HandleObject obj, uint32_t index, bool *result)
+{
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     JSAutoResolveFlags rf(cx, 0);
 
     return JSObject::deleteElement(cx, obj, index, result);
 }
 
 JS_PUBLIC_API(bool)
-JS_DeleteProperty2(JSContext *cx, JSObject *objArg, const char *name, bool *result)
-{
-    RootedObject obj(cx, objArg);
+JS_DeleteProperty2(JSContext *cx, HandleObject obj, const char *name, bool *result)
+{
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     JSAutoResolveFlags rf(cx, 0);
 
     JSAtom *atom = Atomize(cx, name, strlen(name));
     if (!atom)
         return false;
     return JSObject::deleteByValue(cx, obj, StringValue(atom), result);
@@ -3509,34 +3508,34 @@ JS_DeleteUCProperty2(JSContext *cx, JSOb
 
     JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen));
     if (!atom)
         return false;
     return JSObject::deleteByValue(cx, obj, StringValue(atom), result);
 }
 
 JS_PUBLIC_API(bool)
-JS_DeletePropertyById(JSContext *cx, JSObject *objArg, jsid idArg)
+JS_DeletePropertyById(JSContext *cx, HandleObject obj, HandleId id)
 {
     bool junk;
-    return JS_DeletePropertyById2(cx, objArg, idArg, &junk);
+    return JS_DeletePropertyById2(cx, obj, id, &junk);
 }
 
 JS_PUBLIC_API(bool)
-JS_DeleteElement(JSContext *cx, JSObject *objArg, uint32_t index)
+JS_DeleteElement(JSContext *cx, HandleObject obj, uint32_t index)
 {
     bool junk;
-    return JS_DeleteElement2(cx, objArg, index, &junk);
+    return JS_DeleteElement2(cx, obj, index, &junk);
 }
 
 JS_PUBLIC_API(bool)
-JS_DeleteProperty(JSContext *cx, JSObject *objArg, const char *name)
+JS_DeleteProperty(JSContext *cx, HandleObject obj, const char *name)
 {
     bool junk;
-    return JS_DeleteProperty2(cx, objArg, name, &junk);
+    return JS_DeleteProperty2(cx, obj, name, &junk);
 }
 
 static Shape *
 LastConfigurableShape(JSObject *obj)
 {
     for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) {
         Shape *shape = &r.front();
         if (shape->configurable())
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2782,28 +2782,28 @@ JS_DefineOwnProperty(JSContext *cx, JSOb
 
 extern JS_PUBLIC_API(bool)
 JS_DefinePropertyWithTinyId(JSContext *cx, JSObject *obj, const char *name,
                             int8_t tinyid, jsval value,
                             JSPropertyOp getter, JSStrictPropertyOp setter,
                             unsigned attrs);
 
 extern JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnProperty(JSContext *cx, JSObject *obj, const char *name,
+JS_AlreadyHasOwnProperty(JSContext *cx, JS::HandleObject obj, const char *name,
                          bool *foundp);
 
 extern JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnPropertyById(JSContext *cx, JSObject *obj, jsid id,
+JS_AlreadyHasOwnPropertyById(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
                              bool *foundp);
 
 extern JS_PUBLIC_API(bool)
-JS_HasProperty(JSContext *cx, JSObject *obj, const char *name, bool *foundp);
+JS_HasProperty(JSContext *cx, JS::HandleObject obj, const char *name, bool *foundp);
 
 extern JS_PUBLIC_API(bool)
-JS_HasPropertyById(JSContext *cx, JSObject *obj, jsid id, bool *foundp);
+JS_HasPropertyById(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *foundp);
 
 extern JS_PUBLIC_API(bool)
 JS_LookupProperty(JSContext *cx, JSObject *obj, const char *name, JS::MutableHandleValue vp);
 
 extern JS_PUBLIC_API(bool)
 JS_LookupPropertyById(JSContext *cx, JSObject *obj, jsid id, JS::MutableHandleValue vp);
 
 extern JS_PUBLIC_API(bool)
@@ -2996,46 +2996,46 @@ JS_ForwardGetPropertyTo(JSContext *cx, J
 
 extern JS_PUBLIC_API(bool)
 JS_SetProperty(JSContext *cx, JSObject *obj, const char *name, JS::HandleValue v);
 
 extern JS_PUBLIC_API(bool)
 JS_SetPropertyById(JSContext *cx, JSObject *obj, jsid id, JS::HandleValue v);
 
 extern JS_PUBLIC_API(bool)
-JS_DeleteProperty(JSContext *cx, JSObject *obj, const char *name);
+JS_DeleteProperty(JSContext *cx, JS::HandleObject obj, const char *name);
 
 extern JS_PUBLIC_API(bool)
-JS_DeleteProperty2(JSContext *cx, JSObject *obj, const char *name, bool *succeeded);
+JS_DeleteProperty2(JSContext *cx, JS::HandleObject obj, const char *name, bool *succeeded);
 
 extern JS_PUBLIC_API(bool)
-JS_DeletePropertyById(JSContext *cx, JSObject *obj, jsid id);
+JS_DeletePropertyById(JSContext *cx, JS::HandleObject obj, jsid id);
 
 extern JS_PUBLIC_API(bool)
-JS_DeletePropertyById2(JSContext *cx, JSObject *obj, jsid id, bool *succeeded);
+JS_DeletePropertyById2(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *succeeded);
 
 extern JS_PUBLIC_API(bool)
 JS_DefineUCProperty(JSContext *cx, JSObject *obj,
                     const jschar *name, size_t namelen, jsval value,
                     JSPropertyOp getter, JSStrictPropertyOp setter,
                     unsigned attrs);
 
 extern JS_PUBLIC_API(bool)
 JS_DefineUCPropertyWithTinyId(JSContext *cx, JSObject *obj,
                               const jschar *name, size_t namelen,
                               int8_t tinyid, jsval value,
                               JSPropertyOp getter, JSStrictPropertyOp setter,
                               unsigned attrs);
 
 extern JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnUCProperty(JSContext *cx, JSObject *obj, const jschar *name,
+JS_AlreadyHasOwnUCProperty(JSContext *cx, JS::HandleObject obj, const jschar *name,
                            size_t namelen, bool *foundp);
 
 extern JS_PUBLIC_API(bool)
-JS_HasUCProperty(JSContext *cx, JSObject *obj,
+JS_HasUCProperty(JSContext *cx, JS::HandleObject obj,
                  const jschar *name, size_t namelen,
                  bool *vp);
 
 extern JS_PUBLIC_API(bool)
 JS_LookupUCProperty(JSContext *cx, JSObject *obj,
                     const jschar *name, size_t namelen,
                     JS::MutableHandleValue vp);
 
@@ -3065,39 +3065,39 @@ JS_GetArrayLength(JSContext *cx, JS::Han
 extern JS_PUBLIC_API(bool)
 JS_SetArrayLength(JSContext *cx, JS::Handle<JSObject*> obj, uint32_t length);
 
 extern JS_PUBLIC_API(bool)
 JS_DefineElement(JSContext *cx, JSObject *obj, uint32_t index, jsval value,
                  JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs);
 
 extern JS_PUBLIC_API(bool)
-JS_AlreadyHasOwnElement(JSContext *cx, JSObject *obj, uint32_t index, bool *foundp);
+JS_AlreadyHasOwnElement(JSContext *cx, JS::HandleObject obj, uint32_t index, bool *foundp);
 
 extern JS_PUBLIC_API(bool)
-JS_HasElement(JSContext *cx, JSObject *obj, uint32_t index, bool *foundp);
+JS_HasElement(JSContext *cx, JS::HandleObject obj, uint32_t index, bool *foundp);
 
 extern JS_PUBLIC_API(bool)
 JS_LookupElement(JSContext *cx, JSObject *obj, uint32_t index, JS::MutableHandleValue vp);
 
 extern JS_PUBLIC_API(bool)
 JS_GetElement(JSContext *cx, JSObject *obj, uint32_t index, JS::MutableHandleValue vp);
 
 extern JS_PUBLIC_API(bool)
 JS_ForwardGetElementTo(JSContext *cx, JSObject *obj, uint32_t index, JSObject *onBehalfOf,
                        JS::MutableHandleValue vp);
 
 extern JS_PUBLIC_API(bool)
 JS_SetElement(JSContext *cx, JSObject *obj, uint32_t index, JS::MutableHandleValue vp);
 
 extern JS_PUBLIC_API(bool)
-JS_DeleteElement(JSContext *cx, JSObject *obj, uint32_t index);
+JS_DeleteElement(JSContext *cx, JS::HandleObject obj, uint32_t index);
 
 extern JS_PUBLIC_API(bool)
-JS_DeleteElement2(JSContext *cx, JSObject *obj, uint32_t index, bool *succeeded);
+JS_DeleteElement2(JSContext *cx, JS::HandleObject obj, uint32_t index, bool *succeeded);
 
 /*
  * Remove all configurable properties from the given (non-global) object and
  * assign undefined to all writable data properties.
  */
 JS_PUBLIC_API(void)
 JS_ClearNonGlobalObject(JSContext *cx, JSObject *objArg);
 
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -382,18 +382,20 @@ EnsureTrackPropertyTypes(JSContext *cx, 
 
     if (obj->hasSingletonType()) {
         AutoEnterAnalysis enter(cx);
         if (obj->hasLazyType() && !obj->getType(cx)) {
             cx->compartment()->types.setPendingNukeTypes(cx);
             cx->clearPendingException();
             return;
         }
-        if (!obj->type()->unknownProperties())
-            obj->type()->getProperty(cx, id);
+        if (!obj->type()->unknownProperties() && !obj->type()->getProperty(cx, id)) {
+            cx->compartment()->types.setPendingNukeTypes(cx);
+            return;
+        }
     }
 
     JS_ASSERT(obj->type()->unknownProperties() || TrackPropertyTypes(cx, obj, id));
 }
 
 inline bool
 CanHaveEmptyPropertyTypesForOwnProperty(JSObject *obj)
 {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -442,32 +442,29 @@ js::XDRScript(XDRState<mode> *xdr, Handl
     uint32_t scriptBits = 0;
 
     JSContext *cx = xdr->cx();
     RootedScript script(cx);
     natoms = nsrcnotes = 0;
     nconsts = nobjects = nregexps = ntrynotes = nblockscopes = 0;
 
     /* XDR arguments and vars. */
-    uint16_t nargs = 0, nvars = 0;
-    uint32_t argsVars = 0;
+    uint16_t nargs = 0;
+    uint32_t nvars = 0;
     if (mode == XDR_ENCODE) {
         script = scriptp.get();
         JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment());
 
         nargs = script->bindings.numArgs();
         nvars = script->bindings.numVars();
-        argsVars = (nargs << 16) | nvars;
     }
-    if (!xdr->codeUint32(&argsVars))
+    if (!xdr->codeUint16(&nargs))
         return false;
-    if (mode == XDR_DECODE) {
-        nargs = argsVars >> 16;
-        nvars = argsVars & 0xFFFF;
-    }
+    if (!xdr->codeUint32(&nvars))
+        return false;
 
     if (mode == XDR_ENCODE)
         length = script->length();
     if (!xdr->codeUint32(&length))
         return false;
 
     if (mode == XDR_ENCODE) {
         prologLength = script->mainOffset();
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1726,16 +1726,23 @@ class MOZ_STACK_CLASS StringRegExpGuard
 
         JS_ASSERT(ObjectClassIs(obj_, ESClass_RegExp, cx));
 
         if (!RegExpToShared(cx, obj_, &re_))
             return false;
         return true;
     }
 
+    bool init(JSContext *cx, HandleString pattern) {
+        fm.patstr = AtomizeString(cx, pattern);
+        if (!fm.patstr)
+            return false;
+        return true;
+    }
+
     /*
      * Attempt to match |patstr| to |textstr|. A flags argument, metachars in
      * the pattern string, or a lengthy pattern string can thwart this process.
      *
      * |checkMetaChars| looks for regexp metachars in the pattern string.
      *
      * Return whether flat matching could be used.
      *
@@ -2444,17 +2451,17 @@ ReplaceRegExp(JSContext *cx, RegExpStati
 
     rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */
     DoReplace(res, rdata);
     return true;
 }
 
 static bool
 BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr,
-                     const FlatMatch &fm, CallArgs *args)
+                     const FlatMatch &fm, MutableHandleValue rval)
 {
     RopeBuilder builder(cx);
     size_t match = fm.match();
     size_t matchEnd = match + fm.patternLength();
 
     if (textstr->isRope()) {
         /*
          * If we are replacing over a rope, avoid flattening it by iterating
@@ -2516,29 +2523,29 @@ BuildFlatReplacement(JSContext *cx, Hand
         if (!rightSide ||
             !builder.append(leftSide) ||
             !builder.append(repstr) ||
             !builder.append(rightSide)) {
             return false;
         }
     }
 
-    args->rval().setString(builder.result());
+    rval.setString(builder.result());
     return true;
 }
 
 /*
  * Perform a linear-scan dollar substitution on the replacement text,
  * constructing a result string that looks like:
  *
  *      newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
  */
 static inline bool
 BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr,
-                       const jschar *firstDollar, const FlatMatch &fm, CallArgs *args)
+                       const jschar *firstDollar, const FlatMatch &fm, MutableHandleValue rval)
 {
     Rooted<JSLinearString*> textstr(cx, textstrArg->ensureLinear(cx));
     if (!textstr)
         return false;
 
     JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length());
     size_t matchStart = fm.match();
     size_t matchLimit = matchStart + fm.patternLength();
@@ -2600,17 +2607,17 @@ BuildDollarReplacement(JSContext *cx, JS
     ENSURE(rightSide);
 
     RopeBuilder builder(cx);
     ENSURE(builder.append(leftSide) &&
            builder.append(newReplace) &&
            builder.append(rightSide));
 #undef ENSURE
 
-    args->rval().setString(builder.result());
+    rval.setString(builder.result());
     return true;
 }
 
 struct StringRange
 {
     size_t start;
     size_t length;
 
@@ -2856,20 +2863,56 @@ js::str_replace_regexp_raw(JSContext *cx
 
     if (!rdata.g.init(cx, regexp))
         return false;
 
     return StrReplaceRegExp(cx, rdata, rval);
 }
 
 static inline bool
+StrReplaceString(JSContext *cx, ReplaceData &rdata, const FlatMatch &fm, MutableHandleValue rval)
+{
+    /*
+     * Note: we could optimize the text.length == pattern.length case if we wanted,
+     * even in the presence of dollar metachars.
+     */
+    if (rdata.dollar)
+        return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, fm, rval);
+    return BuildFlatReplacement(cx, rdata.str, rdata.repstr, fm, rval);
+}
+
+static const uint32_t ReplaceOptArg = 2;
+
+bool
+js::str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern,
+                          HandleString replacement, MutableHandleValue rval)
+{
+    ReplaceData rdata(cx);
+
+    rdata.str = string;
+    JSLinearString *repl = replacement->ensureLinear(cx);
+    if (!repl)
+        return false;
+    rdata.setReplacementString(repl);
+
+    if (!rdata.g.init(cx, pattern))
+        return false;
+    const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, ReplaceOptArg, false);
+
+    if (fm->match() < 0) {
+        rval.setString(string);
+        return true;
+    }
+
+    return StrReplaceString(cx, rdata, *fm, rval);
+}
+
+static inline bool
 str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm)
 {
-    JS_ASSERT(fm.match() >= 0);
-
     RootedString matchStr(cx, js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength()));
     if (!matchStr)
         return false;
 
     /* lambda(matchStr, matchStart, textstr) */
     static const uint32_t lambdaArgc = 3;
     if (!rdata.fig.args().init(lambdaArgc))
         return false;
@@ -2906,18 +2949,16 @@ str_replace_flat_lambda(JSContext *cx, C
           builder.append(rightSide))) {
         return false;
     }
 
     outerArgs.rval().setString(builder.result());
     return true;
 }
 
-static const uint32_t ReplaceOptArg = 2;
-
 /*
  * Pattern match the script to check if it is is indexing into a particular
  * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
  * such cases, which are used by javascript packers (particularly the popular
  * Dean Edwards packer) to efficiently encode large scripts. We only handle the
  * code patterns generated by such packers here.
  */
 static bool
@@ -3012,38 +3053,31 @@ js::str_replace(JSContext *cx, unsigned 
      *
      * However, if the user invokes our (non-standard) |flags| argument
      * extension then we revert to creating a regular expression. Note that
      * this is observable behavior through the side-effect mutation of the
      * |RegExp| statics.
      */
 
     const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false);
+
     if (!fm) {
         if (cx->isExceptionPending())  /* oom in RopeMatch in tryFlatMatch */
             return false;
         return str_replace_regexp(cx, args, rdata);
     }
 
     if (fm->match() < 0) {
         args.rval().setString(rdata.str);
         return true;
     }
 
     if (rdata.lambda)
         return str_replace_flat_lambda(cx, args, rdata, *fm);
-
-    /*
-     * Note: we could optimize the text.length == pattern.length case if we wanted,
-     * even in the presence of dollar metachars.
-     */
-    if (rdata.dollar)
-        return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, &args);
-
-    return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, &args);
+    return StrReplaceString(cx, rdata, *fm, args.rval());
 }
 
 namespace {
 
 class SplitMatchResult {
     size_t endIndex_;
     size_t length_;
 
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -365,14 +365,18 @@ str_split_string(JSContext *cx, HandleTy
 bool
 str_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
             MutableHandleObject objp);
 
 bool
 str_replace_regexp_raw(JSContext *cx, HandleString string, HandleObject regexp,
                        HandleString replacement, MutableHandleValue rval);
 
+bool
+str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern,
+                       HandleString replacement, MutableHandleValue rval);
+
 } /* namespace js */
 
 extern bool
 js_String(JSContext *cx, unsigned argc, js::Value *vp);
 
 #endif /* jsstr_h */
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -17,17 +17,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 163);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 164);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { }
 
     JSContext *cx() const {
         return context;
--- a/js/xpconnect/src/XPCJSID.cpp
+++ b/js/xpconnect/src/XPCJSID.cpp
@@ -620,17 +620,18 @@ nsJSCID::NewID(const char* str)
     if (str[0] == '{') {
         NS_ENSURE_SUCCESS(idObj->Initialize(str), nullptr);
     } else {
         nsCOMPtr<nsIComponentRegistrar> registrar;
         NS_GetComponentRegistrar(getter_AddRefs(registrar));
         NS_ENSURE_TRUE(registrar, nullptr);
 
         nsCID *cid;
-        NS_ENSURE_SUCCESS(registrar->ContractIDToCID(str, &cid), nullptr);
+        if (NS_FAILED(registrar->ContractIDToCID(str, &cid)))
+            return nullptr;
         bool success = idObj->mDetails.InitWithName(*cid, str);
         nsMemory::Free(cid);
         if (!success)
             return nullptr;
     }
     return idObj.forget();
 }
 
--- a/js/xpconnect/src/dictionary_helper_gen.py
+++ b/js/xpconnect/src/dictionary_helper_gen.py
@@ -188,37 +188,30 @@ def print_cpp_file(fd, conf):
     for d in conf.dictionaries:
         idl = loadIDL(p, options.incdirs, d[1])
         collect_names_and_non_primitive_attribute_types(idl, d[0], attrnames, includes)
     
     for c in includes:
       if not c in conf.exclude_automatic_type_include:
             fd.write("#include \"%s.h\"\n" % c)
 
-    fd.write("\nusing namespace mozilla::idl;\n\n")
+    fd.write("\n"
+             "using namespace mozilla::idl;\n"
+             "using namespace mozilla::dom;\n\n")
 
     for a in attrnames:
-        fd.write("static jsid %s = JSID_VOID;\n"% get_jsid(a))
+        fd.write("static InternedStringId %s;\n" % get_jsid(a))
 
     fd.write("\n"
-             "static bool\n"
-             "InternStaticJSVal(JSContext* aCx, jsid &id, const char* aString)\n"
-             "{\n"
-             "  if (JSString* str = JS_InternString(aCx, aString)) {\n"
-             "    id = INTERNED_STRING_TO_JSID(aCx, str);\n"
-             "    return true;\n"
-             "  }\n"
-             "  return false;\n"
-             "}\n\n"
              "bool\n"
              "InternStaticDictionaryJSVals(JSContext* aCx)\n"
              "{\n"
              "  return\n")
     for a in attrnames:
-        fd.write("    InternStaticJSVal(aCx, %s, \"%s\") &&\n"
+        fd.write("    %s.init(aCx, \"%s\") &&\n"
                  % (get_jsid(a), a))
 
     fd.write("    true;\n")
     fd.write("}\n\n")
 
     dicts = []
     for d in conf.dictionaries:
         if not d[0] in set(dicts):
--- a/mfbt/Attributes.h
+++ b/mfbt/Attributes.h
@@ -150,26 +150,33 @@
 #if defined(MOZ_HAVE_NORETURN)
 #  define MOZ_NORETURN          MOZ_HAVE_NORETURN
 #else
 #  define MOZ_NORETURN          /* no support */
 #endif
 
 /*
  * MOZ_ASAN_BLACKLIST is a macro to tell AddressSanitizer (a compile-time
- * instrumentation shipped with Clang) to not instrument the annotated function.
- * Furthermore, it will prevent the compiler from inlining the function because
- * inlining currently breaks the blacklisting mechanism of AddressSanitizer.
+ * instrumentation shipped with Clang and GCC) to not instrument the annotated
+ * function. Furthermore, it will prevent the compiler from inlining the
+ * function because inlining currently breaks the blacklisting mechanism of
+ * AddressSanitizer.
  */
 #if defined(__has_feature)
 #  if __has_feature(address_sanitizer)
-#    define MOZ_ASAN_BLACKLIST MOZ_NEVER_INLINE __attribute__((no_sanitize_address))
-#  else
-#    define MOZ_ASAN_BLACKLIST /* nothing */
+#    define MOZ_HAVE_ASAN_BLACKLIST
 #  endif
+#elif defined(__GNUC__)
+#  if defined(__SANITIZE_ADDRESS__)
+#    define MOZ_HAVE_ASAN_BLACKLIST
+#  endif
+#endif
+
+#if defined(MOZ_HAVE_ASAN_BLACKLIST)
+#  define MOZ_ASAN_BLACKLIST MOZ_NEVER_INLINE __attribute__((no_sanitize_address))
 #else
 #  define MOZ_ASAN_BLACKLIST /* nothing */
 #endif
 
 /*
  * MOZ_TSAN_BLACKLIST is a macro to tell ThreadSanitizer (a compile-time
  * instrumentation shipped with Clang) to not instrument the annotated function.
  * Furthermore, it will prevent the compiler from inlining the function because
--- a/mobile/android/base/fxa/FxAccountConstants.java.in
+++ b/mobile/android/base/fxa/FxAccountConstants.java.in
@@ -7,19 +7,19 @@ package org.mozilla.gecko.fxa;
 
 import org.mozilla.gecko.background.common.log.Logger;
 
 public class FxAccountConstants {
   public static final String GLOBAL_LOG_TAG = "FxAccounts";
   public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@";
 
   public static final String DEFAULT_IDP_ENDPOINT = "https://api-accounts-onepw.dev.lcip.org";
-  public static final String DEFAULT_AUTH_ENDPOINT = "http://auth.oldsync.dev.lcip.org";
-
-  public static final String PREFS_PATH = "fxa.v1";
+  public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "http://auth.oldsync.dev.lcip.org";
+  public static final String DEFAULT_TOKEN_SERVER_URI = DEFAULT_TOKEN_SERVER_ENDPOINT +
+            (DEFAULT_TOKEN_SERVER_ENDPOINT.endsWith("/") ? "" : "/") + "1.0/sync/1.1";
 
   // For extra debugging.  Not final so it can be changed from Fennec, or from
   // an add-on.
   public static boolean LOG_PERSONAL_INFORMATION = true;
 
   public static void pii(String tag, String message) {
     if (LOG_PERSONAL_INFORMATION) {
       Logger.info(tag, "$$FxA PII$$: " + message);
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
@@ -12,16 +12,17 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
+import org.mozilla.gecko.sync.setup.Constants;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -155,18 +156,23 @@ public class FxAccountCreateAccountActiv
     @Override
     public void handleSuccess(String result) {
       Activity activity = FxAccountCreateAccountActivity.this;
       Logger.info(LOG_TAG, "Got success creating account.");
 
       // We're on the UI thread, but it's okay to create the account here.
       Account account;
       try {
+        final String profile = Constants.DEFAULT_PROFILE;
+        final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI;
         account = AndroidFxAccount.addAndroidAccount(activity, email, password,
-            serverURI, null, null, false);
+            profile,
+            serverURI,
+            tokenServerURI,
+            null, null, false);
         if (account == null) {
           throw new RuntimeException("XXX what?");
         }
       } catch (Exception e) {
         handleError(e);
         return;
       }
 
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
@@ -12,16 +12,17 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
+import org.mozilla.gecko.sync.setup.Constants;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -124,18 +125,22 @@ public class FxAccountSignInActivity ext
     @Override
     public void handleSuccess(LoginResponse result) {
       Activity activity = FxAccountSignInActivity.this;
       Logger.info(LOG_TAG, "Got success signing in.");
 
       // We're on the UI thread, but it's okay to create the account here.
       Account account;
       try {
+        final String profile = Constants.DEFAULT_PROFILE;
+        final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI;
         account = AndroidFxAccount.addAndroidAccount(activity, email, password,
-            serverURI, result.sessionToken, result.keyFetchToken, result.verified);
+            serverURI,
+            tokenServerURI,
+            profile, result.sessionToken, result.keyFetchToken, result.verified);
         if (account == null) {
           throw new RuntimeException("XXX what?");
         }
       } catch (Exception e) {
         handleError(e);
         return;
       }
 
--- a/mobile/android/base/fxa/authenticator/AbstractFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AbstractFxAccount.java
@@ -33,17 +33,22 @@ import org.mozilla.gecko.browserid.Brows
  * <code>setWrappedKb</code> is called, at which point <code>kB</code> will be
  * computed and cached, ready to be returned by <code>getKb</code>.
  */
 public interface AbstractFxAccount {
   /**
    * Get the Firefox Account auth server URI that this account login flow should
    * talk to.
    */
-  public String getServerURI();
+  public String getAccountServerURI();
+
+  /**
+   * @return the profile name associated with the account, such as "default".
+   */
+  public String getProfile();
 
   public boolean isValid();
   public void setInvalid();
 
   public byte[] getSessionToken();
   public byte[] getKeyFetchToken();
 
   public void setSessionToken(byte[] token);
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
@@ -1,19 +1,22 @@
 /* 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/. */
 
 package org.mozilla.gecko.fxa.authenticator;
 
 import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Collections;
 
+import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.RSACryptoImplementation;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 
@@ -28,27 +31,40 @@ import android.os.Bundle;
  * <p>
  * Account user data is accessible only to the Android App(s) that own the
  * Account type. Account user data is not removed when the App's private data is
  * cleared.
  */
 public class AndroidFxAccount implements AbstractFxAccount {
   protected static final String LOG_TAG = AndroidFxAccount.class.getSimpleName();
 
-  public static final String ACCOUNT_KEY_ASSERTION = "assertion";
-  public static final String ACCOUNT_KEY_CERTIFICATE = "certificate";
-  public static final String ACCOUNT_KEY_INVALID = "invalid";
-  public static final String ACCOUNT_KEY_SERVERURI = "serverURI";
-  public static final String ACCOUNT_KEY_SESSION_TOKEN = "sessionToken";
-  public static final String ACCOUNT_KEY_KEY_FETCH_TOKEN = "keyFetchToken";
-  public static final String ACCOUNT_KEY_VERIFIED = "verified";
-  public static final String ACCOUNT_KEY_KA = "kA";
-  public static final String ACCOUNT_KEY_KB = "kB";
-  public static final String ACCOUNT_KEY_UNWRAPKB = "unwrapkB";
-  public static final String ACCOUNT_KEY_ASSERTION_KEY_PAIR = "assertionKeyPair";
+  public static final int CURRENT_PREFS_VERSION = 1;
+
+  public static final int CURRENT_ACCOUNT_VERSION = 3;
+  public static final String ACCOUNT_KEY_ACCOUNT_VERSION = "version";
+  public static final String ACCOUNT_KEY_PROFILE = "profile";
+  public static final String ACCOUNT_KEY_IDP_SERVER = "idpServerURI";
+
+  // The audience should always be a prefix of the token server URI.
+  public static final String ACCOUNT_KEY_AUDIENCE = "audience";                 // Sync-specific.
+  public static final String ACCOUNT_KEY_TOKEN_SERVER = "tokenServerURI";       // Sync-specific.
+  public static final String ACCOUNT_KEY_DESCRIPTOR = "descriptor";
+
+  public static final int CURRENT_BUNDLE_VERSION = 1;
+  public static final String BUNDLE_KEY_BUNDLE_VERSION = "version";
+  public static final String BUNDLE_KEY_ASSERTION = "assertion";
+  public static final String BUNDLE_KEY_CERTIFICATE = "certificate";
+  public static final String BUNDLE_KEY_INVALID = "invalid";
+  public static final String BUNDLE_KEY_SESSION_TOKEN = "sessionToken";
+  public static final String BUNDLE_KEY_KEY_FETCH_TOKEN = "keyFetchToken";
+  public static final String BUNDLE_KEY_VERIFIED = "verified";
+  public static final String BUNDLE_KEY_KA = "kA";
+  public static final String BUNDLE_KEY_KB = "kB";
+  public static final String BUNDLE_KEY_UNWRAPKB = "unwrapkB";
+  public static final String BUNDLE_KEY_ASSERTION_KEY_PAIR = "assertionKeyPair";
 
   protected final Context context;
   protected final AccountManager accountManager;
   protected final Account account;
 
   /**
    * Create an Android Firefox Account instance backed by an Android Account
    * instance.
@@ -66,260 +82,430 @@ public class AndroidFxAccount implements
    *          Android account to use for storage.
    */
   public AndroidFxAccount(Context applicationContext, Account account) {
     this.context = applicationContext;
     this.account = account;
     this.accountManager = AccountManager.get(this.context);
   }
 
+  protected int getAccountVersion() {
+    String v = accountManager.getUserData(account, ACCOUNT_KEY_ACCOUNT_VERSION);
+    if (v == null) {
+      return 0;         // Implicit.
+    }
+
+    try {
+      return Integer.parseInt(v, 10);
+    } catch (NumberFormatException ex) {
+      return 0;
+    }
+  }
+
+  protected void persistBundle(ExtendedJSONObject bundle) {
+    accountManager.setUserData(account, ACCOUNT_KEY_DESCRIPTOR, bundle.toJSONString());
+  }
+
+  protected ExtendedJSONObject unbundle() {
+    final int version = getAccountVersion();
+    if (version < CURRENT_ACCOUNT_VERSION) {
+      // Needs upgrade. For now, do nothing.
+      return null;
+    }
+
+    if (version > CURRENT_ACCOUNT_VERSION) {
+      // Oh dear.
+      return null;
+    }
+
+    String bundle = accountManager.getUserData(account, ACCOUNT_KEY_DESCRIPTOR);
+    if (bundle == null) {
+      return null;
+    }
+    return unbundleAccountV1(bundle);
+  }
+
+  protected String getBundleData(String key) {
+    ExtendedJSONObject o = unbundle();
+    if (o == null) {
+      return null;
+    }
+    return o.getString(key);
+  }
+
+  protected boolean getBundleDataBoolean(String key, boolean def) {
+    ExtendedJSONObject o = unbundle();
+    if (o == null) {
+      return def;
+    }
+    Boolean b = o.getBoolean(key);
+    if (b == null) {
+      return def;
+    }
+    return b.booleanValue();
+  }
+
+  protected byte[] getBundleDataBytes(String key) {
+    ExtendedJSONObject o = unbundle();
+    if (o == null) {
+      return null;
+    }
+    return o.getByteArrayHex(key);
+  }
+
+  protected void updateBundleDataBytes(String key, byte[] value) {
+    updateBundleValue(key, value == null ? null : Utils.byte2Hex(value));
+  }
+
+  protected void updateBundleValue(String key, boolean value) {
+    ExtendedJSONObject descriptor = unbundle();
+    if (descriptor == null) {
+      return;
+    }
+    descriptor.put(key, value);
+    persistBundle(descriptor);
+  }
+
+  protected void updateBundleValue(String key, String value) {
+    ExtendedJSONObject descriptor = unbundle();
+    if (descriptor == null) {
+      return;
+    }
+    descriptor.put(key, value);
+    persistBundle(descriptor);
+  }
+
+  private ExtendedJSONObject unbundleAccountV1(String bundle) {
+    ExtendedJSONObject o;
+    try {
+      o = new ExtendedJSONObject(bundle);
+    } catch (Exception e) {
+      return null;
+    }
+    if (CURRENT_BUNDLE_VERSION == o.getIntegerSafely(BUNDLE_KEY_BUNDLE_VERSION)) {
+      return o;
+    }
+    return null;
+  }
+
   @Override
   public byte[] getEmailUTF8() {
     try {
       return account.name.getBytes("UTF-8");
     } catch (UnsupportedEncodingException e) {
       // Ignore.
       return null;
     }
   }
 
+  /**
+   * Note that if the user clears data, an account will be left pointing to a
+   * deleted profile. Such is life.
+   */
+  @Override
+  public String getProfile() {
+    return accountManager.getUserData(account, ACCOUNT_KEY_PROFILE);
+  }
+
+  @Override
+  public String getAccountServerURI() {
+    return accountManager.getUserData(account, ACCOUNT_KEY_IDP_SERVER);
+  }
+
+  public String getAudience() {
+    return accountManager.getUserData(account, ACCOUNT_KEY_AUDIENCE);
+  }
+
+  public String getTokenServerURI() {
+    return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER);
+  }
+
+  /**
+   * This needs to return a string because of the tortured prefs access in GlobalSession.
+   */
+  public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
+    String profile = getProfile();
+    String username = account.name;
+
+    if (profile == null) {
+      throw new IllegalStateException("Missing profile. Cannot fetch prefs.");
+    }
+
+    if (username == null) {
+      throw new IllegalStateException("Missing username. Cannot fetch prefs.");
+    }
+
+    final String tokenServerURI = getTokenServerURI();
+    if (tokenServerURI == null) {
+      throw new IllegalStateException("No token server URI. Cannot fetch prefs.");
+    }
+
+    final String fxaServerURI = getAccountServerURI();
+    if (fxaServerURI == null) {
+      throw new IllegalStateException("No account server URI. Cannot fetch prefs.");
+    }
+
+    final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".fxa";
+    final long version = CURRENT_PREFS_VERSION;
+
+    // This is unique for each syncing 'view' of the account.
+    final String serverURLThing = fxaServerURI + "!" + tokenServerURI;
+    return Utils.getPrefsPath(product, username, serverURLThing, profile, version);
+  }
+
   @Override
   public void setQuickStretchedPW(byte[] quickStretchedPW) {
     accountManager.setPassword(account, quickStretchedPW == null ? null : Utils.byte2Hex(quickStretchedPW));
   }
 
 
   @Override
   public byte[] getQuickStretchedPW() {
     String quickStretchedPW = accountManager.getPassword(account);
     return quickStretchedPW == null ? null : Utils.hex2Byte(quickStretchedPW);
   }
 
   @Override
-  public String getServerURI() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_SERVERURI);
-  }
-
-  protected byte[] getUserDataBytes(String key) {
-    String data = accountManager.getUserData(account, key);
-    if (data == null) {
-      return null;
-    }
-    return Utils.hex2Byte(data);
-  }
-
-  @Override
   public byte[] getSessionToken() {
-    return getUserDataBytes(ACCOUNT_KEY_SESSION_TOKEN);
+    return getBundleDataBytes(BUNDLE_KEY_SESSION_TOKEN);
   }
 
   @Override
   public byte[] getKeyFetchToken() {
-    return getUserDataBytes(ACCOUNT_KEY_KEY_FETCH_TOKEN);
+    return getBundleDataBytes(BUNDLE_KEY_KEY_FETCH_TOKEN);
   }
 
   @Override
   public void setSessionToken(byte[] sessionToken) {
-    accountManager.setUserData(account, ACCOUNT_KEY_SESSION_TOKEN, sessionToken == null ? null : Utils.byte2Hex(sessionToken));
+    updateBundleDataBytes(BUNDLE_KEY_SESSION_TOKEN, sessionToken);
   }
 
   @Override
   public void setKeyFetchToken(byte[] keyFetchToken) {
-    accountManager.setUserData(account, ACCOUNT_KEY_KEY_FETCH_TOKEN, keyFetchToken == null ? null : Utils.byte2Hex(keyFetchToken));
+    updateBundleDataBytes(BUNDLE_KEY_KEY_FETCH_TOKEN, keyFetchToken);
   }
 
   @Override
   public boolean isVerified() {
-    String data = accountManager.getUserData(account, ACCOUNT_KEY_VERIFIED);
-    return Boolean.valueOf(data);
+    return getBundleDataBoolean(BUNDLE_KEY_VERIFIED, false);
   }
 
   @Override
   public void setVerified() {
-    accountManager.setUserData(account, ACCOUNT_KEY_VERIFIED, Boolean.valueOf(true).toString());
+    updateBundleValue(BUNDLE_KEY_VERIFIED, true);
   }
 
   @Override
   public byte[] getKa() {
-    return getUserDataBytes(ACCOUNT_KEY_KA);
+    return getBundleDataBytes(BUNDLE_KEY_KA);
   }
 
   @Override
   public void setKa(byte[] kA) {
-    accountManager.setUserData(account, ACCOUNT_KEY_KA, Utils.byte2Hex(kA));
+    updateBundleValue(BUNDLE_KEY_KA, Utils.byte2Hex(kA));
   }
 
   @Override
   public void setWrappedKb(byte[] wrappedKb) {
-    byte[] unwrapKb = getUserDataBytes(ACCOUNT_KEY_UNWRAPKB);
+    if (wrappedKb == null) {
+      final String message = "wrappedKb is null: cannot set kB.";
+      Logger.error(LOG_TAG, message);
+      throw new IllegalArgumentException(message);
+    }
+    byte[] unwrapKb = getBundleDataBytes(BUNDLE_KEY_UNWRAPKB);
+    if (unwrapKb == null) {
+      Logger.error(LOG_TAG, "unwrapKb is null: cannot set kB.");
+      return;
+    }
     byte[] kB = new byte[wrappedKb.length]; // We could hard-code this to be 32.
     for (int i = 0; i < wrappedKb.length; i++) {
       kB[i] = (byte) (wrappedKb[i] ^ unwrapKb[i]);
     }
-    accountManager.setUserData(account, ACCOUNT_KEY_KB, Utils.byte2Hex(kB));
+    updateBundleValue(BUNDLE_KEY_KB, Utils.byte2Hex(kB));
   }
 
   @Override
   public byte[] getKb() {
-    return getUserDataBytes(ACCOUNT_KEY_KB);
+    return getBundleDataBytes(BUNDLE_KEY_KB);
   }
 
   protected BrowserIDKeyPair generateNewAssertionKeyPair() throws GeneralSecurityException {
     Logger.info(LOG_TAG, "Generating new assertion key pair.");
     // TODO Have the key size be a non-constant in FxAccountUtils, or read from SharedPreferences, or...
     return RSACryptoImplementation.generateKeyPair(1024);
   }
 
   @Override
   public BrowserIDKeyPair getAssertionKeyPair() throws GeneralSecurityException {
     try {
-      String data = accountManager.getUserData(account, ACCOUNT_KEY_ASSERTION_KEY_PAIR);
+      String data = getBundleData(BUNDLE_KEY_ASSERTION_KEY_PAIR);
       return RSACryptoImplementation.fromJSONObject(new ExtendedJSONObject(data));
     } catch (Exception e) {
       // Fall through to generating a new key pair.
     }
 
     BrowserIDKeyPair keyPair = generateNewAssertionKeyPair();
-    accountManager.setUserData(account, ACCOUNT_KEY_ASSERTION_KEY_PAIR, keyPair.toJSONObject().toJSONString());
+
+    ExtendedJSONObject descriptor = unbundle();
+    if (descriptor == null) {
+      descriptor = new ExtendedJSONObject();
+    }
+    descriptor.put(BUNDLE_KEY_ASSERTION_KEY_PAIR, keyPair.toJSONObject().toJSONString());
+    persistBundle(descriptor);
     return keyPair;
   }
 
   @Override
   public String getCertificate() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_CERTIFICATE);
+    return getBundleData(BUNDLE_KEY_CERTIFICATE);
   }
 
   @Override
   public void setCertificate(String certificate) {
-    accountManager.setUserData(account, ACCOUNT_KEY_CERTIFICATE, certificate);
+    updateBundleValue(BUNDLE_KEY_CERTIFICATE, certificate);
   }
 
   @Override
   public String getAssertion() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_ASSERTION);
+    return getBundleData(BUNDLE_KEY_ASSERTION);
   }
 
   @Override
   public void setAssertion(String assertion) {
-    accountManager.setUserData(account, ACCOUNT_KEY_ASSERTION, assertion);
+    updateBundleValue(BUNDLE_KEY_ASSERTION, assertion);
   }
 
   /**
    * Extract a JSON dictionary of the string values associated to this account.
    * <p>
    * <b>For debugging use only!</b> The contents of this JSON object completely
    * determine the user's Firefox Account status and yield access to whatever
    * user data the device has access to.
    *
    * @return JSON-object of Strings.
    */
   public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject o = new ExtendedJSONObject();
-    for (String key : new String[] {
-        ACCOUNT_KEY_ASSERTION,
-        ACCOUNT_KEY_CERTIFICATE,
-        ACCOUNT_KEY_SERVERURI,
-        ACCOUNT_KEY_SESSION_TOKEN,
-        ACCOUNT_KEY_INVALID,
-        ACCOUNT_KEY_KEY_FETCH_TOKEN,
-        ACCOUNT_KEY_VERIFIED,
-        ACCOUNT_KEY_KA,
-        ACCOUNT_KEY_KB,
-        ACCOUNT_KEY_UNWRAPKB,
-        ACCOUNT_KEY_ASSERTION_KEY_PAIR,
-    }) {
-      o.put(key, accountManager.getUserData(account, key));
-    }
+    ExtendedJSONObject o = unbundle();
     o.put("email", account.name);
     try {
       o.put("emailUTF8", Utils.byte2Hex(account.name.getBytes("UTF-8")));
     } catch (UnsupportedEncodingException e) {
       // Ignore.
     }
     o.put("quickStretchedPW", accountManager.getPassword(account));
     return o;
   }
 
-  public static Account addAndroidAccount(Context context, String email, String password,
-      String serverURI, byte[] sessionToken, byte[] keyFetchToken, boolean verified)
-          throws UnsupportedEncodingException, GeneralSecurityException {
+  public static Account addAndroidAccount(
+      Context context,
+      String email,
+      String password,
+      String profile,
+      String idpServerURI,
+      String tokenServerURI,
+      byte[] sessionToken,
+      byte[] keyFetchToken,
+      boolean verified)
+          throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException {
     if (email == null) {
       throw new IllegalArgumentException("email must not be null");
     }
     if (password == null) {
       throw new IllegalArgumentException("password must not be null");
     }
-    if (serverURI == null) {
-      throw new IllegalArgumentException("serverURI must not be null");
+    if (idpServerURI == null) {
+      throw new IllegalArgumentException("idpServerURI must not be null");
+    }
+    if (tokenServerURI == null) {
+      throw new IllegalArgumentException("tokenServerURI must not be null");
     }
     // sessionToken and keyFetchToken are allowed to be null; they can be
     // fetched via /account/login from the password. These tokens are generated
     // by the server and we have no length or formatting guarantees. However, if
     // one is given, both should be given: they come from the server together.
     if ((sessionToken == null && keyFetchToken != null) ||
         (sessionToken != null && keyFetchToken == null)) {
       throw new IllegalArgumentException("none or both of sessionToken and keyFetchToken may be null");
     }
 
     byte[] emailUTF8 = email.getBytes("UTF-8");
     byte[] passwordUTF8 = password.getBytes("UTF-8");
     byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
     byte[] unwrapBkey = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
 
+    // Android has internal restrictions that require all values in this
+    // bundle to be strings. *sigh*
     Bundle userdata = new Bundle();
-    userdata.putString(AndroidFxAccount.ACCOUNT_KEY_SERVERURI, serverURI);
-    userdata.putString(AndroidFxAccount.ACCOUNT_KEY_SESSION_TOKEN, sessionToken == null ? null : Utils.byte2Hex(sessionToken));
-    userdata.putString(AndroidFxAccount.ACCOUNT_KEY_KEY_FETCH_TOKEN, keyFetchToken == null ? null : Utils.byte2Hex(keyFetchToken));
-    userdata.putString(AndroidFxAccount.ACCOUNT_KEY_VERIFIED, Boolean.valueOf(verified).toString());
-    userdata.putString(AndroidFxAccount.ACCOUNT_KEY_UNWRAPKB, Utils.byte2Hex(unwrapBkey));
+    userdata.putString(ACCOUNT_KEY_ACCOUNT_VERSION, "" + CURRENT_ACCOUNT_VERSION);
+    userdata.putString(ACCOUNT_KEY_IDP_SERVER, idpServerURI);
+    userdata.putString(ACCOUNT_KEY_TOKEN_SERVER, tokenServerURI);
+    userdata.putString(ACCOUNT_KEY_AUDIENCE, computeAudience(tokenServerURI));
+    userdata.putString(ACCOUNT_KEY_PROFILE, profile);
+
+    ExtendedJSONObject descriptor = new ExtendedJSONObject();
+    descriptor.put(BUNDLE_KEY_BUNDLE_VERSION, CURRENT_BUNDLE_VERSION);
+    descriptor.put(BUNDLE_KEY_SESSION_TOKEN, sessionToken == null ? null : Utils.byte2Hex(sessionToken));
+    descriptor.put(BUNDLE_KEY_KEY_FETCH_TOKEN, keyFetchToken == null ? null : Utils.byte2Hex(keyFetchToken));
+    descriptor.put(BUNDLE_KEY_VERIFIED, verified);
+    descriptor.put(BUNDLE_KEY_UNWRAPKB, Utils.byte2Hex(unwrapBkey));
+
+    userdata.putString(ACCOUNT_KEY_DESCRIPTOR, descriptor.toJSONString());
 
     Account account = new Account(email, FxAccountConstants.ACCOUNT_TYPE);
     AccountManager accountManager = AccountManager.get(context);
     boolean added = accountManager.addAccountExplicitly(account, Utils.byte2Hex(quickStretchedPW), userdata);
     if (!added) {
       return null;
     }
     FxAccountAuthenticator.enableSyncing(context, account);
     return account;
   }
 
+  // TODO: this is shit.
+  private static String computeAudience(String tokenServerURI) throws URISyntaxException {
+     URI uri = new URI(tokenServerURI);
+     return new URI(uri.getScheme(), uri.getHost(), null, null).toString();
+  }
+
   @Override
   public boolean isValid() {
-    // Boolean.valueOf only returns true for the string "true"; this errors in
-    // the direction of marking accounts valid.
-    boolean invalid = Boolean.valueOf(accountManager.getUserData(account, ACCOUNT_KEY_INVALID)).booleanValue();
-    return !invalid;
+    return !getBundleDataBoolean(BUNDLE_KEY_INVALID, false);
   }
 
   @Override
   public void setInvalid() {
-    accountManager.setUserData(account, ACCOUNT_KEY_INVALID, Boolean.valueOf(true).toString());
+    updateBundleValue(BUNDLE_KEY_INVALID, true);
   }
 
   /**
    * <b>For debugging only!</b>
    */
   public void dump() {
     if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
       return;
     }
     ExtendedJSONObject o = toJSONObject();
     ArrayList<String> list = new ArrayList<String>(o.keySet());
     Collections.sort(list);
     for (String key : list) {
-      FxAccountConstants.pii(LOG_TAG, key + ": " + o.getString(key));
+      FxAccountConstants.pii(LOG_TAG, key + ": " + o.get(key));
     }
   }
 
   /**
    * <b>For debugging only!</b>
    */
   public void forgetAccountTokens() {
-    accountManager.setUserData(account, ACCOUNT_KEY_SESSION_TOKEN, null);
-    accountManager.setUserData(account, ACCOUNT_KEY_KEY_FETCH_TOKEN, null);
+    ExtendedJSONObject descriptor = unbundle();
+    if (descriptor == null) {
+      return;
+    }
+    descriptor.remove(BUNDLE_KEY_SESSION_TOKEN);
+    descriptor.remove(BUNDLE_KEY_KEY_FETCH_TOKEN);
+    persistBundle(descriptor);
   }
 
   /**
    * <b>For debugging only!</b>
    */
   public void forgetQuickstretchedPW() {
     accountManager.setPassword(account, null);
   }
--- a/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java
+++ b/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java
@@ -51,17 +51,17 @@ public class FxAccountAuthenticator exte
     final AccountManager accountManager = AccountManager.get(context);
     final Account account = new Account(email, FxAccountConstants.ACCOUNT_TYPE);
     final Bundle userData = new Bundle();
     userData.putString(JSON_KEY_UID, uid);
     userData.putString(JSON_KEY_SESSION_TOKEN, sessionToken);
     userData.putString(JSON_KEY_KA, kA);
     userData.putString(JSON_KEY_KB, kB);
     userData.putString(JSON_KEY_IDP_ENDPOINT, FxAccountConstants.DEFAULT_IDP_ENDPOINT);
-    userData.putString(JSON_KEY_AUTH_ENDPOINT, FxAccountConstants.DEFAULT_AUTH_ENDPOINT);
+    userData.putString(JSON_KEY_AUTH_ENDPOINT, FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
     if (!accountManager.addAccountExplicitly(account, sessionToken, userData)) {
       Logger.warn(LOG_TAG, "Error adding account named " + account.name + " of type " + account.type);
       return null;
     }
     // Enable syncing by default.
     enableSyncing(context, account);
     return account;
   }
--- a/mobile/android/base/fxa/authenticator/FxAccountLoginPolicy.java
+++ b/mobile/android/base/fxa/authenticator/FxAccountLoginPolicy.java
@@ -49,17 +49,17 @@ public class FxAccountLoginPolicy {
     return certificateDurationInMilliseconds;
   }
 
   public long getAssertionDurationInMilliseconds() {
     return assertionDurationInMilliseconds;
   }
 
   protected FxAccountClient makeFxAccountClient() {
-    String serverURI = fxAccount.getServerURI();
+    String serverURI = fxAccount.getAccountServerURI();
     return new FxAccountClient20(serverURI, executor);
   }
 
   private SkewHandler skewHandler;
 
   /**
    * Check if this certificate is not worth generating an assertion from: for
    * example, because it is not well-formed, or it is already expired.
@@ -96,17 +96,17 @@ public class FxAccountLoginPolicy {
     NeedsVerification,
     NeedsKeys,
     NeedsCertificate,
     NeedsAssertion,
     Valid,
   };
 
   public AccountState getAccountState(AbstractFxAccount fxAccount) {
-    String serverURI = fxAccount.getServerURI();
+    String serverURI = fxAccount.getAccountServerURI();
     byte[] emailUTF8 = fxAccount.getEmailUTF8();
     byte[] quickStretchedPW = fxAccount.getQuickStretchedPW();
     if (!fxAccount.isValid() || serverURI == null || emailUTF8 == null || quickStretchedPW == null) {
       return AccountState.Invalid;
     }
 
     byte[] sessionToken = fxAccount.getSessionToken();
     if (sessionToken == null) {
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -18,16 +18,17 @@ import org.mozilla.gecko.fxa.FxAccountCo
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginDelegate;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginException;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginPolicy;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
+import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 import org.mozilla.gecko.tokenserver.TokenServerClient;
 import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
@@ -152,36 +153,40 @@ public class FxAccountSyncAdapter extend
     Logger.info(LOG_TAG, "Syncing FxAccount" +
         " account named " + account.name +
         " for authority " + authority +
         " with instance " + this + ".");
 
     final CountDownLatch latch = new CountDownLatch(1);
     final BaseGlobalSessionCallback callback = new SessionCallback(latch, syncResult);
 
-
     try {
-      final String authEndpoint = FxAccountConstants.DEFAULT_AUTH_ENDPOINT;
-      final String tokenServerEndpoint = authEndpoint + (authEndpoint.endsWith("/") ? "" : "/") + "1.0/sync/1.1";
-      final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
-
-      final AndroidFxAccount fxAccount = new AndroidFxAccount(getContext(), account);
+      final Context context = getContext();
+      final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
 
       if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
         fxAccount.dump();
       }
 
-      final SharedPreferences sharedPrefs = getContext().getSharedPreferences(FxAccountConstants.PREFS_PATH, Context.MODE_PRIVATE); // TODO Ensure preferences are per-Account.
+      final String prefsPath = fxAccount.getSyncPrefsPath();
+
+      // This will be the same chunk of SharedPreferences that GlobalSession/SyncConfiguration will later create.
+      final SharedPreferences sharedPrefs = context.getSharedPreferences(prefsPath, Utils.SHARED_PREFERENCES_MODE);
 
-      final FxAccountLoginPolicy loginPolicy = new FxAccountLoginPolicy(getContext(), fxAccount, executor);
+      final String audience = fxAccount.getAudience();
+      final String tokenServerEndpoint = fxAccount.getTokenServerURI();
+      final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
+
+      // TODO: why doesn't the loginPolicy extract the audience from the account?
+      final FxAccountLoginPolicy loginPolicy = new FxAccountLoginPolicy(context, fxAccount, executor);
       loginPolicy.certificateDurationInMilliseconds = 20 * 60 * 1000;
       loginPolicy.assertionDurationInMilliseconds = 15 * 60 * 1000;
       Logger.info(LOG_TAG, "Asking for certificates to expire after 20 minutes and assertions to expire after 15 minutes.");
 
-      loginPolicy.login(authEndpoint, new FxAccountLoginDelegate() {
+      loginPolicy.login(audience, new FxAccountLoginDelegate() {
         @Override
         public void handleSuccess(final String assertion) {
           TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
           tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, new TokenServerClientDelegate() {
             @Override
             public void handleSuccess(final TokenServerToken token) {
               FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
               sharedPrefs.edit().putLong("tokenFailures", 0).commit();
@@ -196,17 +201,17 @@ public class FxAccountSyncAdapter extend
                 // We compute skew over time using SkewHandler. This yields an unchanging
                 // skew adjustment that the HawkAuthHeaderProvider uses to adjust its
                 // timestamps. Eventually we might want this to adapt within the scope of a
                 // global session.
                 final SkewHandler tokenServerSkewHandler = SkewHandler.getSkewHandlerFromEndpointString(token.endpoint);
                 final long tokenServerSkew = tokenServerSkewHandler.getSkewInSeconds();
                 AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, tokenServerSkew);
 
-                globalSession = new FxAccountGlobalSession(token.endpoint, token.uid, authHeaderProvider, FxAccountConstants.PREFS_PATH, syncKeyBundle, callback, getContext(), extras, clientsDataDelegate);
+                globalSession = new FxAccountGlobalSession(token.endpoint, token.uid, authHeaderProvider, prefsPath, syncKeyBundle, callback, context, extras, clientsDataDelegate);
                 globalSession.start();
               } catch (Exception e) {
                 callback.handleError(globalSession, e);
                 return;
               }
             }
 
             @Override
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -13,17 +13,17 @@ import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
-final class HomeConfig {
+public final class HomeConfig {
     /**
      * Used to determine what type of HomeFragment subclass to use when creating
      * a given panel. With the exception of DYNAMIC, all of these types correspond
      * to a default set of built-in panels. The DYNAMIC panel type is used by
      * third-party services to create panels with varying types of content.
      */
     public static enum PanelType implements Parcelable {
         TOP_SITES("top_sites", TopSitesPanel.class),
@@ -95,21 +95,24 @@ final class HomeConfig {
         private final EnumSet<Flags> mFlags;
 
         private static final String JSON_KEY_TYPE = "type";
         private static final String JSON_KEY_TITLE = "title";
         private static final String JSON_KEY_ID = "id";
         private static final String JSON_KEY_LAYOUT = "layout";
         private static final String JSON_KEY_VIEWS = "views";
         private static final String JSON_KEY_DEFAULT = "default";
+        private static final String JSON_KEY_DISABLED = "disabled";
 
         private static final int IS_DEFAULT = 1;
+        private static final int IS_DISABLED = 1;
 
         public enum Flags {
-            DEFAULT_PANEL
+            DEFAULT_PANEL,
+            DISABLED_PANEL
         }
 
         public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
             mType = PanelType.fromId(json.getString(JSON_KEY_TYPE));
             mTitle = json.getString(JSON_KEY_TITLE);
             mId = json.getString(JSON_KEY_ID);
 
             final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null);
@@ -135,16 +138,21 @@ final class HomeConfig {
 
             mFlags = EnumSet.noneOf(Flags.class);
 
             final boolean isDefault = (json.optInt(JSON_KEY_DEFAULT, -1) == IS_DEFAULT);
             if (isDefault) {
                 mFlags.add(Flags.DEFAULT_PANEL);
             }
 
+            final boolean isDisabled = (json.optInt(JSON_KEY_DISABLED, -1) == IS_DISABLED);
+            if (isDisabled) {
+                mFlags.add(Flags.DISABLED_PANEL);
+            }
+
             validate();
         }
 
         @SuppressWarnings("unchecked")
         public PanelConfig(Parcel in) {
             mType = (PanelType) in.readParcelable(getClass().getClassLoader());
             mTitle = in.readString();
             mId = in.readString();
@@ -153,32 +161,47 @@ final class HomeConfig {
             mViews = new ArrayList<ViewConfig>();
             in.readTypedList(mViews, ViewConfig.CREATOR);
 
             mFlags = (EnumSet<Flags>) in.readSerializable();
 
             validate();
         }
 
+        public PanelConfig(PanelConfig panelConfig) {
+            mType = panelConfig.mType;
+            mTitle = panelConfig.mTitle;
+            mId = panelConfig.mId;
+            mLayoutType = panelConfig.mLayoutType;
+
+            mViews = new ArrayList<ViewConfig>();
+            for (ViewConfig viewConfig : panelConfig.mViews) {
+                mViews.add(new ViewConfig(viewConfig));
+            }
+            mFlags = panelConfig.mFlags.clone();
+
+            validate();
+        }
+
         public PanelConfig(PanelType type, String title, String id) {
             this(type, title, id, EnumSet.noneOf(Flags.class));
         }
 
         public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
             this(type, title, id, null, null, flags);
         }
 
         public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
                 List<ViewConfig> views, EnumSet<Flags> flags) {
             mType = type;
             mTitle = title;
             mId = id;
-            mFlags = flags;
             mLayoutType = layoutType;
             mViews = views;
+            mFlags = flags;
 
             validate();
         }
 
         private void validate() {
             if (mType == null) {
                 throw new IllegalArgumentException("Can't create PanelConfig with null type");
             }
@@ -227,16 +250,36 @@ final class HomeConfig {
         public ViewConfig getViewAt(int index) {
             return (mViews != null ? mViews.get(index) : null);
         }
 
         public boolean isDefault() {
             return mFlags.contains(Flags.DEFAULT_PANEL);
         }
 
+        public void setIsDefault(boolean isDefault) {
+            if (isDefault) {
+                mFlags.add(Flags.DEFAULT_PANEL);
+            } else {
+                mFlags.remove(Flags.DEFAULT_PANEL);
+            }
+        }
+
+        public boolean isDisabled() {
+            return mFlags.contains(Flags.DISABLED_PANEL);
+        }
+
+        public void setIsDisabled(boolean isDisabled) {
+            if (isDisabled) {
+                mFlags.add(Flags.DISABLED_PANEL);
+            } else {
+                mFlags.remove(Flags.DISABLED_PANEL);
+            }
+        }
+
         public JSONObject toJSON() throws JSONException {
             final JSONObject json = new JSONObject();
 
             json.put(JSON_KEY_TYPE, mType.toString());
             json.put(JSON_KEY_TITLE, mTitle);
             json.put(JSON_KEY_ID, mId);
 
             if (mLayoutType != null) {
@@ -255,16 +298,20 @@ final class HomeConfig {
 
                 json.put(JSON_KEY_VIEWS, jsonViews);
             }
 
             if (mFlags.contains(Flags.DEFAULT_PANEL)) {
                 json.put(JSON_KEY_DEFAULT, IS_DEFAULT);
             }
 
+            if (mFlags.contains(Flags.DISABLED_PANEL)) {
+                json.put(JSON_KEY_DISABLED, IS_DISABLED);
+            }
+
             return json;
         }
 
         @Override
         public int describeContents() {
             return 0;
         }
 
@@ -410,16 +457,23 @@ final class HomeConfig {
         @SuppressWarnings("unchecked")
         public ViewConfig(Parcel in) {
             mType = (ViewType) in.readParcelable(getClass().getClassLoader());
             mDatasetId = in.readString();
 
             validate();
         }
 
+        public ViewConfig(ViewConfig viewConfig) {
+            mType = viewConfig.mType;
+            mDatasetId = viewConfig.mDatasetId;
+
+            validate();
+        }
+
         public ViewConfig(ViewType type, String datasetId) {
             mType = type;
             mDatasetId = datasetId;
 
             validate();
         }
 
         private void validate() {
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -23,16 +23,17 @@ import android.support.v4.app.LoaderMana
 import android.support.v4.content.Loader;
 import android.support.v4.view.ViewPager;
 import android.view.ViewGroup.LayoutParams;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.ViewGroup;
 import android.view.View;
 
+import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
 public class HomePager extends ViewPager {
 
     private static final int LOADER_ID_CONFIG = 0;
 
     private final Context mContext;
@@ -291,37 +292,44 @@ public class HomePager extends ViewPager
         }
 
         final HomeAdapter adapter = (HomeAdapter) getAdapter();
 
         // Destroy any existing panels currently loaded
         // in the pager.
         setAdapter(null);
 
-        // Update the adapter with the new panel configs
-        adapter.update(panelConfigs);
+        // Only keep enabled panels.
+        final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>();
 
-        // Hide the tab strip if the new configuration contains
-        // no panels for some reason.
-        final int count = (panelConfigs != null ? panelConfigs.size() : 0);
+        for (PanelConfig panelConfig : panelConfigs) {
+            if (!panelConfig.isDisabled()) {
+                enabledPanels.add(panelConfig);
+            }
+        }
+
+        // Update the adapter with the new panel configs
+        adapter.update(enabledPanels);
+
+        // Hide the tab strip if the new configuration contains no panels.
+        final int count = enabledPanels.size();
         mTabStrip.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE);
-
         // Re-install the adapter with the final state
         // in the pager.
         setAdapter(adapter);
 
         // Use the default panel as defined in the HomePager's configuration
         // if the initial panel wasn't explicitly set by the show() caller.
         if (mInitialPanelId != null) {
             // XXX: Handle the case where the desired panel isn't currently in the adapter (bug 949178)
             setCurrentItem(adapter.getItemPosition(mInitialPanelId), false);
             mInitialPanelId = null;
         } else {
             for (int i = 0; i < count; i++) {
-                final PanelConfig panelConfig = panelConfigs.get(i);
+                final PanelConfig panelConfig = enabledPanels.get(i);
                 if (panelConfig.isDefault()) {
                     setCurrentItem(i, false);
                     break;
                 }
             }
         }
     }
 
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -80,16 +80,19 @@
      advisory message on the customise search providers settings page explaining how to add new
      search providers.-->
 <!ENTITY pref_search_tip "TIP: Add any website to your list of search providers by long-pressing on its search field.">
 <!ENTITY pref_category_devtools "Developer tools">
 <!ENTITY pref_developer_remotedebugging "Remote debugging">
 <!ENTITY pref_developer_remotedebugging_docs "Learn more">
 <!ENTITY pref_remember_signons "Remember passwords">
 
+<!ENTITY pref_category_home "Home">
+<!ENTITY pref_category_home_panels "Panels">
+
 <!-- Localization note: These are shown in the left sidebar on tablets -->
 <!ENTITY pref_header_customize "Customize">
 <!ENTITY pref_header_display "Display">
 <!ENTITY pref_header_privacy_short "Privacy">
 <!ENTITY pref_header_help "Help">
 <!ENTITY pref_header_vendor "&vendorShortName;">
 <!ENTITY pref_header_devtools "Developer tools">
 
@@ -146,21 +149,26 @@ size. -->
 <!ENTITY pref_private_data_offlineApps "Offline website data">
 <!ENTITY pref_private_data_siteSettings2 "Site settings">
 <!ENTITY pref_private_data_downloadFiles "Downloaded files">
 
 
 <!ENTITY pref_about_firefox "About &brandShortName;">
 <!ENTITY pref_vendor_faqs "FAQs">
 <!ENTITY pref_vendor_feedback "Give feedback">
-<!ENTITY pref_search_set_default "Set as default">
-<!ENTITY pref_search_default "Default">
-<!ENTITY pref_search_remove "Remove">
+
+<!ENTITY pref_dialog_set_default "Set as default">
+<!ENTITY pref_dialog_default "Default">
+<!ENTITY pref_dialog_remove "Remove">
+
 <!ENTITY pref_search_last_toast "You can\'t remove or disable your last search engine.">
 
+<!ENTITY pref_panels_show "Show">
+<!ENTITY pref_panels_hide "Hide">
+
 <!ENTITY datareporting_notification_title "&brandShortName; stats &amp; data">
 <!ENTITY datareporting_notification_action_long "Choose what information to share">
 <!ENTITY datareporting_notification_action "Choose what to share">
 <!ENTITY datareporting_notification_summary "To improve your experience, &brandShortName; automatically sends some information to &vendorShortName;.">
 <!ENTITY datareporting_notification_summary_short "To improve your experience, &brandShortName;…">
 <!ENTITY datareporting_notification_ticker_text "&datareporting_notification_title;: &datareporting_notification_action_long;">
 
 <!-- Localization note (datareporting_fhr_title, datareporting_fhr_summary2,
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -264,21 +264,25 @@ gbjar.sources += [
     'NotificationHandler.java',
     'NotificationHelper.java',
     'NotificationService.java',
     'NSSBridge.java',
     'OrderedBroadcastHelper.java',
     'preferences/AlignRightLinkPreference.java',
     'preferences/AndroidImport.java',
     'preferences/AndroidImportPreference.java',
+    'preferences/CustomListCategory.java',
+    'preferences/CustomListPreference.java',
     'preferences/FontSizePreference.java',
     'preferences/GeckoPreferenceFragment.java',
     'preferences/GeckoPreferences.java',
     'preferences/LinkPreference.java',
     'preferences/MultiChoicePreference.java',
+    'preferences/PanelsPreference.java',
+    'preferences/PanelsPreferenceCategory.java',
     'preferences/PrivateDataPreference.java',
     'preferences/SearchEnginePreference.java',
     'preferences/SearchPreferenceCategory.java',
     'preferences/SyncPreference.java',
     'PrefsHelper.java',
     'PrivateTab.java',
     'prompts/ColorPickerInput.java',
     'prompts/IconGridInput.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/preferences/CustomListCategory.java
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.preferences;
+
+import android.content.Context;
+import android.preference.PreferenceCategory;
+import android.util.AttributeSet;
+
+public abstract class CustomListCategory extends PreferenceCategory {
+    protected CustomListPreference mDefaultReference;
+
+    public CustomListCategory(Context context) {
+        super(context);
+    }
+
+    public CustomListCategory(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CustomListCategory(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onAttachedToActivity() {
+        super.onAttachedToActivity();
+
+        setOrderingAsAdded(true);
+    }
+
+    /**
+     * Set the default to some available list item. Used if the current default is removed or
+     * disabled.
+     */
+    protected void setFallbackDefault() {
+        if (getPreferenceCount() > 0) {
+            CustomListPreference aItem = (CustomListPreference) getPreference(0);
+            setDefault(aItem);
+        }
+    }
+
+    /**
+     * Removes the given item from the set of available list items.
+     * This only updates the UI, so callers are responsible for persisting any state.
+     *
+     * @param item The given item to remove.
+     */
+    public void uninstall(CustomListPreference item) {
+        removePreference(item);
+        if (item == mDefaultReference) {
+            // If the default is being deleted, set a new default.
+            setFallbackDefault();
+        }
+    }
+
+    /**
+     * Sets the given item as the current default.
+     * This only updates the UI, so callers are responsible for persisting any state.
+     *
+     * @param item The intended new default.
+     */
+    public void setDefault(CustomListPreference item) {
+        if (mDefaultReference != null) {
+            mDefaultReference.setIsDefault(false);
+        }
+
+        item.setIsDefault(true);
+        mDefaultReference = item;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/preferences/CustomListPreference.java
@@ -0,0 +1,173 @@
+/* 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/. */
+
+package org.mozilla.gecko.preferences;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.util.ThreadUtils;
+
+/**
+ * Represents an element in a <code>CustomListCategory</code> preference menu.
+ * This preference con display a dialog when clicked, and also supports
+ * being set as a default item within the preference list category.
+ */
+
+public abstract class CustomListPreference extends Preference implements View.OnLongClickListener {
+    protected String LOGTAG = "CustomListPreference";
+
+    // Indices of the buttons of the Dialog.
+    public static final int INDEX_SET_DEFAULT_BUTTON = 0;
+
+    // Dialog item labels.
+    protected final String[] mDialogItems;
+
+    // Dialog displayed when this element is tapped.
+    protected AlertDialog mDialog;
+
+    // Cache label to avoid repeated use of the resource system.
+    public final String LABEL_IS_DEFAULT;
+    public final String LABEL_SET_AS_DEFAULT;
+
+    protected boolean mIsDefault;
+
+    // Enclosing parent category that contains this preference.
+    protected final CustomListCategory mParentCategory;
+
+    /**
+     * Create a preference object to represent a list preference that is attached to
+     * a category.
+     *
+     * @param context The activity context we operate under.
+     * @param parentCategory The PreferenceCategory this object exists within.
+     */
+    public CustomListPreference(Context context, CustomListCategory parentCategory) {
+        super(context);
+
+        mParentCategory = parentCategory;
+        setLayoutResource(getPreferenceLayoutResource());
+
+        setOnPreferenceClickListener(new OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                CustomListPreference sPref = (CustomListPreference) preference;
+                sPref.showDialog();
+                return true;
+            }
+        });
+
+        Resources res = getContext().getResources();
+
+        // Fetch these strings now, instead of every time we ever want to relabel a button.
+        LABEL_IS_DEFAULT = res.getString(R.string.pref_default);
+        LABEL_SET_AS_DEFAULT = res.getString(R.string.pref_dialog_set_default);
+
+        mDialogItems = getDialogStrings();
+    }
+
+    /**
+     * Returns the Android resource id for the layout.
+     */
+    protected abstract int getPreferenceLayoutResource();
+
+    /**
+     * Set whether this object's UI should display this as the default item. To ensure proper ordering,
+     * this method should only be called after this Preference is added to the PreferenceCategory.
+     * @param isDefault Flag indicating if this represents the default list item.
+     */
+    public void setIsDefault(boolean isDefault) {
+        mIsDefault = isDefault;
+        if (isDefault) {
+            setOrder(0);
+            setSummary(LABEL_IS_DEFAULT);
+        } else {
+            setOrder(1);
+            setSummary("");
+        }
+    }
+
+    /**
+     * Returns the strings to be displayed in the dialog.
+     */
+    abstract protected String[] getDialogStrings();
+
+    /**
+     * Display a dialog for this preference, when the preference is clicked.
+     */
+    public void showDialog() {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+        builder.setTitle(getTitle().toString());
+        builder.setItems(mDialogItems, new DialogInterface.OnClickListener() {
+            // Forward relevant events to the container class for handling.
+            @Override
+            public void onClick(DialogInterface dialog, int indexClicked) {
+                hideDialog();
+                onDialogIndexClicked(indexClicked);
+            }
+        });
+
+        configureDialogBuilder(builder);
+
+        // We have to construct the dialog itself on the UI thread.
+        mDialog = builder.create();
+        mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
+            // Called when the dialog is shown (so we're finally able to manipulate button enabledness).
+            @Override
+            public void onShow(DialogInterface dialog) {
+                configureShownDialog();
+            }
+        });
+        mDialog.show();
+    }
+
+    /**
+     * (Optional) Configure the AlertDialog builder.
+     */
+    protected void configureDialogBuilder(AlertDialog.Builder builder) {
+        return;
+    }
+
+    abstract protected void onDialogIndexClicked(int index);
+
+    /**
+     * Disables buttons in the shown AlertDialog as required. The button elements are not created
+     * until after show is called, so this method has to be called from the onShowListener above.
+     * @see this.showDialog
+     */
+    protected void configureShownDialog() {
+        // If this is already the default list item, disable the button for setting this as the default.
+        final TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON);
+        if (mIsDefault) {
+            defaultButton.setEnabled(false);
+
+            // Failure to unregister this listener leads to tapping the button dismissing the dialog
+            // without doing anything.
+            defaultButton.setOnClickListener(null);
+        }
+    }
+
+    /**
+     * Hide the dialog we previously created, if any.
+     */
+    public void hideDialog() {
+        if (mDialog != null && mDialog.isShowing()) {
+            mDialog.dismiss();
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        // Show the preference dialog on long-press.
+        showDialog();
+        return true;
+    }
+}
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -148,18 +148,18 @@ public class GeckoPreferences
         final ListView mListView = getListView();
         mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
             @Override
             public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                 // Call long-click handler if it the item implements it.
                 final ListAdapter listAdapter = ((ListView) parent).getAdapter();
                 final Object listItem = listAdapter.getItem(position);
 
-                // Only SearchEnginePreference handles long clicks.
-                if (listItem instanceof SearchEnginePreference && listItem instanceof View.OnLongClickListener) {
+                // Only CustomListPreference handles long clicks.
+                if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) {
                     final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem;
                     return longClickListener.onLongClick(view);
                 }
                 return false;
             }
         });
 
         if (Build.VERSION.SDK_INT >= 14)
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/preferences/PanelsPreference.java
@@ -0,0 +1,122 @@
+/* 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/. */
+
+package org.mozilla.gecko.preferences;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.mozilla.gecko.R;
+
+public class PanelsPreference extends CustomListPreference {
+    protected String LOGTAG = "PanelsPreference";
+
+    private static final int INDEX_SHOW_BUTTON = 1;
+    private static final int INDEX_REMOVE_BUTTON = 2;
+
+    private final String LABEL_HIDE;
+    private final String LABEL_SHOW;
+
+    protected boolean mIsHidden = false;
+
+    public PanelsPreference(Context context, CustomListCategory parentCategory) {
+        super(context, parentCategory);
+
+        Resources res = getContext().getResources();
+        LABEL_HIDE = res.getString(R.string.pref_panels_hide);
+        LABEL_SHOW = res.getString(R.string.pref_panels_show);
+    }
+
+    @Override
+    protected int getPreferenceLayoutResource() {
+        return R.layout.preference_panels;
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        // Override view handling so we can grey out "hidden" PanelPreferences.
+        view.setEnabled(!mIsHidden);
+
+        if (view instanceof ViewGroup) {
+            final ViewGroup group = (ViewGroup) view;
+            for (int i = 0; i < group.getChildCount(); i++) {
+                group.getChildAt(i).setEnabled(!mIsHidden);
+            }
+        }
+    }
+
+    @Override
+    protected String[] getDialogStrings() {
+        Resources res = getContext().getResources();
+        // XXX: Don't provide the "Remove" string for now, because we only support built-in
+        // panels, which can only be disabled.
+        return new String[] { LABEL_SET_AS_DEFAULT,
+                              LABEL_HIDE };
+    }
+
+    @Override
+    public void setIsDefault(boolean isDefault) {
+        mIsDefault = isDefault;
+        if (isDefault) {
+            setSummary(LABEL_IS_DEFAULT);
+            if (mIsHidden) {
+                // Unhide the panel if it's being set as the default.
+                setHidden(false);
+            }
+        } else {
+            setSummary("");
+        }
+    }
+
+    @Override
+    protected void onDialogIndexClicked(int index) {
+        switch(index) {
+            case INDEX_SET_DEFAULT_BUTTON:
+                mParentCategory.setDefault(this);
+                break;
+
+            case INDEX_SHOW_BUTTON:
+                ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
+                break;
+
+            case INDEX_REMOVE_BUTTON:
+                mParentCategory.uninstall(this);
+                break;
+
+            default:
+                Log.w(LOGTAG, "Selected index out of range: " + index);
+        }
+    }
+
+    @Override
+    protected void configureShownDialog() {
+        super.configureShownDialog();
+
+        // Handle Show/Hide buttons.
+        final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_SHOW_BUTTON);
+        hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
+    }
+
+    public void setHidden(boolean toHide) {
+        if (toHide) {
+            setIsDefault(false);
+        }
+
+        if (mIsHidden != toHide) {
+            mIsHidden = toHide;
+            notifyChanged();
+        }
+    }
+
+    public boolean isHidden() {
+        return mIsHidden;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/preferences/PanelsPreferenceCategory.java
@@ -0,0 +1,242 @@
+/* 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/. */
+
+package org.mozilla.gecko.preferences;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mozilla.gecko.home.HomeConfig;
+import org.mozilla.gecko.home.HomeConfig.PanelConfig;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
+
+public class PanelsPreferenceCategory extends CustomListCategory {
+    public static final String LOGTAG = "PanelsPrefCategory";
+
+    protected HomeConfig mHomeConfig;
+    protected final List<PanelConfig> mPanelConfigs = new ArrayList<PanelConfig>();
+
+    protected UiAsyncTask<Void, Void, List<PanelConfig>> mLoadTask;
+    protected UiAsyncTask<Void, Void, Void> mSaveTask;
+
+    public PanelsPreferenceCategory(Context context) {
+        super(context);
+        initConfig(context);
+    }
+
+    public PanelsPreferenceCategory(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initConfig(context);
+    }
+
+    public PanelsPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initConfig(context);
+    }
+
+    protected void initConfig(Context context) {
+        mHomeConfig = HomeConfig.getDefault(context);
+    }
+
+    @Override
+    public void onAttachedToActivity() {
+        super.onAttachedToActivity();
+
+        loadHomeConfig();
+    }
+
+    /**
+     * Load the Home Panels config and populate the preferences screen and maintain local state.
+     */
+    private void loadHomeConfig() {
+        mLoadTask = new UiAsyncTask<Void, Void, List<PanelConfig>>(ThreadUtils.getBackgroundHandler()) {
+            @Override
+            public List<PanelConfig> doInBackground(Void... params) {
+                return mHomeConfig.load();
+            }
+
+            @Override
+            public void onPostExecute(List<PanelConfig> panelConfigs) {
+                displayPanelConfig(panelConfigs);
+            }
+        };
+        mLoadTask.execute();
+    }
+
+    private void displayPanelConfig(List<PanelConfig> panelConfigs) {
+        for (PanelConfig panelConfig: panelConfigs) {
+            // Populate our local copy of the panels.
+            mPanelConfigs.add(panelConfig);
+
+            // Create and add the pref.
+            final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this);
+            pref.setTitle(panelConfig.getTitle());
+            pref.setKey(panelConfig.getId());
+            // XXX: Pull icon from PanelInfo.
+            addPreference(pref);
+
+            if (panelConfig.isDefault()) {
+                mDefaultReference = pref;
+                pref.setIsDefault(true);
+            }
+
+            if (panelConfig.isDisabled()) {
+                pref.setHidden(true);
+            }
+        }
+    }
+
+    /**
+     * Update HomeConfig off the main thread.
+     *
+     * @param panelConfigs Configuration to be saved
+     */
+    private void saveHomeConfig() {
+        final List<PanelConfig> panelConfigs = makeConfigListDeepCopy();
+        mSaveTask = new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) {
+            @Override
+            public Void doInBackground(Void... params) {
+                mHomeConfig.save(panelConfigs);
+                return null;
+            }
+        };
+        mSaveTask.execute();
+    }
+
+    private List<PanelConfig> makeConfigListDeepCopy() {
+        List<PanelConfig> copiedList = new ArrayList<PanelConfig>();
+        for (PanelConfig panelConfig : mPanelConfigs) {
+            copiedList.add(new PanelConfig(panelConfig));
+        }
+        return copiedList;
+    }
+
+    @Override
+    public void setDefault(CustomListPreference pref) {
+        super.setDefault(pref);
+        updateConfigDefault();
+        saveHomeConfig();
+    }
+
+    @Override
+    protected void onPrepareForRemoval() {
+        if (mLoadTask != null) {
+            mLoadTask.cancel(true);
+        }
+
+        if (mSaveTask != null) {
+            mSaveTask.cancel(true);
+        }
+     }
+
+    /**
+     * Update the local HomeConfig default state from mDefaultReference.
+     */
+    private void updateConfigDefault() {
+        String mId = null;
+        if (mDefaultReference != null) {
+            mId = mDefaultReference.getKey();
+        }
+
+        for (PanelConfig panelConfig : mPanelConfigs) {
+            if (TextUtils.equals(panelConfig.getId(), mId)) {
+                panelConfig.setIsDefault(true);
+                panelConfig.setIsDisabled(false);
+            } else {
+                panelConfig.setIsDefault(false);
+            }
+        }
+    }
+
+    @Override
+    public void uninstall(CustomListPreference pref) {
+        super.uninstall(pref);
+        // This could change the default, so update the local version of the config.
+        updateConfigDefault();
+
+        final String mId = pref.getKey();
+        PanelConfig toRemove = null;
+        for (PanelConfig panelConfig : mPanelConfigs) {
+            if (TextUtils.equals(panelConfig.getId(), mId)) {
+                toRemove = panelConfig;
+                break;
+            }
+        }
+        mPanelConfigs.remove(toRemove);
+
+        saveHomeConfig();
+    }
+
+    /**
+     * Update the hide/show state of the preference and save the HomeConfig
+     * changes.
+     *
+     * @param pref Preference to update
+     * @param toHide New hidden state of the preference
+     */
+    protected void setHidden(PanelsPreference pref, boolean toHide) {
+        pref.setHidden(toHide);
+        ensureDefaultForHide(pref, toHide);
+
+        final String mId = pref.getKey();
+        for (PanelConfig panelConfig : mPanelConfigs) {
+            if (TextUtils.equals(panelConfig.getId(), mId)) {
+                panelConfig.setIsDisabled(toHide);
+                break;
+            }
+        }
+
+        saveHomeConfig();
+    }
+
+    /**
+     * Ensure a default is set (if possible) for hiding/showing a pref.
+     * If hiding, try to find an enabled pref to set as the default.
+     * If showing, set it as the default if there is no default currently.
+     *
+     * This updates the local HomeConfig state.
+     *
+     * @param pref Preference getting updated
+     * @param toHide Boolean of the new hidden state
+     */
+    private void ensureDefaultForHide(PanelsPreference pref, boolean toHide) {
+        if (toHide) {
+            // Set a default if there is an enabled panel left.
+            if (pref == mDefaultReference) {
+                setFallbackDefault();
+                updateConfigDefault();
+            }
+        } else {
+            if (mDefaultReference == null) {
+                super.setDefault(pref);
+                updateConfigDefault();
+            }
+        }
+    }
+
+    /**
+     * When the default panel is removed or disabled, find an enabled panel
+     * if possible and set it as mDefaultReference.
+     */
+    @Override
+    protected void setFallbackDefault() {
+        for (int i = 0; i < getPreferenceCount(); i++) {
+            final PanelsPreference pref = (PanelsPreference) getPreference(i);
+            if (!pref.isHidden()) {
+                super.setDefault(pref);
+                return;
+            }
+        }
+        mDefaultReference = null;
+    }
+}
--- a/mobile/android/base/preferences/SearchEnginePreference.java
+++ b/mobile/android/base/preferences/SearchEnginePreference.java
@@ -1,25 +1,22 @@
 /* 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/. */
 
 package org.mozilla.gecko.preferences;
 
 import android.app.AlertDialog;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
-import android.preference.Preference;
 import android.text.SpannableString;
 import android.util.Log;
 import android.view.View;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import java.util.Iterator;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
@@ -27,99 +24,108 @@ import org.mozilla.gecko.favicons.decode
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.FaviconView;
 
 /**
  * Represents an element in the list of search engines on the preferences menu.
  */
-public class SearchEnginePreference extends Preference implements View.OnLongClickListener {
-    private static final String LOGTAG = "SearchEnginePreference";
-
-    // Indices in button array of the AlertDialog of the three buttons.
-    public static final int INDEX_SET_DEFAULT_BUTTON = 0;
-    public static final int INDEX_REMOVE_BUTTON = 1;
-
-    // Cache label to avoid repeated use of the resource system.
-    public final String LABEL_IS_DEFAULT;
+public class SearchEnginePreference extends CustomListPreference {
+    protected String LOGTAG = "SearchEnginePreference";
 
-    // Specifies if this engine is configured as the default search engine.
-    private boolean mIsDefaultEngine;
-
-    // Dialog element labels.
-    private String[] mDialogItems;
-
-    // The popup displayed when this element is tapped.
-    private AlertDialog mDialog;
-
-    private final SearchPreferenceCategory mParentCategory;
+    protected static final int INDEX_REMOVE_BUTTON = 1;
 
     // The icon to display in the prompt when clicked.
     private BitmapDrawable mPromptIcon;
+
     // The bitmap backing the drawable above - needed separately for the FaviconView.
     private Bitmap mIconBitmap;
 
     private FaviconView mFaviconView;
 
-    /**
-     * Create a preference object to represent a search engine that is attached to category
-     * containingCategory.
-     * @param context The activity context we operate under.
-     * @param parentCategory The PreferenceCategory this object exists within.
-     * @see this.setSearchEngine
-     */
     public SearchEnginePreference(Context context, SearchPreferenceCategory parentCategory) {
-        super(context);
-        mParentCategory = parentCategory;
-
-        Resources res = getContext().getResources();
-
-        // Set the layout resource for this preference - includes a FaviconView.
-        setLayoutResource(R.layout.preference_search_engine);
-
-        setOnPreferenceClickListener(new OnPreferenceClickListener() {
-            @Override
-            public boolean onPreferenceClick(Preference preference) {
-                SearchEnginePreference sPref = (SearchEnginePreference) preference;
-                sPref.showDialog();
-
-                return true;
-            }
-        });
-
-        // Fetch this resource now, instead of every time we ever want to relabel a button.
-        LABEL_IS_DEFAULT = res.getString(R.string.pref_search_default);
-
-        // Set up default dialog items.
-        mDialogItems = new String[] { res.getString(R.string.pref_search_set_default),
-                                      res.getString(R.string.pref_search_remove) };
+        super(context, parentCategory);
     }
 
     /**
      * Called by Android when we're bound to the custom view. Allows us to set the custom properties
      * of our custom view elements as we desire (We can now use findViewById on them).
      *
      * @param view The view instance for this Preference object.
      */
     @Override
     protected void onBindView(View view) {
         super.onBindView(view);
+
         // Set the icon in the FaviconView.
         mFaviconView = ((FaviconView) view.findViewById(R.id.search_engine_icon));
         mFaviconView.updateAndScaleImage(mIconBitmap, getTitle().toString());
     }
 
     @Override
-    public boolean onLongClick(View view) {
-        // Show the preference dialog on long-press.
-        showDialog();
-        return true;
+    protected int getPreferenceLayoutResource() {
+        return R.layout.preference_search_engine;
+    }
+
+    /**
+     * Returns the strings to be displayed in the dialog.
+     */
+    @Override
+    protected String[] getDialogStrings() {
+        Resources res = getContext().getResources();
+        return new String[] { LABEL_SET_AS_DEFAULT,
+                              res.getString(R.string.pref_dialog_remove) };
     }
 
+    @Override
+    public void showDialog() {
+        // If this is the last engine, then we are the default, and none of the options
+        // on this menu can do anything.
+        if (mParentCategory.getPreferenceCount() == 1) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    Toast.makeText(getContext(), R.string.pref_search_last_toast, Toast.LENGTH_SHORT).show();
+                }
+            });
+            return;
+        }
+
+        super.showDialog();
+    }
+
+    @Override
+    protected void configureDialogBuilder(AlertDialog.Builder builder) {
+        // Copy the icon from this object to the prompt we produce. We lazily create the drawable,
+        // as the user may not ever actually tap this object.
+        if (mPromptIcon == null && mIconBitmap != null) {
+            mPromptIcon = new BitmapDrawable(getContext().getResources(), mFaviconView.getBitmap());
+        }
+
+        builder.setIcon(mPromptIcon);
+    }
+
+    @Override
+    protected void onDialogIndexClicked(int index) {
+        switch (index) {
+            case INDEX_SET_DEFAULT_BUTTON:
+                mParentCategory.setDefault(this);
+                break;
+
+            case INDEX_REMOVE_BUTTON:
+                mParentCategory.uninstall(this);
+                break;
+
+            default:
+                Log.w(LOGTAG, "Selected index out of range.");
+                break;
+        }
+     }
+
     /**
      * Configure this Preference object from the Gecko search engine JSON object.
      * @param geckoEngineJSON The Gecko-formatted JSON object representing the search engine.
      * @throws JSONException If the JSONObject is invalid.
      */
     public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException {
         final String engineName = geckoEngineJSON.getString("name");
         final SpannableString titleSpannable = new SpannableString(engineName);
@@ -179,126 +185,9 @@ public class SearchEnginePreference exte
             }
 
         } catch (IllegalArgumentException e) {
             Log.e(LOGTAG, "IllegalArgumentException creating Bitmap. Most likely a zero-length bitmap.", e);
         } catch (NullPointerException e) {
             Log.e(LOGTAG, "NullPointerException creating Bitmap. Most likely a zero-length bitmap.", e);
         }
     }
-
-    /**
-     * Set if this object's UI should show that this is the default engine. To ensure proper ordering,
-     * this method should only be called after this Preference is added to the PreferenceCategory.
-     * @param isDefault Flag indicating if this represents the default engine.
-     */
-    public void setIsDefaultEngine(boolean isDefault) {
-        mIsDefaultEngine = isDefault;
-        if (isDefault) {
-            setOrder(0);
-            setSummary(LABEL_IS_DEFAULT);
-        } else {
-            setOrder(1);
-            setSummary("");
-        }
-    }
-
-    /**
-     * Display the AlertDialog providing options to reconfigure this search engine. Sets an event
-     * listener to disable buttons in the dialog as appropriate after they have been constructed by
-     * Android.
-     * @see this.configureShownDialog
-     * @see this.hideDialog
-     */
-    public void showDialog() {
-        // If we are the only engine left, then we are the default engine, and none of the options
-        // on this menu can do anything.
-        if (mParentCategory.getPreferenceCount() == 1) {
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    Toast.makeText(getContext(), R.string.pref_search_last_toast, Toast.LENGTH_SHORT).show();
-                }
-            });
-            return;
-        }
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-        builder.setTitle(getTitle().toString());
-        builder.setItems(mDialogItems, new DialogInterface.OnClickListener() {
-            // Forward the various events that we care about to the container class for handling.
-            @Override
-            public void onClick(DialogInterface dialog, int indexClicked) {
-                hideDialog();
-                switch (indexClicked) {
-                    case INDEX_SET_DEFAULT_BUTTON:
-                        mParentCategory.setDefault(SearchEnginePreference.this);
-                        break;
-                    case INDEX_REMOVE_BUTTON:
-                        mParentCategory.uninstall(SearchEnginePreference.this);
-                        break;
-                    default:
-                        Log.w(LOGTAG, "Selected index out of range.");
-                        break;
-                }
-            }
-        });
-
-        // Copy the icon from this object to the prompt we produce. We lazily create the drawable,
-        // as the user may not ever actually tap this object.
-        if (mPromptIcon == null && mIconBitmap != null) {
-            mPromptIcon = new BitmapDrawable(getContext().getResources(), mFaviconView.getBitmap());
-        }
-        builder.setIcon(mPromptIcon);
-
-        // Icons are hidden until Bug 926711 is fixed.
-        //builder.setIcon(mPromptIcon);
-
-        // We have to construct the dialog itself on the UI thread.
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mDialog = builder.create();
-                mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
-                    // Called when the dialog is shown (so we're finally able to manipulate button enabledness).
-                    @Override
-                    public void onShow(DialogInterface dialog) {
-                        configureShownDialog();
-                    }
-                });
-                mDialog.show();
-            }
-        });
-    }
-
-    /**
-     * Hide the dialog we previously created, if any.
-     */
-    public void hideDialog() {
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // Null check so we can chain engine-mutating methods up in SearchPreferenceCategory
-                // without consequence.
-                if (mDialog != null && mDialog.isShowing()) {
-                    mDialog.dismiss();
-                }
-            }
-        });
-    }
-
-    /**
-     * Disables buttons in the shown AlertDialog as required. The button elements are not created
-     * until after we call show, so this method has to be called from the onShowListener above.
-     * @see this.showDialog
-     */
-    private void configureShownDialog() {
-        // If we are the default engine, disable the "Set as default" button.
-        final TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON);
-        // Disable "Set as default" button if we are already the default.
-        if (mIsDefaultEngine) {
-            defaultButton.setEnabled(false);
-            // Failure to unregister this listener leads to tapping the button dismissing the dialog
-            // without doing anything.
-            defaultButton.setOnClickListener(null);
-        }
-    }
 }
--- a/mobile/android/base/preferences/SearchPreferenceCategory.java
+++ b/mobile/android/base/preferences/SearchPreferenceCategory.java
@@ -1,64 +1,71 @@
 /* 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/. */
 
 package org.mozilla.gecko.preferences;
 
 import android.content.Context;
 import android.preference.Preference;
-import android.preference.PreferenceCategory;
 import android.util.AttributeSet;
 import android.util.Log;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.util.GeckoEventListener;
 
-public class SearchPreferenceCategory extends PreferenceCategory implements GeckoEventListener {
+public class SearchPreferenceCategory extends CustomListCategory implements GeckoEventListener {
     public static final String LOGTAG = "SearchPrefCategory";
 
-    private SearchEnginePreference mDefaultEngineReference;
-
-    // These seemingly redundant constructors are mandated by the Android system, else it fails to
-    // inflate this object.
-
-    public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
+    public SearchPreferenceCategory(Context context) {
+        super(context);
     }
 
     public SearchPreferenceCategory(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public SearchPreferenceCategory(Context context) {
-        super(context);
+    public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
     }
 
     @Override
     protected void onAttachedToActivity() {
         super.onAttachedToActivity();
 
-        // Ensures default engine remains at top of list.
-        setOrderingAsAdded(true);
-
         // Register for SearchEngines messages and request list of search engines from Gecko.
         GeckoAppShell.registerEventListener("SearchEngines:Data", this);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
     }
 
     @Override
     protected void onPrepareForRemoval() {
         GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
     }
 
     @Override
+    public void setDefault(CustomListPreference item) {
+        super.setDefault(item);
+
+        sendGeckoEngineEvent("SearchEngines:SetDefault", item.getTitle().toString());
+    }
+
+    @Override
+    public void uninstall(CustomListPreference item) {
+        super.uninstall(item);
+
+        sendGeckoEngineEvent("SearchEngines:Remove", item.getTitle().toString());
+    }
+
+    @Override
     public void handleMessage(String event, final JSONObject data) {
         if (event.equals("SearchEngines:Data")) {
             // Parse engines array from JSON.
             JSONArray engines;
             try {
                 engines = data.getJSONArray("searchEngines");
             } catch (JSONException e) {
                 Log.e(LOGTAG, "Unable to decode search engine data from Gecko.", e);
@@ -88,73 +95,34 @@ public class SearchPreferenceCategory ex
 
                     addPreference(enginePreference);
 
                     // The first element in the array is the default engine.
                     if (i == 0) {
                         // We set this here, not in setSearchEngineFromJSON, because it allows us to
                         // keep a reference  to the default engine to use when the AlertDialog
                         // callbacks are used.
-                        enginePreference.setIsDefaultEngine(true);
-                        mDefaultEngineReference = enginePreference;
+                        enginePreference.setIsDefault(true);
+                        mDefaultReference = enginePreference;
                     }
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "JSONException parsing engine at index " + i, e);
                 }
             }
         }
     }
 
     /**
-     * Set the default engine to any available engine. Used if the current default is removed or
-     * disabled.
-     */
-    private void setFallbackDefaultEngine() {
-        if (getPreferenceCount() > 0) {
-            SearchEnginePreference aEngine = (SearchEnginePreference) getPreference(0);
-            setDefault(aEngine);
-        }
-    }
-
-    /**
      * Helper method to send a particular event string to Gecko with an associated engine name.
      * @param event The type of event to send.
      * @param engine The engine to which the event relates.
      */
-    private void sendGeckoEngineEvent(String event, SearchEnginePreference engine) {
+    private void sendGeckoEngineEvent(String event, String engineName) {
         JSONObject json = new JSONObject();
         try {
-            json.put("engine", engine.getTitle());
+            json.put("engine", engineName);
         } catch (JSONException e) {
             Log.e(LOGTAG, "JSONException creating search engine configuration change message for Gecko.", e);
             return;
         }
         GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent(event, json.toString()));
     }
-
-    // Methods called by tapping items on the submenus for each search engine are below.
-
-    /**
-     * Removes the given engine from the set of available engines.
-     * @param engine The engine to remove.
-     */
-    public void uninstall(SearchEnginePreference engine) {
-        removePreference(engine);
-        if (engine == mDefaultEngineReference) {
-            // If they're deleting their default engine, get them a new default engine.
-            setFallbackDefaultEngine();
-        }
-
-        sendGeckoEngineEvent("SearchEngines:Remove", engine);
-    }
-
-    /**
-     * Sets the given engine as the current default engine.
-     * @param engine The intended new default engine.
-     */
-    public void setDefault(SearchEnginePreference engine) {
-        engine.setIsDefaultEngine(true);
-        mDefaultEngineReference.setIsDefaultEngine(false);
-        mDefaultEngineReference = engine;
-
-        sendGeckoEngineEvent("SearchEngines:SetDefault", engine);
-    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/preference_panels.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="?android:attr/listPreferredItemHeight"
+              android:gravity="center_vertical"
+              android:orientation="vertical"
+              android:paddingLeft="15dp"
+              android:paddingRight="?android:attr/scrollbarSize">
+
+       <TextView android:id="@+android:id/title"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:textAppearance="?android:attr/textAppearanceMedium"
+                 android:singleLine="true"
+                 android:ellipsize="marquee"
+                 android:fadingEdge="horizontal" />
+
+       <TextView android:id="@+android:id/summary"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:textAppearance="?android:attr/textAppearanceSmall"
+                 android:maxLines="2" />
+
+</LinearLayout>
--- a/mobile/android/base/resources/xml-v11/preferences_customize.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize.xml
@@ -10,16 +10,23 @@
                   android:enabled="false">
 
      <PreferenceScreen android:title="@string/pref_category_search"
                        android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
          <extra android:name="resource"
                 android:value="preferences_search"/>
      </PreferenceScreen>
 
+
+    <PreferenceScreen android:title="@string/pref_category_home"
+                      android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
+            <extra android:name="resource"
+                   android:value="preferences_home" />
+    </PreferenceScreen>
+
     <org.mozilla.gecko.preferences.AndroidImportPreference
                   android:key="android.not_a_preference.import_android"
                   gecko:entries="@array/pref_import_android_entries"
                   gecko:entryKeys="@array/pref_import_android_keys"
                   gecko:initialValues="@array/pref_import_android_values"
                   android:title="@string/pref_import_android"
                   android:positiveButtonText="@string/bookmarkhistory_button_import"
                   android:negativeButtonText="@string/button_cancel"
--- a/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
@@ -17,16 +17,22 @@
                                                   android:persistent="false" />
 
     <PreferenceScreen android:title="@string/pref_category_search"
                       android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
         <extra android:name="resource"
                android:value="preferences_search"/>
     </PreferenceScreen>
 
+    <PreferenceScreen android:title="@string/pref_category_home"
+                      android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
+            <extra android:name="resource"
+                   android:value="preferences_home" />
+    </PreferenceScreen>
+
     <org.mozilla.gecko.preferences.AndroidImportPreference
             android:key="android.not_a_preference.import_android"
             gecko:entries="@array/pref_import_android_entries"
             gecko:entryKeys="@array/pref_import_android_keys"
             gecko:initialValues="@array/pref_import_android_values"
             android:title="@string/pref_import_android"
             android:positiveButtonText="@string/bookmarkhistory_button_import"
             android:negativeButtonText="@string/button_cancel"
--- a/mobile/android/base/resources/xml/preferences_customize.xml
+++ b/mobile/android/base/resources/xml/preferences_customize.xml
@@ -1,26 +1,37 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                   android:enabled="false">
+
     <PreferenceScreen android:title="@string/pref_category_search" >
         <intent android:action="android.intent.action.VIEW"
                 android:targetPackage="@string/android_package_name"
                 android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" >
             <extra
                 android:name="resource"
                 android:value="preferences_search" />
         </intent>
     </PreferenceScreen>
 
+    <PreferenceScreen android:title="@string/pref_category_home" >
+        <intent android:action="android.intent.action.VIEW"
+                android:targetPackage="@string/android_package_name"
+                android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" >
+            <extra
+                android:name="resource"
+                android:value="preferences_home" />
+        </intent>
+    </PreferenceScreen>
+
     <org.mozilla.gecko.preferences.AndroidImportPreference
                   android:key="android.not_a_preference.import_android"
                   gecko:entries="@array/pref_import_android_entries"
                   gecko:entryKeys="@array/pref_import_android_keys"
                   gecko:initialValues="@array/pref_import_android_values"
                   android:title="@string/pref_import_android"
                   android:positiveButtonText="@string/bookmarkhistory_button_import"
                   android:negativeButtonText="@string/button_cancel"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/xml/preferences_home.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:gecko="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/pref_category_home"
+                  android:enabled="false">
+
+    <org.mozilla.gecko.preferences.PanelsPreferenceCategory
+                android:title="@string/pref_category_home_panels"/>
+
+</PreferenceScreen>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -103,16 +103,19 @@
   <string name="pref_search_restore_defaults">&pref_search_restore_defaults;</string>
   <string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string>
   <string name="pref_search_tip">&pref_search_tip;</string>
 
   <string name="pref_category_devtools">&pref_category_devtools;</string>
   <string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
   <string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>
 
+  <string name="pref_category_home">&pref_category_home;</string>
+  <string name="pref_category_home_panels">&pref_category_home_panels;</string>
+
   <string name="pref_header_customize">&pref_header_customize;</string>
   <string name="pref_header_display">&pref_header_display;</string>
   <string name="pref_header_privacy_short">&pref_header_privacy_short;</string>
   <string name="pref_header_vendor">&pref_header_vendor;</string>
   <string name="pref_header_devtools">&pref_header_devtools;</string>
 
   <string name="pref_remember_signons">&pref_remember_signons;</string>
 
@@ -164,22 +167,25 @@
   <string name="pref_update_autodownload_disabled">&pref_update_autodownload_never;</string>
   <string name="pref_update_autodownload_enabled">&pref_update_autodownload_always;</string>
 
 
   <string name="pref_about_firefox">&pref_about_firefox;</string>
   <string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
   <string name="pref_vendor_feedback">&pref_vendor_feedback;</string>
 
-  <!-- Strings used in default search provider config preferences menu -->
-  <string name="pref_search_set_default">&pref_search_set_default;</string>
-  <string name="pref_search_default">&pref_search_default;</string>
-  <string name="pref_search_remove">&pref_search_remove;</string>
+  <string name="pref_dialog_set_default">&pref_dialog_set_default;</string>
+  <string name="pref_default">&pref_dialog_default;</string>
+  <string name="pref_dialog_remove">&pref_dialog_remove;</string>
+
   <string name="pref_search_last_toast">&pref_search_last_toast;</string>
 
+  <string name="pref_panels_show">&pref_panels_show;</string>
+  <string name="pref_panels_hide">&pref_panels_hide;</string>
+
   <string name="datareporting_notification_title">&datareporting_notification_title;</string>
   <string name="datareporting_notification_action_long">&datareporting_notification_action_long;</string>
   <string name="datareporting_notification_action">&datareporting_notification_action;</string>
   <string name="datareporting_notification_summary">&datareporting_notification_summary;</string>
   <string name="datareporting_notification_summary_short">&datareporting_notification_summary_short;</string>
   <string name="datareporting_notification_ticker_text">&datareporting_notification_ticker_text;</string>
 
   <string name="datareporting_telemetry_title">&datareporting_telemetry_title;</string>
--- a/mobile/android/base/sync/ExtendedJSONObject.java
+++ b/mobile/android/base/sync/ExtendedJSONObject.java
@@ -10,16 +10,17 @@ import java.io.StringReader;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
+import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
 
 /**
  * Extend JSONObject to do little things, like, y'know, accessing members.
  *
  * @author rnewman
  *
  */
@@ -358,9 +359,31 @@ public class ExtendedJSONObject {
       if (value == null) {
         throw new BadRequiredFieldJSONException("Expected key not present in result: " + k);
       }
       if (requiredFieldClass != null && !(requiredFieldClass.isInstance(value))) {
         throw new BadRequiredFieldJSONException("Value for key not an instance of " + requiredFieldClass + ": " + k);
       }
     }
   }
+
+  /**
+   * Return a base64-encoded string value as a byte array.
+   */
+  public byte[] getByteArrayBase64(String key) {
+    String s = (String) this.object.get(key);
+    if (s == null) {
+      return null;
+    }
+    return Base64.decodeBase64(s);
+  }
+
+  /**
+   * Return a hex-encoded string value as a byte array.
+   */
+  public byte[] getByteArrayHex(String key) {
+    String s = (String) this.object.get(key);
+    if (s == null) {
+      return null;
+    }
+    return Utils.hex2Byte(s);
+  }
 }
--- a/mobile/android/base/tests/testSettingsMenuItems.java
+++ b/mobile/android/base/tests/testSettingsMenuItems.java
@@ -27,16 +27,17 @@ public class testSettingsMenuItems exten
      *
      * These menu items are the ones that are always present - to test menu items that differ
      * based on build (e.g., release vs. nightly), add the items in <code>addConditionalSettings</code>. 
      */
 
     // Customize menu items.
     String[][] OPTIONS_CUSTOMIZE = {
         { "Search settings", "", "Show search suggestions", "Installed search engines"},
+        { "Home", "", "Panels" },
         { "Import from Android", "", "Bookmarks", "History", "Import" },
         { "Tabs", "Don't restore after quitting " + BRAND_NAME, "Always restore", "Don't restore after quitting " + BRAND_NAME },
     };
 
     // Display menu items.
     String[][] OPTIONS_DISPLAY = {
         { "Text size" },
         { "Title bar", "Show page title", "Show page title", "Show page address" },
--- a/mobile/android/chrome/content/aboutApps.js
+++ b/mobile/android/chrome/content/aboutApps.js
@@ -7,16 +7,18 @@
  * ***** END LICENSE BLOCK ***** */
 
 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm")
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 
+const DEFAULT_ICON = "chrome://browser/skin/images/default-app-icon.png";
+
 let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutApps.properties");
 
 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function()
   window.QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIWebNavigation)
     .QueryInterface(Ci.nsIDocShellTreeItem)
     .rootTreeItem
     .QueryInterface(Ci.nsIInterfaceRequestor)
@@ -34,16 +36,17 @@ function openLink(aEvent) {
   try {
     let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
     let url = formatter.formatURLPref(aEvent.currentTarget.getAttribute("pref"));
     let BrowserApp = gChromeWin.BrowserApp;
     BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id });
   } catch (ex) {}
 }
 
+#ifndef MOZ_ANDROID_SYNTHAPKS
 var ContextMenus = {
   target: null,
 
   init: function() {
     document.addEventListener("contextmenu", this, false);
     document.getElementById("addToHomescreenLabel").addEventListener("click", this.addToHomescreen, false);
     document.getElementById("uninstallLabel").addEventListener("click", this.uninstall, false);
   },
@@ -53,18 +56,17 @@ var ContextMenus = {
     this.target = event.target;
     while (!this.target.hasAttribute("contextmenu")) {
       this.target = this.target.parentNode;
     }
   },
 
   addToHomescreen: function() {
     let manifest = this.target.manifest;
-    let origin = Services.io.newURI(this.target.app.origin, null, null);
-    gChromeWin.WebappsUI.createShortcut(manifest.name, manifest.fullLaunchPath(), gChromeWin.WebappsUI.getBiggestIcon(manifest.icons, origin), "webapp");
+    gChromeWin.WebappsUI.createShortcut(manifest.name, manifest.fullLaunchPath(), manifest.biggestIconURL || DEFAULT_ICON, "webapp");
     this.target = null;
   },
 
   uninstall: function() {
     navigator.mozApps.mgmt.uninstall(this.target.app);
 
     let manifest = this.target.manifest;
     gChromeWin.sendMessageToJava({
@@ -72,35 +74,31 @@ var ContextMenus = {
       title: manifest.name,
       url: manifest.fullLaunchPath(),
       origin: this.target.app.origin,
       shortcutType: "webapp"
     });
     this.target = null;
   }
 }
+#endif
 
 function onLoad(aEvent) {
-  try {
-    let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
-    let link = document.getElementById("marketplaceURL");
-    let url = formatter.formatURLPref(link.getAttribute("pref"));
-    link.setAttribute("href", url);
-  } catch (e) {}
-
   let elmts = document.querySelectorAll("[pref]");
   for (let i = 0; i < elmts.length; i++) {
     elmts[i].addEventListener("click",  openLink,  false);
   }
 
   navigator.mozApps.mgmt.oninstall = onInstall;
   navigator.mozApps.mgmt.onuninstall = onUninstall;
   updateList();
 
+#ifndef MOZ_ANDROID_SYNTHAPKS
   ContextMenus.init();
+#endif
 }
 
 function updateList() {
   let grid = document.getElementById("appgrid");
   while (grid.lastChild) {
     grid.removeChild(grid.lastChild);
   }
 
@@ -114,26 +112,31 @@ function updateList() {
 }
 
 function addApplication(aApp) {
   let list = document.getElementById("appgrid");
   let manifest = new ManifestHelper(aApp.manifest, aApp.origin);
 
   let container = document.createElement("div");
   container.className = "app list-item";
+#ifndef MOZ_ANDROID_SYNTHAPKS
   container.setAttribute("contextmenu", "appmenu");
+#endif
   container.setAttribute("id", "app-" + aApp.origin);
   container.setAttribute("mozApp", aApp.origin);
   container.setAttribute("title", manifest.name);
 
   let img = document.createElement("img");
-  let origin = Services.io.newURI(aApp.origin, null, null);
-  img.src = gChromeWin.WebappsUI.getBiggestIcon(manifest.icons, origin);
+  img.src = manifest.biggestIconURL || DEFAULT_ICON;
   img.onerror = function() {
-      img.src = "chrome://browser/skin/images/default-app-icon.png";
+    // If the image failed to load, and it was not our default icon, attempt to
+    // use our default as a fallback.
+    if (img.src != DEFAULT_ICON) {
+      img.src = DEFAULT_ICON;
+    }
   }
   img.setAttribute("title", manifest.name);
 
   let title = document.createElement("div");
   title.appendChild(document.createTextNode(manifest.name));
 
   container.appendChild(img);
   container.appendChild(title);
--- a/mobile/android/chrome/content/aboutApps.xhtml
+++ b/mobile/android/chrome/content/aboutApps.xhtml
@@ -24,21 +24,23 @@
     <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
     <link rel="stylesheet" type="text/css" href="chrome://browser/skin/aboutBase.css" media="all" />
     <link rel="stylesheet" type="text/css" href="chrome://browser/skin/aboutApps.css" media="all" />
     <script type="text/javascript;version=1.8" src="chrome://browser/content/aboutApps.js"></script>
   </head>
 
   <body dir="&locale.dir;">
 
+#ifndef MOZ_ANDROID_SYNTHAPKS
     <menu type="context" id="appmenu">
       <menuitem id="addToHomescreenLabel" label="&aboutApps.addToHomescreen;"></menuitem>
       <menuitem id="uninstallLabel" label="&aboutApps.uninstall;"></menuitem>
     </menu>
-  
+#endif
+
     <div class="header">
       <div>&aboutApps.header;</div>
       <div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL"/>
     </div>
 
     <div id="main-container" class="hidden">
       <div>
         <div class="spacer" id="spacer1"> </div>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -413,62 +413,65 @@ var BrowserApp = {
       if (window.arguments[2])
         gScreenHeight = window.arguments[2];
       if (window.arguments[3])
         pinned = window.arguments[3];
       if (window.arguments[4])
         this.isGuest = window.arguments[4];
     }
 
-    let status = this.startupStatus();
     if (pinned) {
-      this._initRuntime(status, url, aUrl => this.addTab(aUrl));
+      this._initRuntime(this._startupStatus, url, aUrl => this.addTab(aUrl));
     } else {
       SearchEngines.init();
       this.initContextMenu();
     }
     // The order that context menu items are added is important
     // Make sure the "Open in App" context menu item appears at the bottom of the list
     ExternalApps.init();
 
     // XXX maybe we don't do this if the launch was kicked off from external
     Services.io.offline = false;
 
     // Broadcast a UIReady message so add-ons know we are finished with startup
     let event = document.createEvent("Events");
     event.initEvent("UIReady", true, false);
     window.dispatchEvent(event);
 
-    if (status)
+    if (this._startupStatus)
       this.onAppUpdated();
 
     // Store the low-precision buffer pref
     this.gUseLowPrecision = Services.prefs.getBoolPref("layers.low-precision-buffer");
 
     // notify java that gecko has loaded
     sendMessageToJava({ type: "Gecko:Ready" });
 
 #ifdef MOZ_SAFE_BROWSING
     // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
     setTimeout(function() { SafeBrowsing.init(); }, 5000);
 #endif
   },
 
-  startupStatus: function() {
-    let savedmstone = null;
+  get _startupStatus() {
+    delete this._startupStatus;
+
+    let savedMilestone = null;
     try {
-      savedmstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
+      savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
     } catch (e) {
     }
-#expand    let ourmstone = "__MOZ_APP_VERSION__";
-    if (ourmstone != savedmstone) {
-      Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourmstone);
-      return savedmstone ? "upgrade" : "new";
-    }
-    return "";
+#expand    let ourMilestone = "__MOZ_APP_VERSION__";
+    this._startupStatus = "";
+    if (ourMilestone != savedMilestone) {
+      Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourMilestone);
+      this._startupStatus = savedMilestone ? "upgrade" : "new";
+    }
+
+    return this._startupStatus;
   },
 
   /**
    * Pass this a locale string, such as "fr" or "es_ES".
    */
   setLocale: function (locale) {
     console.log("browser.js: requesting locale set: " + locale);
     sendMessageToJava({ type: "Locale:Set", locale: locale });
@@ -911,18 +914,18 @@ var BrowserApp = {
       type: "Tab:Close",
       tabID: aTab.id
     };
     sendMessageToJava(message);
   },
 
 #ifdef MOZ_ANDROID_SYNTHAPKS
   _loadWebapp: function(aMessage) {
-    // TODO: figure out when (if ever) to pass "new" to the status parameter.
-    this._initRuntime("", aMessage.url, aUrl => {
+
+    this._initRuntime(this._startupStatus, aMessage.url, aUrl => {
       this.manifestUrl = aMessage.url;
       this.addTab(aUrl, { title: aMessage.name });
     });
   },
 #endif
 
   // Calling this will update the state in BrowserApp after a tab has been
   // closed in the Java UI.
@@ -7088,16 +7091,17 @@ var WebappsUI = {
 
   uninit: function unint() {
     Services.obs.removeObserver(this, "webapps-ask-install");
     Services.obs.removeObserver(this, "webapps-launch");
     Services.obs.removeObserver(this, "webapps-uninstall");
     Services.obs.removeObserver(this, "webapps-install-error");
   },
 
+  DEFAULT_ICON: "chrome://browser/skin/images/default-app-icon.png",
   DEFAULT_PREFS_FILENAME: "default-prefs.js",
 
   observe: function observe(aSubject, aTopic, aData) {
     let data = {};
     try {
       data = JSON.parse(aData);
       data.mm = aSubject;
     } catch(ex) { }
@@ -7129,49 +7133,16 @@ var WebappsUI = {
         sendMessageToJava({
           type: "WebApps:Uninstall",
           origin: data.origin
         });
         break;
     }
   },
 
-  getBiggestIcon: function getBiggestIcon(aIcons, aOrigin) {
-    const DEFAULT_ICON = "chrome://browser/skin/images/default-app-icon.png";
-    if (!aIcons)
-      return DEFAULT_ICON;
-  
-    let iconSizes = Object.keys(aIcons);
-    if (iconSizes.length == 0)
-      return DEFAULT_ICON;
-    iconSizes.sort(function(a, b) a - b);
-
-    let biggestIcon = aIcons[iconSizes.pop()];
-    let iconURI = null;
-    try {
-      iconURI = Services.io.newURI(biggestIcon, null, null);
-      if (iconURI.scheme == "data") {
-        return iconURI.spec;
-      }
-    } catch (ex) {
-      // we don't have a biggestIcon or its not a valid url
-    }
-
-    // if we have an origin, try to resolve biggestIcon as a relative url
-    if (!iconURI && aOrigin) {
-      try {
-        iconURI = Services.io.newURI(aOrigin.resolve(biggestIcon), null, null);
-      } catch (ex) {
-        console.log("Could not resolve url: " + aOrigin.spec + " " + biggestIcon + " - " + ex);
-      }
-    }
-
-    return iconURI ? iconURI.spec : DEFAULT_ICON;
-  },
-
   doInstall: function doInstall(aData) {
     let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
     let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
     let showPrompt = true;
 
     if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) {
       // Get a profile for the app to be installed in. We'll download everything before creating the icons.
       let origin = aData.app.origin;
@@ -7187,17 +7158,17 @@ var WebappsUI = {
 
         let self = this;
         DOMApplicationRegistry.confirmInstall(aData, file,
           function (aManifest) {
             let localeManifest = new ManifestHelper(aManifest, aData.app.origin);
 
             // the manifest argument is the manifest from within the zip file,
             // TODO so now would be a good time to ask about permissions.
-            self.makeBase64Icon(self.getBiggestIcon(manifest.icons, Services.io.newURI(aData.app.origin, null, null)),
+            self.makeBase64Icon(localeManifest.biggestIconURL || this.DEFAULT_ICON,
               function(scaledIcon, fullsizeIcon) {
                 // if java returned a profile path to us, try to use it to pre-populate the app cache
                 // also save the icon so that it can be used in the splash screen
                 try {
                   let iconFile = file.clone();
                   iconFile.append("logo.png");
                   let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist);
                   persist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
@@ -7317,19 +7288,18 @@ var WebappsUI = {
       canvas = null;
       aCallbackFunction.call(null, scaledIcon, fullsizeIcon);
     };
     favicon.onerror = function() {
       Cu.reportError("CreateShortcut: favicon image load error");
 
       // if the image failed to load, and it was not our default icon, attempt to
       // use our default as a fallback
-      let uri = Services.io.newURI(favicon.src, null, null);
-      if (!/^chrome$/.test(uri.scheme)) {
-        favicon.src = WebappsUI.getBiggestIcon(null);
+      if (favicon.src != WebappsUI.DEFAULT_ICON) {
+        favicon.src = WebappsUI.DEFAULT_ICON;
       }
     };
   
     favicon.src = aIconURL;
   },
 
   createShortcut: function createShortcut(aTitle, aURL, aIconURL, aType) {
     this.makeBase64Icon(aIconURL, function _createShortcut(icon) {
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -21,18 +21,18 @@ chrome.jar:
   content/aboutPrivateBrowsing.js      (content/aboutPrivateBrowsing.js)
   content/aboutReader.html             (content/aboutReader.html)
   content/aboutReader.js               (content/aboutReader.js)
   content/Readability.js               (content/Readability.js)
   content/JSDOMParser.js               (content/JSDOMParser.js)
   content/readerWorker.js              (content/readerWorker.js)
   content/aboutHome.xhtml              (content/aboutHome.xhtml)
   content/aboutRights.xhtml            (content/aboutRights.xhtml)
-  content/aboutApps.xhtml              (content/aboutApps.xhtml)
-  content/aboutApps.js                 (content/aboutApps.js)
+* content/aboutApps.xhtml              (content/aboutApps.xhtml)
+* content/aboutApps.js                 (content/aboutApps.js)
   content/blockedSite.xhtml            (content/blockedSite.xhtml)
   content/languages.properties         (content/languages.properties)
   content/browser.xul                  (content/browser.xul)
 * content/browser.js                   (content/browser.js)
   content/bindings/checkbox.xml        (content/bindings/checkbox.xml)
   content/bindings/settings.xml        (content/bindings/settings.xml)
   content/exceptions.js                (content/exceptions.js)
   content/downloads.js                 (content/downloads.js)
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -381,16 +381,18 @@
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
 @BINPATH@/components/nsINIProcessor.manifest
 @BINPATH@/components/nsINIProcessor.js
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
+@BINPATH@/components/TelemetryStartup.js
+@BINPATH@/components/TelemetryStartup.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1128,21 +1128,24 @@ NS_IMETHODIMP CacheEntry::AsyncDoom(nsIC
   {
     mozilla::MutexAutoLock lock(mLock);
 
     if (mIsDoomed || mDoomCallback)
       return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
 
     mIsDoomed = true;
     mDoomCallback = aCallback;
-    BackgroundOp(Ops::DOOM);
   }
 
-  // Immediately remove the entry from the storage hash table
-  CacheStorageService::Self()->RemoveEntry(this);
+  // This immediately removes the entry from the master hashtable and also
+  // immediately dooms the file.  This way we make sure that any consumer
+  // after this point asking for the same entry won't get
+  //   a) this entry
+  //   b) a new entry with the same file
+  PurgeAndDoom();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
 {
   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
 
@@ -1398,56 +1401,63 @@ bool CacheEntry::Purge(uint32_t aWhat)
   LOG(("  ?"));
   return false;
 }
 
 void CacheEntry::PurgeAndDoom()
 {
   LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
 
-  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
-
   CacheStorageService::Self()->RemoveEntry(this);
   DoomAlreadyRemoved();
 }
 
 void CacheEntry::DoomAlreadyRemoved()
 {
   LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
 
+  mozilla::MutexAutoLock lock(mLock);
+
   mIsDoomed = true;
 
-  if (!CacheStorageService::IsOnManagementThread()) {
-    mozilla::MutexAutoLock lock(mLock);
-
-    BackgroundOp(Ops::DOOM);
-    return;
-  }
-
-  CacheStorageService::Self()->UnregisterEntry(this);
+  // This schedules dooming of the file, dooming is ensured to happen
+  // sooner than demand to open the same file made after this point
+  // so that we don't get this file for any newer opened entry(s).
+  DoomFile();
 
-  {
-    mozilla::MutexAutoLock lock(mLock);
+  // Must force post here since may be indirectly called from
+  // InvokeCallbacks of this entry and we don't want reentrancy here.
+  BackgroundOp(Ops::CALLBACKS, true);
+  // Process immediately when on the management thread.
+  BackgroundOp(Ops::UNREGISTER);
+}
 
-    if (mCallbacks.Length()) {
-      // Must force post here since may be indirectly called from
-      // InvokeCallbacks of this entry and we don't want reentrancy here.
-      BackgroundOp(Ops::CALLBACKS, true);
-    }
-  }
+void CacheEntry::DoomFile()
+{
+  nsresult rv = NS_ERROR_NOT_AVAILABLE;
 
   if (NS_SUCCEEDED(mFileStatus)) {
-    nsresult rv = mFile->Doom(mDoomCallback ? this : nullptr);
+    // Always calls the callback asynchronously.
+    rv = mFile->Doom(mDoomCallback ? this : nullptr);
     if (NS_SUCCEEDED(rv)) {
       LOG(("  file doomed"));
       return;
     }
+    
+    if (NS_ERROR_FILE_NOT_FOUND == rv) {
+      // File is set to be just memory-only, notify the callbacks
+      // and pretend dooming has succeeded.  From point of view of
+      // the entry it actually did - the data is gone and cannot be
+      // reused.
+      rv = NS_OK;
+    }
   }
 
-  OnFileDoomed(NS_OK);
+  // Always posts to the main thread.
+  OnFileDoomed(rv);
 }
 
 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
 {
   mLock.AssertCurrentThreadOwns();
 
   if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
     if (mBackgroundOperations.Set(aOperations))
@@ -1491,20 +1501,20 @@ void CacheEntry::BackgroundOp(uint32_t a
   }
 
   if (aOperations & Ops::REGISTER) {
     LOG(("CacheEntry REGISTER [this=%p]", this));
 
     CacheStorageService::Self()->RegisterEntry(this);
   }
 
-  if (aOperations & Ops::DOOM) {
-    LOG(("CacheEntry DOOM [this=%p]", this));
+  if (aOperations & Ops::UNREGISTER) {
+    LOG(("CacheEntry UNREGISTER [this=%p]", this));
 
-    DoomAlreadyRemoved();
+    CacheStorageService::Self()->UnregisterEntry(this);
   }
 
   if (aOperations & Ops::CALLBACKS) {
     LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
 
     mozilla::MutexAutoLock lock(mLock);
     InvokeCallbacks();
   }
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -216,16 +216,19 @@ private:
   void OnOutputClosed();
 
   // Schedules a background operation on the management thread.
   // When executed on the management thread directly, the operation(s)
   // is (are) executed immediately.
   void BackgroundOp(uint32_t aOperation, bool aForceAsync = false);
   void StoreFrecency();
 
+  // Called only from DoomAlreadyRemoved()
+  void DoomFile();
+
   already_AddRefed<CacheEntryHandle> ReopenTruncated(nsICacheEntryOpenCallback* aCallback);
   void TransferCallbacks(CacheEntry & aFromEntry);
 
   mozilla::Mutex mLock;
 
   // Reflects the number of existing handles for this entry
   friend class CacheEntryHandle;
   ::mozilla::ThreadSafeAutoRefCnt mHandlersCount;
@@ -300,18 +303,18 @@ private:
   CacheEntryHandle* mWriter;
 
   // Background thread scheduled operation.  Set (under the lock) one
   // of this flags to tell the background thread what to do.
   class Ops {
   public:
     static uint32_t const REGISTER =          1 << 0;
     static uint32_t const FRECENCYUPDATE =    1 << 1;
-    static uint32_t const DOOM =              1 << 2;
-    static uint32_t const CALLBACKS =         1 << 3;
+    static uint32_t const CALLBACKS =         1 << 2;
+    static uint32_t const UNREGISTER =        1 << 3;
 
     Ops() : mFlags(0) { }
     uint32_t Grab() { uint32_t flags = mFlags; mFlags = 0; return flags; }
     bool Set(uint32_t aFlags) { if (mFlags & aFlags) return false; mFlags |= aFlags; return true; }
   private:
     uint32_t mFlags;
   } mBackgroundOperations;
 
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -3,28 +3,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CacheLog.h"
 #include "CacheFile.h"
 
 #include "CacheFileChunk.h"
 #include "CacheFileInputStream.h"
 #include "CacheFileOutputStream.h"
-#include "nsITimer.h"
 #include "nsThreadUtils.h"
 #include "mozilla/DebugOnly.h"
 #include <algorithm>
 #include "nsComponentManagerUtils.h"
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace net {
 
-#define kMetadataWriteDelay        5000
-
 class NotifyCacheFileListenerEvent : public nsRunnable {
 public:
   NotifyCacheFileListenerEvent(CacheFileListener *aCallback,
                                nsresult aResult,
                                bool aIsNew)
     : mCallback(aCallback)
     , mRV(aResult)
     , mIsNew(aIsNew)
@@ -88,139 +85,16 @@ public:
 
 protected:
   nsCOMPtr<CacheFileChunkListener> mCallback;
   nsresult                         mRV;
   uint32_t                         mChunkIdx;
   nsRefPtr<CacheFileChunk>         mChunk;
 };
 
-class MetadataWriteTimer : public nsITimerCallback
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSITIMERCALLBACK
-
-  MetadataWriteTimer(CacheFile *aFile);
-  nsresult Fire();
-  nsresult Cancel();
-  bool     ShouldFireNew();
-
-protected:
-  virtual ~MetadataWriteTimer();
-
-  nsCOMPtr<nsIWeakReference> mFile;
-  nsCOMPtr<nsITimer>         mTimer;
-  nsCOMPtr<nsIEventTarget>   mTarget;
-  PRIntervalTime             mFireTime;
-};
-
-NS_IMPL_ADDREF(MetadataWriteTimer)
-NS_IMPL_RELEASE(MetadataWriteTimer)
-
-NS_INTERFACE_MAP_BEGIN(MetadataWriteTimer)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
-  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
-NS_INTERFACE_MAP_END_THREADSAFE
-
-MetadataWriteTimer::MetadataWriteTimer(CacheFile *aFile)
-  : mFireTime(0)
-{
-  LOG(("MetadataWriteTimer::MetadataWriteTimer() [this=%p, file=%p]",
-       this, aFile));
-  MOZ_COUNT_CTOR(MetadataWriteTimer);
-
-  mFile = do_GetWeakReference(static_cast<CacheFileChunkListener *>(aFile));
-  mTarget = NS_GetCurrentThread();
-}
-
-MetadataWriteTimer::~MetadataWriteTimer()
-{
-  LOG(("MetadataWriteTimer::~MetadataWriteTimer() [this=%p]", this));
-  MOZ_COUNT_DTOR(MetadataWriteTimer);
-
-  NS_ProxyRelease(mTarget, mTimer.forget().get());
-  NS_ProxyRelease(mTarget, mFile.forget().get());
-}
-
-NS_IMETHODIMP
-MetadataWriteTimer::Notify(nsITimer *aTimer)
-{
-  LOG(("MetadataWriteTimer::Notify() [this=%p, timer=%p]", this, aTimer));
-
-  MOZ_ASSERT(aTimer == mTimer);
-
-  nsCOMPtr<nsISupports> supp = do_QueryReferent(mFile);
-  if (!supp)
-    return NS_OK;
-
-  CacheFile *file = static_cast<CacheFile *>(
-                      static_cast<CacheFileChunkListener *>(supp.get()));
-
-  CacheFileAutoLock lock(file);
-
-  if (file->mTimer != this)
-    return NS_OK;
-
-  if (file->mMemoryOnly)
-    return NS_OK;
-
-  file->WriteMetadataIfNeeded();
-  file->mTimer = nullptr;
-
-  return NS_OK;
-}
-
-nsresult
-MetadataWriteTimer::Fire()
-{
-  LOG(("MetadataWriteTimer::Fire() [this=%p]", this));
-
-  nsresult rv;
-
-#ifdef DEBUG
-  bool onCurrentThread = false;
-  rv = mTarget->IsOnCurrentThread(&onCurrentThread);
-  MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrentThread);
-#endif
-
-  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = mTimer->InitWithCallback(this, kMetadataWriteDelay,
-                                nsITimer::TYPE_ONE_SHOT);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mFireTime = PR_IntervalNow();
-
-  return NS_OK;
-}
-
-nsresult
-MetadataWriteTimer::Cancel()
-{
-  LOG(("MetadataWriteTimer::Cancel() [this=%p]", this));
-  return mTimer->Cancel();
-}
-
-bool
-MetadataWriteTimer::ShouldFireNew()
-{
-  uint32_t delta = PR_IntervalToMilliseconds(PR_IntervalNow() - mFireTime);
-
-  if (delta > kMetadataWriteDelay / 2) {
-    LOG(("MetadataWriteTimer::ShouldFireNew() - returning true [this=%p]",
-         this));
-    return true;
-  }
-
-  LOG(("MetadataWriteTimer::ShouldFireNew() - returning false [this=%p]",
-       this));
-  return false;
-}
 
 class DoomFileHelper : public CacheFileIOListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   DoomFileHelper(CacheFileListener *aListener)
     : mListener(aListener)
@@ -268,110 +142,22 @@ private:
   }
 
   nsCOMPtr<CacheFileListener>  mListener;
 };
 
 NS_IMPL_ISUPPORTS1(DoomFileHelper, CacheFileIOListener)
 
 
-// We try to write metadata when the last reference to CacheFile is released.
-// We call WriteMetadataIfNeeded() under the lock from CacheFile::Release() and
-// if writing fails synchronously the listener is released and lock in
-// CacheFile::Release() is re-entered. This helper class ensures that the
-// listener is released outside the lock in case of synchronous failure.
-class MetadataListenerHelper : public CacheFileMetadataListener
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  MetadataListenerHelper(CacheFile *aFile)
-    : mFile(aFile)
-  {
-    MOZ_COUNT_CTOR(MetadataListenerHelper);
-    mListener = static_cast<CacheFileMetadataListener *>(aFile);
-  }
-
-  NS_IMETHOD OnMetadataRead(nsresult aResult)
-  {
-    MOZ_CRASH("MetadataListenerHelper::OnMetadataRead should not be called!");
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  NS_IMETHOD OnMetadataWritten(nsresult aResult)
-  {
-    mFile = nullptr;
-    return mListener->OnMetadataWritten(aResult);
-  }
-
-private:
-  virtual ~MetadataListenerHelper()
-  {
-    MOZ_COUNT_DTOR(MetadataListenerHelper);
-    if (mFile) {
-      mFile->ReleaseOutsideLock(mListener.forget().get());
-    }
-  }
-
-  CacheFile*                           mFile;
-  nsCOMPtr<CacheFileMetadataListener>  mListener;
-};
-
-NS_IMPL_ISUPPORTS1(MetadataListenerHelper, CacheFileMetadataListener)
-
-
 NS_IMPL_ADDREF(CacheFile)
-NS_IMETHODIMP_(nsrefcnt)
-CacheFile::Release()
-{
-  NS_PRECONDITION(0 != mRefCnt, "dup release");
-  nsrefcnt count = --mRefCnt;
-  NS_LOG_RELEASE(this, count, "CacheFile");
-
-  MOZ_ASSERT(count != 0, "Unexpected");
-
-  if (count == 1) {
-    bool deleteFile = false;
-
-    // Not using CacheFileAutoLock since it hard-refers the file
-    // and in it's destructor reenters this method.
-    Lock();
-
-    if (mMemoryOnly) {
-      deleteFile = true;
-    }
-    else if (mMetadata) {
-      WriteMetadataIfNeeded();
-      if (mWritingMetadata) {
-        MOZ_ASSERT(mRefCnt > 1);
-      } else {
-        if (mRefCnt == 1)
-          deleteFile = true;
-      }
-    }
-
-    Unlock();
-
-    if (!deleteFile) {
-      return count;
-    }
-
-    NS_LOG_RELEASE(this, 0, "CacheFile");
-    delete (this);
-    return 0;
-  }
-
-  return count;
-}
-
+NS_IMPL_RELEASE(CacheFile)
 NS_INTERFACE_MAP_BEGIN(CacheFile)
   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
   NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,
                                    mozilla::net::CacheFileChunkListener)
 NS_INTERFACE_MAP_END_THREADSAFE
 
 CacheFile::CacheFile()
   : mLock("CacheFile.mLock")
   , mOpeningFile(false)
   , mReady(false)
@@ -379,26 +165,25 @@ CacheFile::CacheFile()
   , mDataAccessed(false)
   , mDataIsDirty(false)
   , mWritingMetadata(false)
   , mStatus(NS_OK)
   , mDataSize(-1)
   , mOutput(nullptr)
 {
   LOG(("CacheFile::CacheFile() [this=%p]", this));
-
-  NS_ADDREF(this);
-  MOZ_COUNT_CTOR(CacheFile);
 }
 
 CacheFile::~CacheFile()
 {
   LOG(("CacheFile::~CacheFile() [this=%p]", this));
 
-  MOZ_COUNT_DTOR(CacheFile);
+  MutexAutoLock lock(mLock);
+  if (!mMemoryOnly)
+    WriteMetadataIfNeededLocked(true);
 }
 
 nsresult
 CacheFile::Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
                 bool aPriority,
                 bool aKeyIsHash,
@@ -556,17 +341,17 @@ CacheFile::OnChunkWritten(nsresult aResu
   LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, chunk=%p]",
        this, aChunk));
 
   aChunk->mRemovingChunk = true;
   ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
                        aChunk->mFile.forget().get()));
   mCachedChunks.Put(aChunk->Index(), aChunk);
   mChunks.Remove(aChunk->Index());
-  WriteMetadataIfNeeded();
+  WriteMetadataIfNeededLocked();
 
   return NS_OK;
 }
 
 nsresult
 CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
                             CacheFileChunk *aChunk)
 {
@@ -779,17 +564,17 @@ CacheFile::OnMetadataWritten(nsresult aR
   if (NS_FAILED(aResult)) {
     // TODO close streams with an error ???
   }
 
   if (mOutput || mInputs.Length() || mChunks.Count())
     return NS_OK;
 
   if (IsDirty())
-    WriteMetadataIfNeeded();
+    WriteMetadataIfNeededLocked();
 
   if (!mWritingMetadata) {
     LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
          this));
     CacheFileIOManager::ReleaseNSPRHandle(mHandle);
   }
 
   return NS_OK;
@@ -912,21 +697,20 @@ nsresult
 CacheFile::Doom(CacheFileListener *aCallback)
 {
   CacheFileAutoLock lock(this);
 
   MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
 
   LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback));
 
-  nsresult rv;
+  nsresult rv = NS_OK;
 
   if (mMemoryOnly) {
-    // TODO what exactly to do here?
-    return NS_ERROR_NOT_AVAILABLE;
+    return NS_ERROR_FILE_NOT_FOUND;
   }
 
   nsCOMPtr<CacheFileIOListener> listener;
   if (aCallback || !mHandle) {
     listener = new DoomFileHelper(aCallback);
   }
   if (mHandle) {
     rv = CacheFileIOManager::DoomFile(mHandle, listener);
@@ -1351,17 +1135,17 @@ CacheFile::RemoveChunk(CacheFileChunk *a
          this, chunk.get()));
 
     chunk->mRemovingChunk = true;
     ReleaseOutsideLock(static_cast<CacheFileChunkListener *>(
                          chunk->mFile.forget().get()));
     mCachedChunks.Put(chunk->Index(), chunk);
     mChunks.Remove(chunk->Index());
     if (!mMemoryOnly)
-      WriteMetadataIfNeeded();
+      WriteMetadataIfNeededLocked();
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFile::RemoveInput(CacheFileInputStream *aInput)
 {
@@ -1371,17 +1155,17 @@ CacheFile::RemoveInput(CacheFileInputStr
 
   DebugOnly<bool> found;
   found = mInputs.RemoveElement(aInput);
   MOZ_ASSERT(found);
 
   ReleaseOutsideLock(static_cast<nsIInputStream*>(aInput));
 
   if (!mMemoryOnly)
-    WriteMetadataIfNeeded();
+    WriteMetadataIfNeededLocked();
 
   return NS_OK;
 }
 
 nsresult
 CacheFile::RemoveOutput(CacheFileOutputStream *aOutput)
 {
   AssertOwnsLock();
@@ -1395,17 +1179,17 @@ CacheFile::RemoveOutput(CacheFileOutputS
   }
 
   mOutput = nullptr;
 
   // Cancel all queued chunk and update listeners that cannot be satisfied
   NotifyListenersAboutOutputRemoval();
 
   if (!mMemoryOnly)
-    WriteMetadataIfNeeded();
+    WriteMetadataIfNeededLocked();
 
   // Notify close listener as the last action
   aOutput->NotifyCloseListener();
 
   return NS_OK;
 }
 
 nsresult
@@ -1527,81 +1311,70 @@ CacheFile::IsDirty()
   return mDataIsDirty || mMetadata->IsDirty();
 }
 
 void
 CacheFile::WriteMetadataIfNeeded()
 {
   LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
 
+  CacheFileAutoLock lock(this);
+
+  if (!mMemoryOnly)
+    WriteMetadataIfNeededLocked();
+}
+
+void
+CacheFile::WriteMetadataIfNeededLocked(bool aFireAndForget)
+{
+  // When aFireAndForget is set to true, we are called from dtor.
+  // |this| must not be referenced after this method returns!
+
+  LOG(("CacheFile::WriteMetadataIfNeededLocked() [this=%p]", this));
+
   nsresult rv;
 
   AssertOwnsLock();
   MOZ_ASSERT(!mMemoryOnly);
 
-  if (mTimer) {
-    mTimer->Cancel();
-    mTimer = nullptr;
+  if (!aFireAndForget) {
+    // if aFireAndForget is set, we are called from dtor. Write
+    // scheduler hard-refers CacheFile otherwise, so we cannot be here.
+    CacheFileIOManager::UnscheduleMetadataWrite(this);
   }
 
   if (NS_FAILED(mStatus))
     return;
 
   if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() ||
       mWritingMetadata || mOpeningFile)
     return;
 
-  LOG(("CacheFile::WriteMetadataIfNeeded() - Writing metadata [this=%p]",
+  LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing metadata [this=%p]",
        this));
 
-  nsRefPtr<MetadataListenerHelper> mlh = new MetadataListenerHelper(this);
-
-  rv = mMetadata->WriteMetadata(mDataSize, mlh);
+  rv = mMetadata->WriteMetadata(mDataSize, aFireAndForget ? nullptr : this);
   if (NS_SUCCEEDED(rv)) {
     mWritingMetadata = true;
     mDataIsDirty = false;
-  }
-  else {
-    LOG(("CacheFile::WriteMetadataIfNeeded() - Writing synchronously failed "
+  } else {
+    LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously failed "
          "[this=%p]", this));
     // TODO: close streams with error
     if (NS_SUCCEEDED(mStatus))
       mStatus = rv;
   }
 }
 
 void
 CacheFile::PostWriteTimer()
 {
   LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
 
-  nsresult rv;
-
-  AssertOwnsLock();
-
-  if (mTimer) {
-    if (mTimer->ShouldFireNew()) {
-      LOG(("CacheFile::PostWriteTimer() - Canceling old timer [this=%p]",
-           this));
-      mTimer->Cancel();
-      mTimer = nullptr;
-    }
-    else {
-      LOG(("CacheFile::PostWriteTimer() - Keeping old timer [this=%p]", this));
-      return;
-    }
-  }
-
-  mTimer = new MetadataWriteTimer(this);
-
-  rv = mTimer->Fire();
-  if (NS_FAILED(rv)) {
-    LOG(("CacheFile::PostWriteTimer() - Firing timer failed with error 0x%08x "
-         "[this=%p]", rv, this));
-  }
+  CacheFileIOManager::ScheduleMetadataWrite(this);
 }
 
 PLDHashOperator
 CacheFile::WriteAllCachedChunks(const uint32_t& aIdx,
                                 nsRefPtr<CacheFileChunk>& aChunk,
                                 void* aClosure)
 {
   CacheFile *file = static_cast<CacheFile*>(aClosure);
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CacheFile__h__
 #define CacheFile__h__
 
 #include "CacheFileChunk.h"
-#include "nsWeakReference.h"
 #include "CacheFileIOManager.h"
 #include "CacheFileMetadata.h"
 #include "nsRefPtrHashtable.h"
 #include "nsClassHashtable.h"
 #include "mozilla/Mutex.h"
 
 class nsIInputStream;
 class nsIOutputStream;
@@ -42,17 +41,16 @@ public:
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileListener, CACHEFILELISTENER_IID)
 
 
 class CacheFile : public CacheFileChunkListener
                 , public CacheFileIOListener
                 , public CacheFileMetadataListener
-                , public nsSupportsWeakReference
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   CacheFile();
 
   nsresult Init(const nsACString &aKey,
                 bool aCreateNew,
@@ -96,22 +94,22 @@ public:
   nsresult GetFrecency(uint32_t *_retval);
   nsresult GetLastFetched(uint32_t *_retval);
   nsresult GetFetchCount(uint32_t *_retval);
 
   bool DataSize(int64_t* aSize);
   void Key(nsACString& aKey) { aKey = mKey; }
 
 private:
+  friend class CacheFileIOManager;
   friend class CacheFileChunk;
   friend class CacheFileInputStream;
   friend class CacheFileOutputStream;
   friend class CacheFileAutoLock;
   friend class MetadataWriteTimer;
-  friend class MetadataListenerHelper;
 
   virtual ~CacheFile();
 
   void     Lock();
   void     Unlock();
   void     AssertOwnsLock();
   void     ReleaseOutsideLock(nsISupports *aObject);
 
@@ -134,16 +132,17 @@ private:
                               CacheFileChunkListener *aCallback);
   nsresult NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
                                 CacheFileChunk *aChunk);
   bool     HaveChunkListeners(uint32_t aIndex);
   void     NotifyListenersAboutOutputRemoval();
 
   bool IsDirty();
   void WriteMetadataIfNeeded();
+  void WriteMetadataIfNeededLocked(bool aFireAndForget = false);
   void PostWriteTimer();
 
   static PLDHashOperator WriteAllCachedChunks(const uint32_t& aIdx,
                                               nsRefPtr<CacheFileChunk>& aChunk,
                                               void* aClosure);
 
   static PLDHashOperator FailListenersIfNonExistentChunk(
                            const uint32_t& aIdx,
@@ -166,17 +165,16 @@ private:
   bool           mKeyIsHash;
   nsresult       mStatus;
   int64_t        mDataSize;
   nsCString      mKey;
 
   nsRefPtr<CacheFileHandle>    mHandle;
   nsRefPtr<CacheFileMetadata>  mMetadata;
   nsCOMPtr<CacheFileListener>  mListener;
-  nsRefPtr<MetadataWriteTimer> mTimer;
   nsCOMPtr<CacheFileIOListener>   mDoomAfterOpenListener;
 
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
   nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners;
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks;
 
   nsTArray<CacheFileInputStream*> mInputs;
   CacheFileOutputStream          *mOutput;
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -4,16 +4,17 @@
 
 #include "CacheLog.h"
 #include "CacheFileIOManager.h"
 
 #include "../cache/nsCacheUtils.h"
 #include "CacheHashUtils.h"
 #include "CacheStorageService.h"
 #include "nsThreadUtils.h"
+#include "CacheFile.h"
 #include "nsIFile.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/DebugOnly.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "private/pprio.h"
 #include "mozilla/VisualEventTracer.h"
 
@@ -31,343 +32,292 @@
 // XXX add necessary include file for ftruncate (or equivalent)
 #endif
 
 
 namespace mozilla {
 namespace net {
 
 #define kOpenHandlesLimit   64
+#define kMetadataWriteDelay 5000
 
+bool
+CacheFileHandle::DispatchRelease()
+{
+  if (CacheFileIOManager::IsOnIOThreadOrCeased())
+    return false;
+
+  nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+  if (!ioTarget)
+    return false;
+
+  nsRefPtr<nsRunnableMethod<CacheFileHandle, nsrefcnt, false> > event =
+    NS_NewNonOwningRunnableMethod(this, &CacheFileHandle::Release);
+  nsresult rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+  if (NS_FAILED(rv))
+    return false;
+
+  return true;
+}
 
 NS_IMPL_ADDREF(CacheFileHandle)
 NS_IMETHODIMP_(nsrefcnt)
 CacheFileHandle::Release()
 {
+  nsrefcnt count = mRefCnt - 1;
+  if (DispatchRelease()) {
+    // Redispatched to the IO thread.
+    return count;
+  }
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
   LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get()));
   NS_PRECONDITION(0 != mRefCnt, "dup release");
-  nsrefcnt count = --mRefCnt;
+  count = --mRefCnt;
   NS_LOG_RELEASE(this, count, "CacheFileHandle");
 
   if (0 == count) {
     mRefCnt = 1;
     delete (this);
     return 0;
   }
 
-  if (!mRemovingHandle && count == 1 && !mClosed) {
-    CacheFileIOManager::gInstance->CloseHandle(this);
-  }
-
   return count;
 }
 
 NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END_THREADSAFE
 
 CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash,
                                  bool aPriority)
   : mHash(aHash)
   , mIsDoomed(false)
-  , mRemovingHandle(false)
   , mPriority(aPriority)
   , mClosed(false)
   , mInvalid(false)
   , mFileExists(false)
   , mFileSize(-1)
   , mFD(nullptr)
 {
   LOG(("CacheFileHandle::CacheFileHandle() [this=%p]", this));
-  MOZ_COUNT_CTOR(CacheFileHandle);
-  PR_INIT_CLIST(this);
 }
 
 CacheFileHandle::~CacheFileHandle()
 {
   LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
-  MOZ_COUNT_DTOR(CacheFileHandle);
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+  nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
+  if (ioMan) {
+    ioMan->CloseHandleInternal(this);
+  }
+}
+
+/******************************************************************************
+ *  CacheFileHandles::HandleHashKey
+ *****************************************************************************/
+
+void
+CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle)
+{
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+  mHandles.InsertElementAt(0, aHandle);
+}
+
+void
+CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle)
+{
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+  DebugOnly<bool> found;
+  found = mHandles.RemoveElement(aHandle);
+  MOZ_ASSERT(found);
 }
 
+already_AddRefed<CacheFileHandle>
+CacheFileHandles::HandleHashKey::GetNewestHandle()
+{
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+  nsRefPtr<CacheFileHandle> handle;
+  if (mHandles.Length())
+    handle = mHandles[0];
+
+  return handle.forget();
+}
+
+void
+CacheFileHandles::HandleHashKey::GetHandles(nsTArray<nsRefPtr<CacheFileHandle> > &aResult)
+{
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+  for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+    CacheFileHandle* handle = mHandles[i];
+    aResult.AppendElement(handle);
+  }
+}
+
+#ifdef DEBUG
+
+void
+CacheFileHandles::HandleHashKey::AssertHandlesState()
+{
+  for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+    CacheFileHandle* handle = mHandles[i];
+    MOZ_ASSERT(handle->IsDoomed());
+  }
+}
+
+#endif
 
 /******************************************************************************
  *  CacheFileHandles
  *****************************************************************************/
 
-class CacheFileHandlesEntry : public PLDHashEntryHdr
-{
-public:
-  PRCList      *mHandles;
-  SHA1Sum::Hash mHash;
-};
-
-const PLDHashTableOps CacheFileHandles::mOps =
-{
-  PL_DHashAllocTable,
-  PL_DHashFreeTable,
-  HashKey,
-  MatchEntry,
-  MoveEntry,
-  ClearEntry,
-  PL_DHashFinalizeStub
-};
-
 CacheFileHandles::CacheFileHandles()
-  : mInitialized(false)
 {
   LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
   MOZ_COUNT_CTOR(CacheFileHandles);
 }
 
 CacheFileHandles::~CacheFileHandles()
 {
   LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
   MOZ_COUNT_DTOR(CacheFileHandles);
-
-  if (mInitialized)
-    Shutdown();
-}
-
-nsresult
-CacheFileHandles::Init()
-{
-  LOG(("CacheFileHandles::Init() %p", this));
-
-  MOZ_ASSERT(!mInitialized);
-  mInitialized = PL_DHashTableInit(&mTable, &mOps, nullptr,
-                                   sizeof(CacheFileHandlesEntry), 512);
-
-  return mInitialized ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-}
-
-void
-CacheFileHandles::Shutdown()
-{
-  LOG(("CacheFileHandles::Shutdown() %p", this));
-
-  if (mInitialized) {
-    PL_DHashTableFinish(&mTable);
-    mInitialized = false;
-  }
-}
-
-PLDHashNumber
-CacheFileHandles::HashKey(PLDHashTable *table, const void *key)
-{
-  const SHA1Sum::Hash *hash = static_cast<const SHA1Sum::Hash *>(key);
-  return static_cast<PLDHashNumber>(((*hash)[0] << 24) | ((*hash)[1] << 16) |
-                                    ((*hash)[2] << 8) | (*hash)[3]);
-}
-
-bool
-CacheFileHandles::MatchEntry(PLDHashTable *table,
-                             const PLDHashEntryHdr *header,
-                             const void *key)
-{
-  const CacheFileHandlesEntry *entry;
-
-  entry = static_cast<const CacheFileHandlesEntry *>(header);
-
-  return (memcmp(&entry->mHash, key, sizeof(SHA1Sum::Hash)) == 0);
-}
-
-void
-CacheFileHandles::MoveEntry(PLDHashTable *table,
-                            const PLDHashEntryHdr *from,
-                            PLDHashEntryHdr *to)
-{
-  const CacheFileHandlesEntry *src;
-  CacheFileHandlesEntry *dst;
-
-  src = static_cast<const CacheFileHandlesEntry *>(from);
-  dst = static_cast<CacheFileHandlesEntry *>(to);
-
-  dst->mHandles = src->mHandles;
-  memcpy(&dst->mHash, &src->mHash, sizeof(SHA1Sum::Hash));
-
-  LOG(("CacheFileHandles::MoveEntry() hash=%08x%08x%08x%08x%08x "
-       "moving from %p to %p", LOGSHA1(src->mHash), from, to));
-
-  // update pointer to mHash in all handles
-  CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(dst->mHandles);
-  while (handle != dst->mHandles) {
-    handle->mHash = &dst->mHash;
-    handle = (CacheFileHandle *)PR_NEXT_LINK(handle);
-  }
-}
-
-void
-CacheFileHandles::ClearEntry(PLDHashTable *table,
-                             PLDHashEntryHdr *header)
-{
-  CacheFileHandlesEntry *entry = static_cast<CacheFileHandlesEntry *>(header);
-  delete entry->mHandles;
-  entry->mHandles = nullptr;
 }
 
 nsresult
 CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash,
                             bool aReturnDoomed,
                             CacheFileHandle **_retval)
 {
-  MOZ_ASSERT(mInitialized);
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+  MOZ_ASSERT(aHash);
 
   // find hash entry for key
-  CacheFileHandlesEntry *entry;
-  entry = static_cast<CacheFileHandlesEntry *>(
-    PL_DHashTableOperate(&mTable,
-                         (void *)aHash,
-                         PL_DHASH_LOOKUP));
-  if (PL_DHASH_ENTRY_IS_FREE(entry)) {
+  HandleHashKey *entry = mTable.GetEntry(*aHash);
+  if (!entry) {