Merge m-c to b2g-inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 16 May 2014 15:29:08 -0400
changeset 183585 8b3b717daa951e7cb0653024fcfd4dc04d65ab87
parent 183584 c01ea5e44ac5d0df20a2eaf807b40bb8f27d368b (current diff)
parent 183569 616dc757d98a99e40531d33860868eee42f2120f (diff)
child 183586 d12d94618483b234979a0fee910485d4770af963
push idunknown
push userunknown
push dateunknown
milestone32.0a1
Merge m-c to b2g-inbound.
mobile/android/modules/DownloadNotifications.jsm
--- a/browser/base/content/aboutneterror/netError.css
+++ b/browser/base/content/aboutneterror/netError.css
@@ -29,17 +29,16 @@ ul {
 
 #errorPageContainer {
   min-width: 320px;
   max-width: 512px;
 }
 
 #errorTitle {
   background: url("info.svg") left 0 no-repeat;
-  background-size: 3em 3em;
   -moz-margin-start: -5em;
   -moz-padding-start: 5em;
 }
 
 #errorTitle:-moz-locale-dir(rtl) {
   background-position: right 0;
 }
 
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -1381,31 +1381,38 @@ let CustomizableUIInternal = {
                     closemenuVal : "auto";
       }
 
       if (isMenuItem && target.hasAttribute("closemenu")) {
         let closemenuVal = target.getAttribute("closemenu");
         menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
                             closemenuVal : "auto";
       }
+      // Break out of the loop immediately for disabled items, as we need to
+      // keep the menu open in that case.
+      if (target.getAttribute("disabled") == "true") {
+        return true;
+      }
+
       // 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;
       }
     }
+
     // 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
--- a/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
+++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
@@ -54,17 +54,17 @@ add_task(function() {
   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() {
+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";
@@ -86,16 +86,32 @@ add_task(function() {
   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");
 });
 
+add_task(function*() {
+  button = document.createElement("toolbarbutton");
+  button.id = "browser_946166_button_disabled";
+  button.setAttribute("disabled", "true");
+  button.setAttribute("label", "Button");
+  PanelUI.contents.appendChild(button);
+  yield PanelUI.show();
+  EventUtils.synthesizeMouseAtCenter(button, {});
+  is(PanelUI.panel.state, "open", "Popup stays open");
+  button.removeAttribute("disabled");
+  let hiddenAgain = promisePanelHidden(window);
+  EventUtils.synthesizeMouseAtCenter(button, {});
+  yield hiddenAgain;
+  button.remove();
+});
+
 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
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -77,17 +77,20 @@ TranslationUI.prototype = {
     let notification =
       PopupNotifications.getNotification(removeId, this.browser);
     if (notification)
       PopupNotifications.remove(notification);
 
     let callback = aTopic => {
       if (aTopic != "showing")
         return false;
-      if (!this.notificationBox.getNotificationWithValue("translation"))
+      let notification = this.notificationBox.getNotificationWithValue("translation");
+      if (notification)
+        notification.close();
+      else
         this.showTranslationInfoBar();
       return true;
     };
 
     let addId = aTranslated ? "translated" : "translate";
     PopupNotifications.show(this.browser, addId, null,
                             addId + "-notification-icon", null, null,
                             {dismissed: true, eventCallback: callback});
--- a/browser/components/translation/test/browser_translation_infobar.js
+++ b/browser/components/translation/test/browser_translation_infobar.js
@@ -56,16 +56,20 @@ function showTranslationUI(aDetectedLang
   let browser = gBrowser.selectedBrowser;
   Translation.languageDetected(browser, aDetectedLanguage);
   let ui = browser.translationUI;
   for (let name of ["translate", "_reset", "failTranslation", "finishTranslation"])
     ui[name] = TranslationStub[name];
   return ui.notificationBox.getNotificationWithValue("translation");
 }
 
+function hasTranslationInfoBar() {
+  return !!gBrowser.getNotificationBox().getNotificationWithValue("translation");
+}
+
 function test() {
   waitForExplicitFinish();
 
   let tab = gBrowser.addTab();
   gBrowser.selectedTab = tab;
   tab.linkedBrowser.addEventListener("load", function onload() {
     tab.linkedBrowser.removeEventListener("load", onload, true);
     TranslationStub.browser = gBrowser.selectedBrowser;
@@ -178,23 +182,28 @@ function run_tests(aFinishCallback) {
   is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
   ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
   is(notif.translation.translatedFrom, "ja", "from language correct");
   notif.close();
 
   info("Reopen to check the 'Not Now' button closes the notification.");
   notif = showTranslationUI("fr");
   let notificationBox = gBrowser.getNotificationBox();
-  ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification");
+  is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
   notif._getAnonElt("notNow").click();
-  ok(!notificationBox.getNotificationWithValue("translation"), "no 'translate' notification after clicking 'not now'");
+  is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking 'not now'");
+
+  info("Reopen to check the url bar icon closes the notification.");
+  notif = showTranslationUI("fr");
+  is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
+  PopupNotifications.getNotification("translate").anchorElement.click();
+  is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking the url bar icon");
 
   info("Check that clicking the url bar icon reopens the info bar");
   checkURLBarIcon();
   // Clicking the anchor element causes a 'showing' event to be sent
   // asynchronously to our callback that will then show the infobar.
   PopupNotifications.getNotification("translate").anchorElement.click();
-  waitForCondition(() => !!notificationBox.getNotificationWithValue("translation"), () => {
-    ok(!!notificationBox.getNotificationWithValue("translation"),
-       "there's a 'translate' notification");
+  waitForCondition(hasTranslationInfoBar, () => {
+    ok(hasTranslationInfoBar(), "there's a 'translate' notification");
     aFinishCallback();
   }, "timeout waiting for the info bar to reappear");
 }
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -684,17 +684,17 @@ AddonDebugger.prototype = {
 }
 
 function initChromeDebugger(aOnClose) {
   info("Initializing a chrome debugger process.");
 
   let deferred = promise.defer();
 
   // Wait for the toolbox process to start...
-  BrowserToolboxProcess.init(aOnClose, aProcess => {
+  BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => {
     info("Browser toolbox process started successfully.");
 
     prepareDebugger(aProcess);
     deferred.resolve(aProcess);
   });
 
   return deferred.promise;
 }
--- a/browser/devtools/inspector/selector-search.js
+++ b/browser/devtools/inspector/selector-search.js
@@ -393,48 +393,57 @@ SelectorSearch.prototype = {
         this._onHTMLSearch();
         break;
     }
   },
 
   /**
    * Populates the suggestions list and show the suggestion popup.
    */
-  _showPopup: function(aList, aFirstPart) {
+  _showPopup: function(aList, aFirstPart, aState) {
     let total = 0;
     let query = this.searchBox.value;
-    let toLowerCase = false;
     let items = [];
-    // In case of tagNames, change the case to small.
-    if (query.match(/.*[\.#][^\.#]{0,}$/) == null) {
-      toLowerCase = true;
-    }
-    for (let [value, count] of aList) {
+
+    for (let [value, count, state] of aList) {
       // for cases like 'div ' or 'div >' or 'div+'
       if (query.match(/[\s>+]$/)) {
         value = query + value;
       }
       // for cases like 'div #a' or 'div .a' or 'div > d' and likewise
       else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#]*$/)) {
         let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+\.#]*$/)[0];
         value = query.slice(0, -1 * lastPart.length + 1) + value;
       }
       // for cases like 'div.class' or '#foo.bar' and likewise
       else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
         let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s>+]*$/)[0];
         value = query.slice(0, -1 * lastPart.length + 1) + value;
       }
+
       let item = {
         preLabel: query,
         label: value,
         count: count
       };
-      if (toLowerCase) {
+
+      // In case of tagNames, change te case to small
+      if (value.match(/.*[\.#][^\.#]{0,}$/) == null) {
         item.label = value.toLowerCase();
       }
+
+      // In case the query's state is tag and the item's state is id or class
+      // adjust the preLabel
+      if (aState === this.States.TAG && state === this.States.CLASS) {
+        item.preLabel = "." + item.preLabel;
+      }
+      if (aState === this.States.TAG && state === this.States.ID) {
+        item.preLabel = "#" + item.preLabel;
+      }
+
       items.unshift(item);
       if (++total > MAX_SUGGESTIONS - 1) {
         break;
       }
     }
     if (total > 0) {
       this.searchPopup.setItems(items);
       this.searchPopup.openPopup(this.searchBox);
@@ -445,48 +454,53 @@ SelectorSearch.prototype = {
   },
 
   /**
    * Suggests classes,ids and tags based on the user input as user types in the
    * searchbox.
    */
   showSuggestions: function() {
     let query = this.searchBox.value;
+    let state = this.state;
     let firstPart = "";
-    if (this.state == this.States.TAG) {
+
+    if (state == this.States.TAG) {
       // gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
       // 'di' returns 'di' and likewise.
       firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
       query = query.slice(0, query.length - firstPart.length);
     }
-    else if (this.state == this.States.CLASS) {
+    else if (state == this.States.CLASS) {
       // gets the class that is being completed. For ex. '.foo.b' returns 'b'
       firstPart = query.match(/\.([^\.]*)$/)[1];
       query = query.slice(0, query.length - firstPart.length - 1);
     }
-    else if (this.state == this.States.ID) {
+    else if (state == this.States.ID) {
       // gets the id that is being completed. For ex. '.foo#b' returns 'b'
       firstPart = query.match(/#([^#]*)$/)[1];
       query = query.slice(0, query.length - firstPart.length - 1);
     }
     // TODO: implement some caching so that over the wire request is not made
     // everytime.
     if (/[\s+>~]$/.test(query)) {
       query += "*";
     }
+
     this._currentSuggesting = query;
-    return this.walker.getSuggestionsForQuery(query, firstPart, this.state).then(result => {
+    return this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
       if (this._currentSuggesting != result.query) {
         // This means that this response is for a previous request and the user
         // as since typed something extra leading to a new request.
         return;
       }
       this._lastToLastValidSearch = this._lastValidSearch;
-      if (this.state == this.States.CLASS) {
+
+      if (state == this.States.CLASS) {
         firstPart = "." + firstPart;
       }
-      else if (this.state == this.States.ID) {
+      else if (state == this.States.ID) {
         firstPart = "#" + firstPart;
       }
-      this._showPopup(result.suggestions, firstPart);
+
+      this._showPopup(result.suggestions, firstPart, state);
     });
   }
 };
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -45,8 +45,9 @@ support-files =
 [browser_inspector_select_last_selected.js]
 [browser_inspector_sidebarstate.js]
 [browser_inspector_bug_848731_reset_selection_on_delete.js]
 [browser_inspector_bug_922125_destroy_on_navigate.js]
 [browser_inspector_bug_958456_highlight_comments.js]
 [browser_inspector_bug_958169_switch_to_inspector_on_pick.js]
 [browser_inspector_bug_961771_picker_stops_on_tool_select.js]
 [browser_inspector_bug_962478_picker_stops_on_destroy.js]
+[browser_inspector_search-suggests-ids-and-classes.js]
--- a/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
@@ -10,27 +10,27 @@ function test()
   // The various states of the inspector: [key, suggestions array]
   // [
   //  what key to press,
   //  suggestions array with count [
   //    [suggestion1, count1], [suggestion2] ...
   //  ] count can be left to represent 1
   // ]
   let keyStates = [
-    ["d", [["div", 4]]],
+    ["d", [["div", 4], ["#d1", 1], ["#d2", 1]]],
     ["i", [["div", 4]]],
     ["v", []],
     [" ", [["div div", 2], ["div span", 2]]],
     [">", [["div >div", 2], ["div >span", 2]]],
     ["VK_BACK_SPACE", [["div div", 2], ["div span", 2]]],
     ["+", [["div +span"]]],
     ["VK_BACK_SPACE", [["div div", 2], ["div span", 2]]],
     ["VK_BACK_SPACE", []],
     ["VK_BACK_SPACE", [["div", 4]]],
-    ["VK_BACK_SPACE", [["div", 4]]],
+    ["VK_BACK_SPACE", [["div", 4], ["#d1", 1], ["#d2", 1]]],
     ["VK_BACK_SPACE", []],
     ["p", []],
     [" ", [["p strong"]]],
     ["+", [["p +button"], ["p +p"]]],
     ["b", [["p +button"]]],
     ["u", [["p +button"]]],
     ["t", [["p +button"]]],
     ["t", [["p +button"]]],
--- a/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
@@ -10,25 +10,25 @@ function test()
   // The various states of the inspector: [key, suggestions array]
   // [
   //  what key to press,
   //  suggestions array with count [
   //    [suggestion1, count1], [suggestion2] ...
   //  ] count can be left to represent 1
   // ]
   let keyStates = [
-    ["d", [["div", 2]]],
+    ["d", [["div", 2], ["#d1", 1], ["#d2", 1]]],
     ["i", [["div", 2]]],
     ["v", []],
     [".", [["div.c1"]]],
     ["VK_BACK_SPACE", []],
     ["#", [["div#d1"], ["div#d2"]]],
     ["VK_BACK_SPACE", []],
     ["VK_BACK_SPACE", [["div", 2]]],
-    ["VK_BACK_SPACE", [["div", 2]]],
+    ["VK_BACK_SPACE", [["div", 2], ["#d1", 1], ["#d2", 1]]],
     ["VK_BACK_SPACE", []],
     [".", [[".c1", 3], [".c2"]]],
     ["c", [[".c1", 3], [".c2"]]],
     ["2", []],
     ["VK_BACK_SPACE", [[".c1", 3], [".c2"]]],
     ["1", []],
     ["#", [["#d2"], ["#p1"], ["#s2"]]],
     ["VK_BACK_SPACE", []],
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_search-suggests-ids-and-classes.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the selector-search input proposes ids and classes even when . and
+// # is missing, but that this only occurs when the query is one word (no
+// selector combination)
+
+function test()
+{
+  waitForExplicitFinish();
+
+  let inspector, searchBox, state, popup;
+
+  // The various states of the inspector: [key, suggestions array]
+  // [
+  //  what key to press,
+  //  suggestions array with count [
+  //    [suggestion1, count1], [suggestion2] ...
+  //  ] count can be left to represent 1
+  // ]
+  let keyStates = [
+    ["s", [["span", 1], [".span", 1], ["#span", 1]]],
+    ["p", [["span", 1], [".span", 1], ["#span", 1]]],
+    ["a", [["span", 1], [".span", 1], ["#span", 1]]],
+    ["n", []],
+    [" ", [["span div", 1]]],
+    ["d", [["span div", 1]]], // mixed tag/class/id suggestions only work for the first word
+    ["VK_BACK_SPACE", [["span div", 1]]],
+    ["VK_BACK_SPACE", []],
+    ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
+    ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
+    ["VK_BACK_SPACE", [["span", 1], [".span", 1], ["#span", 1]]],
+    ["VK_BACK_SPACE", []],
+    // Test that mixed tags, classes and ids are grouped by types, sorted by
+    // count and alphabetical order
+    ["b", [
+      ["button", 3],
+      ["body", 1],
+      [".bc", 3],
+      [".ba", 1],
+      [".bb", 1],
+      ["#ba", 1],
+      ["#bb", 1],
+      ["#bc", 1]
+    ]],
+  ];
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    waitForFocus(setupTest, content);
+  }, true);
+
+  content.location = "data:text/html," +
+                     "<span class='span' id='span'>" +
+                     "  <div class='div' id='div'></div>" +
+                     "</span>" +
+                     "<button class='ba bc' id='bc'></button>" +
+                     "<button class='bb bc' id='bb'></button>" +
+                     "<button class='bc' id='ba'></button>";
+
+  function $(id) {
+    if (id == null) return null;
+    return content.document.getElementById(id);
+  }
+
+  function setupTest()
+  {
+    openInspector(startTest);
+  }
+
+  function startTest(aInspector)
+  {
+    inspector = aInspector;
+
+    searchBox =
+      inspector.panelWin.document.getElementById("inspector-searchbox");
+    popup = inspector.searchSuggestions.searchPopup;
+
+    focusSearchBoxUsingShortcut(inspector.panelWin, function() {
+      searchBox.addEventListener("command", checkState, true);
+      checkStateAndMoveOn(0);
+    });
+  }
+
+  function checkStateAndMoveOn(index) {
+    if (index == keyStates.length) {
+      finishUp();
+      return;
+    }
+
+    let [key, suggestions] = keyStates[index];
+    state = index;
+
+    info("pressing key " + key + " to get suggestions " +
+         JSON.stringify(suggestions));
+    EventUtils.synthesizeKey(key, {}, inspector.panelWin);
+  }
+
+  function checkState(event) {
+    inspector.searchSuggestions._lastQuery.then(() => {
+      let [key, suggestions] = keyStates[state];
+      let actualSuggestions = popup.getItems();
+      is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
+         "There are expected number of suggestions at " + state + "th step.");
+      actualSuggestions.reverse();
+      for (let i = 0; i < suggestions.length; i++) {
+        is(suggestions[i][0], actualSuggestions[i].label,
+           "The suggestion at " + i + "th index for " + state +
+           "th step is correct.")
+        is(suggestions[i][1] || 1, actualSuggestions[i].count,
+           "The count for suggestion at " + i + "th index for " + state +
+           "th step is correct.")
+      }
+      checkStateAndMoveOn(state + 1);
+    });
+  }
+
+  function finishUp() {
+    searchBox = null;
+    popup = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
--- a/browser/devtools/sourceeditor/css-autocompleter.js
+++ b/browser/devtools/sourceeditor/css-autocompleter.js
@@ -752,49 +752,64 @@ CSSCompleter.prototype = {
   */
   prepareSelectorResults: function(result) {
     if (this._currentQuery != result.query)
       return [];
 
     result = result.suggestions;
     let query = this.selector;
     let completion = [];
-    for (let value of result) {
+    for (let [value, count, state] of result) {
       switch(this.selectorState) {
         case SELECTOR_STATES.id:
         case SELECTOR_STATES.class:
         case SELECTOR_STATES.pseudo:
           if (/^[.:#]$/.test(this.completing)) {
-            value[0] = query.slice(0, query.length - this.completing.length) +
-                       value[0];
+            value = query.slice(0, query.length - this.completing.length) +
+                       value;
           } else {
-            value[0] = query.slice(0, query.length - this.completing.length - 1) +
-                       value[0];
+            value = query.slice(0, query.length - this.completing.length - 1) +
+                       value;
           }
           break;
 
         case SELECTOR_STATES.tag:
-          value[0] = query.slice(0, query.length - this.completing.length) +
-                     value[0];
+          value = query.slice(0, query.length - this.completing.length) +
+                     value;
           break;
 
         case SELECTOR_STATES.null:
-          value[0] = query + value[0];
+          value = query + value;
           break;
 
         default:
-         value[0] = query.slice(0, query.length - this.completing.length) +
-                    value[0];
+         value = query.slice(0, query.length - this.completing.length) +
+                    value;
       }
-      completion.push({
-        label: value[0],
+
+      let item = {
+        label: value,
         preLabel: query,
-        text: value[0],
-        score: value[1]
-      });
+        text: value,
+        score: count
+      };
+
+      // In case the query's state is tag and the item's state is id or class
+      // adjust the preLabel
+      if (this.selectorState === SELECTOR_STATES.tag &&
+          state === SELECTOR_STATES.class) {
+        item.preLabel = "." + item.preLabel;
+      }
+      if (this.selectorState === SELECTOR_STATES.tag &&
+          state === SELECTOR_STATES.id) {
+        item.preLabel = "#" + item.preLabel;
+      }
+
+      completion.push(item);
+
       if (completion.length > this.maxEntries - 1)
         break;
     }
     return completion;
   },
 
   /**
    * Returns CSS property name suggestions based on the input.
--- a/browser/devtools/sourceeditor/test/css_autocompletion_tests.json
+++ b/browser/devtools/sourceeditor/test/css_autocompletion_tests.json
@@ -16,21 +16,21 @@
              '-moz-animation-play-state', '-moz-animation-timing-function',
              '-moz-appearance']],
   [[12, 20], ['none', 'number-input']],
   [[12, 22], ['none']],
   [[17, 22], ['hsl', 'hsla']],
   [[21,  9], ["-moz-calc", "auto", "calc", "inherit", "initial","unset"]],
   [[22,  5], ['color', 'color-interpolation', 'color-interpolation-filters']],
   [[25, 26], ['.devtools-toolbarbutton > tab',
-              '.devtools-toolbarbutton > .toolbarbutton-menubutton-button',
-              '.devtools-toolbarbutton > hbox']],
+              '.devtools-toolbarbutton > hbox',
+              '.devtools-toolbarbutton > .toolbarbutton-menubutton-button']],
   [[25, 31], ['.devtools-toolbarbutton > hbox.toolbarbutton-menubutton-button']],
   [[29, 20], ['.devtools-menulist:after', '.devtools-menulist:active']],
   [[30, 10], ['#devtools-anotherone', '#devtools-itjustgoeson', '#devtools-menu',
               '#devtools-okstopitnow', '#devtools-toolbarbutton', '#devtools-yetagain']],
   [[39, 39], ['.devtools-toolbarbutton:not([label]) > tab']],
   [[43, 51], ['.devtools-toolbarbutton:not([checked=true]):hover:after',
               '.devtools-toolbarbutton:not([checked=true]):hover:active']],
   [[58, 36], ['!important;']],
-  [[73, 42], [':last-child', ':lang(', ':last-of-type', ':link']],
+  [[73, 42], [':lang(', ':last-of-type', ':link', ':last-child']],
   [[77, 25], ['.visible']],
 ]
--- a/browser/devtools/styleinspector/ruleview.css
+++ b/browser/devtools/styleinspector/ruleview.css
@@ -55,11 +55,10 @@
   line-height: 1.5em;
 }
 
 .ruleview-header.ruleview-expandable-header {
   cursor: pointer;
 }
 
 .ruleview-colorswatch {
-  display: inline-block;
   cursor: pointer;
 }
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -377,16 +377,20 @@ Experiments.Experiments = function (poli
   // * saving the cache (if _dirty)
   this._mainTask = null;
 
   // Timer for re-evaluating experiment status.
   this._timer = null;
 
   this._shutdown = false;
 
+  // We need to tell when we first evaluated the experiments to fire an
+  // experiments-changed notification when we only loaded completed experiments.
+  this._firstEvaluate = true;
+
   this.init();
 };
 
 Experiments.Experiments.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
 
   init: function () {
     this._shutdown = false;
@@ -1170,18 +1174,19 @@ Experiments.Experiments.prototype = {
           experiment._enabled = false;
           yield experiment.reconcileAddonState();
         }
       }
     }
 
     gPrefs.set(PREF_ACTIVE_EXPERIMENT, activeExperiment != null);
 
-    if (activeChanged) {
+    if (activeChanged || this._firstEvaluate) {
       Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC, null);
+      this._firstEvaluate = false;
     }
 
     if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) {
       try {
         gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id);
       } catch (e) {
         // It's ok if crash reporting is disabled.
       }
--- a/browser/experiments/ExperimentsService.js
+++ b/browser/experiments/ExperimentsService.js
@@ -67,31 +67,43 @@ ExperimentsService.prototype = {
   },
 
   observe: function (subject, topic, data) {
     switch (topic) {
       case "profile-after-change":
         if (gExperimentsEnabled) {
           Services.obs.addObserver(this, "quit-application", false);
           Services.obs.addObserver(this, "sessionstore-state-finalized", false);
+          Services.obs.addObserver(this, "EM-loaded", false);
 
           if (gActiveExperiment) {
             this._initialized = true;
             Experiments.instance(); // for side effects
           }
         }
         break;
       case "sessionstore-state-finalized":
         if (!this._initialized) {
           CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer");
         }
         break;
+      case "EM-loaded":
+        if (!this._initialized) {
+          Experiments.instance(); // for side effects
+          this._initialized = true;
+
+          if (this._delayedInitTimer) {
+            this._delayedInitTimer.clear();
+          }
+        }
+        break;
       case "quit-application":
         Services.obs.removeObserver(this, "quit-application");
         Services.obs.removeObserver(this, "sessionstore-state-finalized");
+        Services.obs.removeObserver(this, "EM-loaded");
         if (this._delayedInitTimer) {
           this._delayedInitTimer.clear();
         }
         break;
     }
   },
 };
 
--- a/browser/experiments/test/xpcshell/test_api.js
+++ b/browser/experiments/test/xpcshell/test_api.js
@@ -156,18 +156,18 @@ add_task(function* test_getExperiments()
   // Trigger update, clock set to before any activation.
   // Use updateManifest() to provide for coverage of that path.
 
   let now = baseDate;
   gTimerScheduleOffset = -1;
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   Assert.equal(experiments.getActiveExperimentID(), null,
                "getActiveExperimentID should return null");
 
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
   let addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
 
@@ -372,18 +372,18 @@ add_task(function* test_addonAlreadyInst
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for the experiment to start.
 
   now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
@@ -676,18 +676,18 @@ add_task(function* test_installFailure()
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for experiment 1 & 2 to start,
   // invalid hash for experiment 1.
   // Order in the manifest matters, so we should start experiment 1,
   // fail to install it & start experiment 2 instead.
 
@@ -782,18 +782,18 @@ add_task(function* test_userDisabledAndU
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for experiment 1 to start.
 
   now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
@@ -881,18 +881,18 @@ add_task(function* test_updateActiveExpe
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   let todayActive = yield experiments.lastActiveToday();
   Assert.equal(todayActive, null, "No experiment active today.");
 
   // Trigger update, clock set for the experiment to start.
 
@@ -972,18 +972,18 @@ add_task(function* test_disableActiveExp
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for the experiment to start.
 
   now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
@@ -1065,29 +1065,29 @@ add_task(function* test_freezePendingExp
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for the experiment to start but frozen.
 
   now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   gManifestObject.experiments[0].frozen = true;
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called.");
+  Assert.equal(observerFireCount, expectedObserverFireCount,
+               "Experiments observer should have not been called.");
 
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should have no entries yet.");
 
   // Trigger an update with the experiment not being frozen anymore.
 
   now = futureDate(now, 1 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
@@ -1143,18 +1143,18 @@ add_task(function* test_freezeActiveExpe
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for the experiment to start.
 
   now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
@@ -1235,18 +1235,18 @@ add_task(function* test_removeActiveExpe
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for the experiment to start.
 
   now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
@@ -1316,18 +1316,18 @@ add_task(function* test_invalidUrl() {
 
   // Trigger update, clock set for the experiment to start.
 
   let now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   gTimerScheduleOffset = null;
 
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, expectedObserverFireCount,
-               "Experiments observer should not have been called.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   Assert.equal(gTimerScheduleOffset, null, "No new timer should have been scheduled.");
 
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Cleanup.
 
   Services.obs.removeObserver(observer, OBSERVER_TOPIC);
@@ -1370,18 +1370,18 @@ add_task(function* test_unexpectedUninst
 
   let experiments = new Experiments.Experiments(gPolicy);
 
   // Trigger update, clock set to before any activation.
 
   let now = baseDate;
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
 
   // Trigger update, clock set for the experiment to start.
 
   now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   yield experiments.updateManifest();
--- a/browser/experiments/test/xpcshell/test_disableExperiments.js
+++ b/browser/experiments/test/xpcshell/test_disableExperiments.js
@@ -117,18 +117,18 @@ add_task(function* test_disableExperimen
 
   // Trigger update, clock set to before any activation.
   // Use updateManifest() to provide for coverage of that path.
 
   let now = baseDate;
   defineNow(gPolicy, now);
 
   yield experiments.updateManifest();
-  Assert.equal(observerFireCount, 0,
-               "Experiments observer should not have been called yet.");
+  Assert.equal(observerFireCount, ++expectedObserverFireCount,
+               "Experiments observer should have been called.");
   let list = yield experiments.getExperiments();
   Assert.equal(list.length, 0, "Experiment list should be empty.");
   let addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
 
   // Trigger update, clock set for experiment 1 to start.
 
   now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
--- a/browser/themes/linux/devtools/computedview.css
+++ b/browser/themes/linux/devtools/computedview.css
@@ -145,15 +145,29 @@ body {
 
 .link {
   padding: 0 3px;
   cursor: pointer;
   float: right;
 }
 
 .computedview-colorswatch {
-  display: inline-block;
   border-radius: 50%;
   width: 1em;
   height: 1em;
   vertical-align: text-top;
   -moz-margin-end: 5px;
+  display: inline-block;
 }
+
+.computedview-colorswatch::before {
+  content: '';
+  background-color: #eee;
+  background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+                    linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+  background-size: 12px 12px;
+  background-position: 0 0, 6px 6px;
+  position: absolute;
+  border-radius: 50%;
+  width: 1em;
+  height: 1em;
+  z-index: -1;
+}
--- a/browser/themes/osx/devtools/computedview.css
+++ b/browser/themes/osx/devtools/computedview.css
@@ -163,15 +163,29 @@ body {
 
 .link {
   padding: 0 3px;
   cursor: pointer;
   float: right;
 }
 
 .computedview-colorswatch {
-  display: inline-block;
   border-radius: 50%;
   width: 1em;
   height: 1em;
   vertical-align: text-top;
   -moz-margin-end: 5px;
+  display: inline-block;
 }
+
+.computedview-colorswatch::before {
+  content: '';
+  background-color: #eee;
+  background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+                    linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+  background-size: 12px 12px;
+  background-position: 0 0, 6px 6px;
+  position: absolute;
+  border-radius: 50%;
+  width: 1em;
+  height: 1em;
+  z-index: -1;
+}
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -134,18 +134,17 @@
   border-color: hsla(210,8%,5%,.6);
 }
 
 .theme-fg-contrast { /* To be used for text on theme-bg-contrast */
   color: black;
 }
 
 .ruleview-colorswatch,
-.computedview-colorswatch,
-.markupview-colorswatch {
+.computedview-colorswatch {
   box-shadow: 0 0 0 1px #818181;
 }
 
 /* CodeMirror specific styles.
  * Best effort to match the existing theme, some of the colors
  * are duplicated here to prevent weirdness in the main theme. */
 
 .CodeMirror { /* Inherit platform specific font sizing and styles */
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -133,18 +133,17 @@
 .devtools-toolbar,
 .devtools-sidebar-tabs > tabs { /* General toolbar styling */
   color: #585959;
   background-color: #f0f1f2;
   border-color: #aaa;
 }
 
 .ruleview-colorswatch,
-.computedview-colorswatch,
-.markupview-colorswatch {
+.computedview-colorswatch {
   box-shadow: 0 0 0 1px #c4c4c4;
 }
 
 /* CodeMirror specific styles.
  * Best effort to match the existing theme, some of the colors
  * are duplicated here to prevent weirdness in the main theme. */
 
 .CodeMirror { /* Inherit platform specific font sizing and styles */
--- a/browser/themes/shared/devtools/ruleview.css
+++ b/browser/themes/shared/devtools/ruleview.css
@@ -113,16 +113,31 @@
 }
 
 .ruleview-colorswatch {
   border-radius: 50%;
   width: 1em;
   height: 1em;
   vertical-align: text-top;
   -moz-margin-end: 5px;
+  display: inline-block;
+}
+
+.ruleview-colorswatch::before {
+  content: '';
+  background-color: #eee;
+  background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+                    linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+  background-size: 12px 12px;
+  background-position: 0 0, 6px 6px;
+  position: absolute;
+  border-radius: 50%;
+  width: 1em;
+  height: 1em;
+  z-index: -1;
 }
 
 .ruleview-overridden {
   text-decoration: line-through;
 }
 
 .theme-light .ruleview-overridden {
   -moz-text-decoration-color: #667380; /*  Content (Text) - Dark Grey */
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -764,16 +764,17 @@ toolbarbutton[sdk-button="true"][cui-are
   }
 
   #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(rtl),
   #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(ltr) {
     border-top-left-radius: 0;
     border-bottom-left-radius: 0;
   }
 
+  #nav-bar .toolbarbutton-1:not([disabled=true]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
   #nav-bar .toolbarbutton-1:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
   #nav-bar .toolbarbutton-1:not([disabled]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
   #nav-bar .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
   #nav-bar .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-text,
   #nav-bar .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-badge-container,
   @conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
     background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
     background-color: transparent;
@@ -806,17 +807,17 @@ toolbarbutton[sdk-button="true"][cui-are
   transition-duration: 10ms;
 }
 
 %ifdef WINDOWS_AERO
 @media (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
 %endif
   /* < Win8 */
-  #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover:active > .toolbarbutton-icon,
+  #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open]) > .toolbarbutton-icon,
   #nav-bar .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon,
   #nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon,
   #nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-text,
   #nav-bar .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container {
     background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
     background-color: hsla(210,54%,20%,.15);
     border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
     box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
--- a/browser/themes/windows/devtools/computedview.css
+++ b/browser/themes/windows/devtools/computedview.css
@@ -163,15 +163,29 @@ body {
 
 .link {
   padding: 0 3px;
   cursor: pointer;
   float: right;
 }
 
 .computedview-colorswatch {
-  display: inline-block;
   border-radius: 50%;
   width: 1em;
   height: 1em;
   vertical-align: text-top;
   -moz-margin-end: 5px;
+  display: inline-block;
 }
+
+.computedview-colorswatch::before {
+  content: '';
+  background-color: #eee;
+  background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+                    linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+  background-size: 12px 12px;
+  background-position: 0 0, 6px 6px;
+  position: absolute;
+  border-radius: 50%;
+  width: 1em;
+  height: 1em;
+  z-index: -1;
+}
--- a/build/mobile/sutagent/android/SUTAgentAndroid.java
+++ b/build/mobile/sutagent/android/SUTAgentAndroid.java
@@ -593,22 +593,18 @@ public class SUTAgentAndroid extends Act
 
         if (eap.contentEquals("peap"))
             {
             wc.eap.setValue("PEAP");
             wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
             wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
             }
 
-        wc.hiddenSSID = false;
         wc.status = WifiConfiguration.Status.ENABLED;
 
-        wc.password.setValue("\"password\"");
-        wc.identity.setValue("\"bmoss@mozilla.com\"");
-
         if (!wifi.isWifiEnabled())
             wifi.setWifiEnabled(true);
 
         while(wifi.getWifiState() != WifiManager.WIFI_STATE_ENABLED)
             {
             Thread.yield();
             if (++lcv > 10000)
                 return(bRet);
--- a/content/media/omx/OMXCodecWrapper.cpp
+++ b/content/media/omx/OMXCodecWrapper.cpp
@@ -446,16 +446,23 @@ OMXVideoEncoder::SetBitrate(int32_t aKbp
   msg->setInt32("videoBitrate", aKbps * 1000 /* kbps -> bps */);
   status_t result = mCodec->setParameters(msg);
   MOZ_ASSERT(result == OK);
   return result == OK ? NS_OK : NS_ERROR_FAILURE;
 }
 #endif
 
 nsresult
+OMXVideoEncoder::RequestIDRFrame()
+{
+  MOZ_ASSERT(mStarted, "Configure() should be called before RequestIDRFrame().");
+  return mCodec->requestIDRFrame() == OK ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
 OMXAudioEncoder::Configure(int aChannels, int aInputSampleRate,
                            int aEncodedSampleRate)
 {
   MOZ_ASSERT(!mStarted);
 
   NS_ENSURE_TRUE(aChannels > 0 && aInputSampleRate > 0 && aEncodedSampleRate >= 0,
                  NS_ERROR_INVALID_ARG);
 
--- a/content/media/omx/OMXCodecWrapper.h
+++ b/content/media/omx/OMXCodecWrapper.h
@@ -277,16 +277,22 @@ public:
 #endif
 
   /**
    * Get current AVC codec config blob. The output format depends on the
    * aBlobFormat argument given when Configure() was called.
    */
   nsresult GetCodecConfig(nsTArray<uint8_t>* aOutputBuf);
 
+  /**
+   * Ask codec to generate an instantaneous decoding refresh (IDR) frame
+   * (defined in ISO/IEC 14496-10).
+   */
+  nsresult RequestIDRFrame();
+
 protected:
   virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
                                        ABuffer* aData) MOZ_OVERRIDE;
 
   // If configured to output MP4 format blob, AVC/H.264 encoder has to replace
   // NAL unit start code with the unit length as specified in
   // ISO/IEC 14496-15 5.2.3.
   virtual void AppendFrame(nsTArray<uint8_t>* aOutputBuf,
--- a/dom/xbl/nsXBLMaybeCompiled.h
+++ b/dom/xbl/nsXBLMaybeCompiled.h
@@ -77,22 +77,27 @@ private:
 
   friend class js::GCMethods<nsXBLMaybeCompiled<UncompiledT> >;
 };
 
 /* Add support for JS::Heap<nsXBLMaybeCompiled>. */
 namespace js {
 
 template <class UncompiledT>
-struct GCMethods<nsXBLMaybeCompiled<UncompiledT> > : public GCMethods<JSObject *>
+struct GCMethods<nsXBLMaybeCompiled<UncompiledT> >
 {
   typedef struct GCMethods<JSObject *> Base;
 
   static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); }
 
+  /*
+   * No implementation of kind() is provided to prevent
+   * Root<nsXBLMaybeCompiled<UncompiledT>> from being used.
+   */
+
   static bool poisoned(nsXBLMaybeCompiled<UncompiledT> function)
   {
     return function.IsCompiled() && Base::poisoned(function.GetJSFunction());
   }
 
   static bool needsPostBarrier(nsXBLMaybeCompiled<UncompiledT> function)
   {
     return function.IsCompiled() && Base::needsPostBarrier(function.GetJSFunction());
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -272,22 +272,22 @@ struct AutoPaintSetup {
 
     // TODO: We could skip the temporary for operator_source and just
     // clear the clip rect. The other operators would be harder
     // but could be worth it to skip pushing a group.
     if (needsGroup) {
       mPaint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
       SkPaint temp;
       temp.setXfermodeMode(GfxOpToSkiaOp(aOptions.mCompositionOp));
-      temp.setAlpha(U8CPU(aOptions.mAlpha*255));
+      temp.setAlpha(U8CPU(aOptions.mAlpha*255+0.5));
       //TODO: Get a rect here
       mCanvas->saveLayer(nullptr, &temp);
       mNeedsRestore = true;
     } else {
-      mPaint.setAlpha(U8CPU(aOptions.mAlpha*255.0));
+      mPaint.setAlpha(U8CPU(aOptions.mAlpha*255.0+0.5));
       mAlpha = aOptions.mAlpha;
     }
     mPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
   }
 
   // TODO: Maybe add an operator overload to access this easier?
   SkPaint mPaint;
   TempBitmap mTmpBitmap;
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -21,100 +21,40 @@ APZCCallbackHelper::HasValidPresShellId(
     MOZ_ASSERT(aUtils);
 
     uint32_t presShellId;
     nsresult rv = aUtils->GetPresShellId(&presShellId);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
     return NS_SUCCEEDED(rv) && aMetrics.mPresShellId == presShellId;
 }
 
-/**
- * Expands a given rectangle to the next tile boundary. Note, this will
- * expand the rectangle if it is already on tile boundaries.
- */
-static CSSRect ExpandDisplayPortToTileBoundaries(
-  const CSSRect& aDisplayPort,
-  const CSSToLayerScale& aLayerPixelsPerCSSPixel)
+static void
+AdjustDisplayPortForScrollDelta(mozilla::layers::FrameMetrics& aFrameMetrics,
+                                const CSSPoint& aActualScrollOffset)
 {
-  // Convert the given rect to layer coordinates so we can inflate to tile
-  // boundaries (layer space corresponds to texture pixel space here).
-  LayerRect displayPortInLayerSpace = aDisplayPort * aLayerPixelsPerCSSPixel;
-
-  // Inflate the rectangle by 1 so that we always push to the next tile
-  // boundary. This is desirable to stop from having a rectangle with a
-  // moving origin occasionally being smaller when it coincidentally lines
-  // up to tile boundaries.
-  displayPortInLayerSpace.Inflate(1);
-
-  // Now nudge the rectangle to the nearest equal or larger tile boundary.
-  int32_t tileWidth = gfxPrefs::LayersTileWidth();
-  int32_t tileHeight = gfxPrefs::LayersTileHeight();
-  gfxFloat left = tileWidth * floor(displayPortInLayerSpace.x / tileWidth);
-  gfxFloat right = tileWidth * ceil(displayPortInLayerSpace.XMost() / tileWidth);
-  gfxFloat top = tileHeight * floor(displayPortInLayerSpace.y / tileHeight);
-  gfxFloat bottom = tileHeight * ceil(displayPortInLayerSpace.YMost() / tileHeight);
-
-  displayPortInLayerSpace = LayerRect(left, top, right - left, bottom - top);
-  CSSRect displayPort = displayPortInLayerSpace / aLayerPixelsPerCSSPixel;
-
-  return displayPort;
-}
-
-static void
-MaybeAlignAndClampDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics,
-                              const CSSPoint& aActualScrollOffset)
-{
-  // Correct the display-port by the difference between the requested scroll
-  // offset and the resulting scroll offset after setting the requested value.
-  if (!aFrameMetrics.GetUseDisplayPortMargins()) {
-      CSSRect& displayPort = aFrameMetrics.mDisplayPort;
-      displayPort += aFrameMetrics.GetScrollOffset() - aActualScrollOffset;
-
-      // Expand the display port to the next tile boundaries, if tiled thebes layers
-      // are enabled.
-      if (gfxPrefs::LayersTilesEnabled()) {
-        // We don't use LayersPixelsPerCSSPixel() here as mCumulativeResolution on
-        // this FrameMetrics may be incorrect (and is about to be reset by mZoom).
-        displayPort =
-          ExpandDisplayPortToTileBoundaries(displayPort + aActualScrollOffset,
-                                            aFrameMetrics.GetZoom() *
-                                            ScreenToLayerScale(1.0))
-          - aActualScrollOffset;
-      }
-
-      // Finally, clamp the display port to the expanded scrollable rect.
-      CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect();
-      displayPort = scrollableRect.Intersect(displayPort + aActualScrollOffset)
-        - aActualScrollOffset;
-  } else {
-      LayerPoint shift =
-          (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
-          aFrameMetrics.LayersPixelsPerCSSPixel();
-      LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
-      margins.left -= shift.x;
-      margins.right += shift.x;
-      margins.top -= shift.y;
-      margins.bottom += shift.y;
-      aFrameMetrics.SetDisplayPortMargins(margins);
-  }
+    // Correct the display-port by the difference between the requested scroll
+    // offset and the resulting scroll offset after setting the requested value.
+    LayerPoint shift =
+        (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
+        aFrameMetrics.LayersPixelsPerCSSPixel();
+    LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
+    margins.left -= shift.x;
+    margins.right += shift.x;
+    margins.top -= shift.y;
+    margins.bottom += shift.y;
+    aFrameMetrics.SetDisplayPortMargins(margins);
 }
 
 static void
 RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
 {
-    if (!aFrameMetrics.GetUseDisplayPortMargins()) {
-        CSSSize compositionSize = aFrameMetrics.CalculateCompositedSizeInCssPixels();
-        aFrameMetrics.mDisplayPort.x = (compositionSize.width - aFrameMetrics.mDisplayPort.width) / 2;
-        aFrameMetrics.mDisplayPort.y = (compositionSize.height - aFrameMetrics.mDisplayPort.height) / 2;
-    } else {
-        LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
-        margins.right = margins.left = margins.LeftRight() / 2;
-        margins.top = margins.bottom = margins.TopBottom() / 2;
-        aFrameMetrics.SetDisplayPortMargins(margins);
-    }
+    LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
+    margins.right = margins.left = margins.LeftRight() / 2;
+    margins.top = margins.bottom = margins.TopBottom() / 2;
+    aFrameMetrics.SetDisplayPortMargins(margins);
 }
 
 static CSSPoint
 ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint, bool& aSuccessOut)
 {
   aSuccessOut = false;
 
   if (!aFrame) {
@@ -156,16 +96,17 @@ ScrollFrameTo(nsIScrollableFrame* aFrame
 }
 
 void
 APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils,
                                     FrameMetrics& aMetrics)
 {
     // Precondition checks
     MOZ_ASSERT(aUtils);
+    MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
     if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
         return;
     }
 
     // Set the scroll port size, which determines the scroll range. For example if
     // a 500-pixel document is shown in a 100-pixel frame, the scroll port length would
     // be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent
     // overscroll). Note that if the content here was zoomed to 2x, the document would
@@ -175,31 +116,30 @@ APZCCallbackHelper::UpdateRootFrame(nsID
     CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels();
     aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height);
 
     // Scroll the window to the desired spot
     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
     bool scrollUpdated = false;
     CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
 
-    if (!scrollUpdated) {
-      // For whatever reason we couldn't update the scroll offset on the scroll frame,
-      // which means the data APZ used for its displayport calculation is stale. Fall
-      // back to a sane default behaviour. Note that we don't tile-align the recentered
-      // displayport because tile-alignment depends on the scroll position, and the
-      // scroll position here is out of our control. See bug 966507 comment 21 for a
-      // more detailed explanation.
-      RecenterDisplayPort(aMetrics);
+    if (scrollUpdated) {
+        // Correct the display port due to the difference between mScrollOffset and the
+        // actual scroll offset.
+        AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+    } else {
+        // For whatever reason we couldn't update the scroll offset on the scroll frame,
+        // which means the data APZ used for its displayport calculation is stale. Fall
+        // back to a sane default behaviour. Note that we don't tile-align the recentered
+        // displayport because tile-alignment depends on the scroll position, and the
+        // scroll position here is out of our control. See bug 966507 comment 21 for a
+        // more detailed explanation.
+        RecenterDisplayPort(aMetrics);
     }
 
-    // Correct the display port due to the difference between mScrollOffset and the
-    // actual scroll offset, possibly align it to tile boundaries (if tiled layers are
-    // enabled), and clamp it to the scrollable rect.
-    MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
-
     aMetrics.SetScrollOffset(actualScrollOffset);
 
     // The mZoom variable on the frame metrics stores the CSS-to-screen scale for this
     // frame. This scale includes all of the (cumulative) resolutions set on the presShells
     // from the root down to this frame. However, when setting the resolution, we only
     // want the piece of the resolution that corresponds to this presShell, rather than
     // all of the cumulative stuff, so we need to divide out the parent resolutions.
     // Finally, we multiply by a ScreenToLayerScale of 1.0f because the goal here is to
@@ -216,49 +156,43 @@ APZCCallbackHelper::UpdateRootFrame(nsID
     nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
     if (!content) {
         return;
     }
     nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content);
     if (!element) {
         return;
     }
-    if (!aMetrics.GetUseDisplayPortMargins()) {
-        aUtils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
-                                         aMetrics.mDisplayPort.y,
-                                         aMetrics.mDisplayPort.width,
-                                         aMetrics.mDisplayPort.height,
-                                         element, 0);
-    } else {
-        gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
-            ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
-              gfx::IntSize(0, 0);
-        LayerMargin margins = aMetrics.GetDisplayPortMargins();
-        aUtils->SetDisplayPortMarginsForElement(margins.left,
-                                                margins.top,
-                                                margins.right,
-                                                margins.bottom,
-                                                alignment.width,
-                                                alignment.height,
-                                                element, 0);
-        CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
-        nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
-                    baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
-                    baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
-                    baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
-        nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base);
-    }
+
+    gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
+        ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
+          gfx::IntSize(0, 0);
+    LayerMargin margins = aMetrics.GetDisplayPortMargins();
+    aUtils->SetDisplayPortMarginsForElement(margins.left,
+                                            margins.top,
+                                            margins.right,
+                                            margins.bottom,
+                                            alignment.width,
+                                            alignment.height,
+                                            element, 0);
+    CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
+    nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
+                baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
+                baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
+                baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
+    nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base);
 }
 
 void
 APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent,
                                    FrameMetrics& aMetrics)
 {
     // Precondition checks
     MOZ_ASSERT(aContent);
+    MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
     if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
         return;
     }
 
     nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent);
     if (!utils) {
         return;
     }
@@ -267,45 +201,38 @@ APZCCallbackHelper::UpdateSubFrame(nsICo
     // be scrolled, so here we only have to set the scroll position and displayport.
 
     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
     bool scrollUpdated = false;
     CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
 
     nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent);
     if (element) {
-        if (!scrollUpdated) {
+        if (scrollUpdated) {
+            AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+        } else {
             RecenterDisplayPort(aMetrics);
         }
-        MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
-        if (!aMetrics.GetUseDisplayPortMargins()) {
-            utils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
-                                            aMetrics.mDisplayPort.y,
-                                            aMetrics.mDisplayPort.width,
-                                            aMetrics.mDisplayPort.height,
-                                            element, 0);
-        } else {
-            gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
-                ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
-                  gfx::IntSize(0, 0);
-            LayerMargin margins = aMetrics.GetDisplayPortMargins();
-            utils->SetDisplayPortMarginsForElement(margins.left,
-                                                   margins.top,
-                                                   margins.right,
-                                                   margins.bottom,
-                                                   alignment.width,
-                                                   alignment.height,
-                                                   element, 0);
-            CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
-            nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
-                        baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
-                        baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
-                        baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
-            nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
-        }
+        gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
+            ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
+              gfx::IntSize(0, 0);
+        LayerMargin margins = aMetrics.GetDisplayPortMargins();
+        utils->SetDisplayPortMarginsForElement(margins.left,
+                                               margins.top,
+                                               margins.right,
+                                               margins.bottom,
+                                               alignment.width,
+                                               alignment.height,
+                                               element, 0);
+        CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
+        nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
+                    baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
+                    baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
+                    baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
+        nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
     }
 
     aMetrics.SetScrollOffset(actualScrollOffset);
 }
 
 already_AddRefed<nsIDOMWindowUtils>
 APZCCallbackHelper::GetDOMWindowUtils(const nsIDocument* aDoc)
 {
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -389,19 +389,16 @@ BasicCompositor::BeginFrame(const nsIntR
   // manager styling. We want to ignore that.
   intRect.MoveTo(0, 0);
   Rect rect = Rect(0, 0, intRect.width, intRect.height);
 
   // Sometimes the invalid region is larger than we want to draw.
   nsIntRegion invalidRegionSafe;
   invalidRegionSafe.And(aInvalidRegion, intRect);
 
-  // FIXME: Redraw the whole screen in every frame to work around bug 972728.
-  invalidRegionSafe = intRect;
-
   nsIntRect invalidRect = invalidRegionSafe.GetBounds();
   mInvalidRect = IntRect(invalidRect.x, invalidRect.y, invalidRect.width, invalidRect.height);
   mInvalidRegion = invalidRegionSafe;
 
   if (aRenderBoundsOut) {
     *aRenderBoundsOut = Rect();
   }
 
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -66,28 +66,27 @@ CanvasClient2D::Update(gfx::IntSize aSiz
                                                 : gfxContentType::COLOR_ALPHA;
     gfxImageFormat format
       = gfxPlatform::GetPlatform()->OptimalFormatForContent(contentType);
     TextureFlags flags = TextureFlags::DEFAULT;
     if (mTextureFlags & TextureFlags::NEEDS_Y_FLIP) {
       flags |= TextureFlags::NEEDS_Y_FLIP;
     }
 
+    gfx::SurfaceFormat surfaceFormat = gfx::ImageFormatToSurfaceFormat(format);
     if (aLayer->IsGLLayer()) {
       // We want a cairo backend here as we don't want to be copying into
       // an accelerated backend and we like LockBits to work. This is currently
       // the most effective way to make this work.
-      mBuffer = CreateBufferTextureClient(gfx::ImageFormatToSurfaceFormat(format),
-                                          flags,
-                                          BackendType::CAIRO);
+      mBuffer = CreateBufferTextureClient(surfaceFormat, flags, BackendType::CAIRO);
     } else {
-      mBuffer = CreateTextureClientForDrawing(gfx::ImageFormatToSurfaceFormat(format),
-        flags,
-        gfxPlatform::GetPlatform()->GetPreferredCanvasBackend(),
-        aSize);
+      // XXX - We should use CreateTextureClientForDrawing, but we first need
+      // to use double buffering.
+      mBuffer = CreateBufferTextureClient(surfaceFormat, flags,
+        gfxPlatform::GetPlatform()->GetPreferredCanvasBackend());
     }
     MOZ_ASSERT(mBuffer->CanExposeDrawTarget());
     mBuffer->AllocateForSurface(aSize);
 
     bufferCreated = true;
   }
 
   if (!mBuffer->Lock(OpenMode::OPEN_WRITE_ONLY)) {
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -134,16 +134,17 @@ class GCRuntime
     bool shouldPreserveJITCode(JSCompartment *comp, int64_t currentTime);
     bool drainMarkStack(SliceBudget &sliceBudget, gcstats::Phase phase);
     template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
     void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
     template <class ZoneIterT, class CompartmentIterT> void markGrayReferences();
     void markGrayReferencesInCurrentGroup();
     void beginSweepPhase(bool lastGC);
     void findZoneGroups();
+    bool findZoneEdgesForWeakMaps();
     void getNextZoneGroup();
     void endMarkingZoneGroup();
     void beginSweepingZoneGroup();
     bool releaseObservedTypes();
     void endSweepingZoneGroup();
     bool sweepPhase(SliceBudget &sliceBudget);
     void endSweepPhase(JSGCInvocationKind gckind, bool lastGC);
     void sweepZones(FreeOp *fop, bool lastGC);
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -58,16 +58,21 @@ Zone::~Zone()
     if (this == rt->gc.systemZone)
         rt->gc.systemZone = nullptr;
 
 #ifdef JS_ION
     js_delete(jitZone_);
 #endif
 }
 
+bool Zone::init()
+{
+    return gcZoneGroupEdges.init();
+}
+
 void
 Zone::setNeedsBarrier(bool needs, ShouldUpdateIon updateIon)
 {
 #ifdef JS_ION
     if (updateIon == UpdateIon && needs != ionUsingBarriers_) {
         jit::ToggleBarriers(this, needs);
         ionUsingBarriers_ = needs;
     }
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -3,16 +3,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/. */
 
 #ifndef gc_Zone_h
 #define gc_Zone_h
 
 #include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "jscntxt.h"
 #include "jsgc.h"
 #include "jsinfer.h"
 
 #include "gc/FindSCCs.h"
 
@@ -142,16 +143,17 @@ struct Zone : public JS::shadow::Zone,
         Sweep,
         Finished
     };
 
   private:
     bool                         gcScheduled;
     GCState                      gcState;
     bool                         gcPreserveCode;
+    mozilla::DebugOnly<unsigned> gcLastZoneGroupIndex;
 
   public:
     bool isCollecting() const {
         if (runtimeFromMainThread()->isHeapCollecting())
             return gcState != NoGC;
         else
             return needsBarrier();
     }
@@ -223,16 +225,26 @@ struct Zone : public JS::shadow::Zone,
     bool isGCSweeping() {
         return gcState == Sweep;
     }
 
     bool isGCFinished() {
         return gcState == Finished;
     }
 
+#ifdef DEBUG
+    /*
+     * For testing purposes, return the index of the zone group which this zone
+     * was swept in in the last GC.
+     */
+    unsigned lastZoneGroupIndex() {
+        return gcLastZoneGroupIndex;
+    }
+#endif
+
     /* This is updated by both the main and GC helper threads. */
     mozilla::Atomic<size_t, mozilla::ReleaseAcquire> gcBytes;
 
     size_t                       gcTriggerBytes;
     size_t                       gcMaxMallocBytes;
     double                       gcHeapGrowthFactor;
 
     bool                         isSystem;
@@ -267,22 +279,33 @@ struct Zone : public JS::shadow::Zone,
      * This should be a bool, but Atomic only supports 32-bit and pointer-sized
      * types.
      */
     mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> gcMallocGCTriggered;
 
     /* This compartment's gray roots. */
     js::Vector<js::GrayRoot, 0, js::SystemAllocPolicy> gcGrayRoots;
 
+    /*
+     * A set of edges from this zone to other zones.
+     *
+     * This is used during GC while calculating zone groups to record edges that
+     * can't be determined by examining this zone by itself.
+     */
+    typedef js::HashSet<Zone *, js::DefaultHasher<Zone *>, js::SystemAllocPolicy> ZoneSet;
+    ZoneSet gcZoneGroupEdges;
+
     /* Per-zone data for use by an embedder. */
     void *data;
 
     Zone(JSRuntime *rt);
     ~Zone();
 
+    bool init();
+
     void findOutgoingEdges(js::gc::ComponentFinder<JS::Zone> &finder);
 
     void discardJitCode(js::FreeOp *fop);
 
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 size_t *typePool,
                                 size_t *baselineStubsOptimized);
 
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -862,23 +862,26 @@ NativeRegExpMacroAssembler::LoadCurrentC
     LoadCurrentCharacterUnchecked(cp_offset, characters);
 }
 
 void
 NativeRegExpMacroAssembler::LoadCurrentCharacterUnchecked(int cp_offset, int characters)
 {
     IonSpew(SPEW_PREFIX "LoadCurrentCharacterUnchecked(%d, %d)", cp_offset, characters);
 
-    JS_ASSERT(characters == 1);
     if (mode_ == ASCII) {
         MOZ_ASSUME_UNREACHABLE("Ascii loading not implemented");
     } else {
         JS_ASSERT(mode_ == JSCHAR);
-        masm.load16ZeroExtend(BaseIndex(input_end_pointer, current_position, TimesOne, cp_offset * sizeof(jschar)),
-                              current_character);
+        JS_ASSERT(characters <= 2);
+        BaseIndex address(input_end_pointer, current_position, TimesOne, cp_offset * sizeof(jschar));
+        if (characters == 2)
+            masm.load32(address, current_character);
+        else
+            masm.load16ZeroExtend(address, current_character);
     }
 }
 
 void
 NativeRegExpMacroAssembler::PopCurrentPosition()
 {
     IonSpew(SPEW_PREFIX "PopCurrentPosition");
 
@@ -1230,19 +1233,17 @@ NativeRegExpMacroAssembler::CheckSpecial
       default:
         return false;
     }
 }
 
 bool
 NativeRegExpMacroAssembler::CanReadUnaligned()
 {
-    // XXX Bug 1006799 should this be enabled? Unaligned loads can be slow even
-    // on platforms where they are supported.
-    return false;
+    return true;
 }
 
 const uint8_t
 NativeRegExpMacroAssembler::word_character_map[] =
 {
     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -1987,36 +1987,21 @@ ICCompare_String::Compiler::generateStub
 
     JS_ASSERT(IsEqualityOp(op));
 
     Register left = masm.extractString(R0, ExtractTemp0);
     Register right = masm.extractString(R1, ExtractTemp1);
 
     GeneralRegisterSet regs(availableGeneralRegs(2));
     Register scratchReg = regs.takeAny();
-    // x86 doesn't have the luxury of a second scratch.
-    Register scratchReg2;
-    if (regs.empty()) {
-        scratchReg2 = BaselineStubReg;
-        masm.push(BaselineStubReg);
-    } else {
-        scratchReg2 = regs.takeAny();
-    }
-    JS_ASSERT(scratchReg2 != scratchReg);
-
-    Label inlineCompareFailed;
-    masm.compareStrings(op, left, right, scratchReg2, scratchReg, &inlineCompareFailed);
-    masm.tagValue(JSVAL_TYPE_BOOLEAN, scratchReg2, R0);
-    if (scratchReg2 == BaselineStubReg)
-        masm.pop(BaselineStubReg);
+
+    masm.compareStrings(op, left, right, scratchReg, &failure);
+    masm.tagValue(JSVAL_TYPE_BOOLEAN, scratchReg, R0);
     EmitReturnFromIC(masm);
 
-    masm.bind(&inlineCompareFailed);
-    if (scratchReg2 == BaselineStubReg)
-        masm.pop(BaselineStubReg);
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
 //
 // Compare_Boolean
 //
@@ -4219,20 +4204,20 @@ ICGetElemNativeCompiler::generateStubCod
     Register strExtract = masm.extractString(R1, ExtractTemp1);
 
     // If needsAtomize_ is true, and the string is not already an atom, then atomize the
     // string before proceeding.
     if (needsAtomize_) {
         Label skipAtomize;
 
         // If string is already an atom, skip the atomize.
-        masm.branchTestPtr(Assembler::NonZero,
-                           Address(strExtract, JSString::offsetOfLengthAndFlags()),
-                           Imm32(JSString::ATOM_BIT),
-                           &skipAtomize);
+        masm.branchTest32(Assembler::NonZero,
+                          Address(strExtract, JSString::offsetOfFlags()),
+                          Imm32(JSString::ATOM_BIT),
+                          &skipAtomize);
 
         // Stow R0.
         EmitStowICValues(masm, 1);
 
         enterStubFrame(masm, R0.scratchReg());
 
         // Atomize the string into a new value.
         masm.push(strExtract);
@@ -4424,33 +4409,28 @@ ICGetElem_String::Compiler::generateStub
     masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
 
     GeneralRegisterSet regs(availableGeneralRegs(2));
     Register scratchReg = regs.takeAny();
 
     // Unbox string in R0.
     Register str = masm.extractString(R0, ExtractTemp0);
 
-    // Load string lengthAndFlags
-    Address lengthAndFlagsAddr(str, JSString::offsetOfLengthAndFlags());
-    masm.loadPtr(lengthAndFlagsAddr, scratchReg);
-
     // Check for non-linear strings.
-    masm.branchTest32(Assembler::Zero, scratchReg, Imm32(JSString::FLAGS_MASK), &failure);
+    masm.branchIfRope(str, &failure);
 
     // Unbox key.
     Register key = masm.extractInt32(R1, ExtractTemp1);
 
-    // Extract length and bounds check.
-    masm.rshiftPtr(Imm32(JSString::LENGTH_SHIFT), scratchReg);
-    masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
+    // Bounds check.
+    masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()),
+                  key, &failure);
 
     // Get char code.
-    Address charsAddr(str, JSString::offsetOfChars());
-    masm.loadPtr(charsAddr, scratchReg);
+    masm.loadStringChars(str, scratchReg);
     masm.load16ZeroExtend(BaseIndex(scratchReg, key, TimesTwo, 0), scratchReg);
 
     // Check if char code >= UNIT_STATIC_LIMIT.
     masm.branch32(Assembler::AboveOrEqual, scratchReg, Imm32(StaticStrings::UNIT_STATIC_LIMIT),
                   &failure);
 
     // Load static string.
     masm.movePtr(ImmPtr(&cx->staticStrings().unitStaticTable), str);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4,16 +4,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 "jit/CodeGenerator.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/MathAlgorithms.h"
 
 #include "jslibmath.h"
 #include "jsmath.h"
 #include "jsnum.h"
 #include "jsprf.h"
 
 #include "builtin/Eval.h"
 #include "builtin/TypedObject.h"
@@ -3345,16 +3346,17 @@ CodeGenerator::emitDebugResultChecks(LIn
       case MIRType_Object:
       case MIRType_String:
         return emitObjectOrStringResultChecks(ins, mir);
       case MIRType_Value:
         return emitValueResultChecks(ins, mir);
       default:
         return true;
     }
+    return true;
 }
 #endif
 
 bool
 CodeGenerator::generateBody()
 {
     IonScriptCounts *counts = maybeCreateScriptCounts();
 
@@ -3607,32 +3609,118 @@ CodeGenerator::visitNewObjectVMCall(LNew
 
     if (ReturnReg != objReg)
         masm.movePtr(ReturnReg, objReg);
 
     restoreLive(lir);
     return true;
 }
 
+static bool
+ShouldInitFixedSlots(LInstruction *lir, JSObject *templateObj)
+{
+    // Look for StoreFixedSlot instructions following an object allocation
+    // that write to this object before a GC is triggered or this object is
+    // passed to a VM call. If all fixed slots will be initialized, the
+    // allocation code doesn't need to set the slots to |undefined|.
+
+    uint32_t nfixed = templateObj->numUsedFixedSlots();
+    if (nfixed == 0)
+        return false;
+
+    // Only optimize if all fixed slots are initially |undefined|, so that we
+    // can assume incremental pre-barriers are not necessary. See also the
+    // comment below.
+    for (uint32_t slot = 0; slot < nfixed; slot++) {
+        if (!templateObj->getSlot(slot).isUndefined())
+            return true;
+    }
+
+    // Keep track of the fixed slots that are initialized. initializedSlots is
+    // a bit mask with a bit for each slot.
+    MOZ_ASSERT(nfixed <= JSObject::MAX_FIXED_SLOTS);
+    static_assert(JSObject::MAX_FIXED_SLOTS <= 32, "Slot bits must fit in 32 bits");
+    uint32_t initializedSlots = 0;
+    uint32_t numInitialized = 0;
+
+    MInstruction *allocMir = lir->mirRaw()->toInstruction();
+    MBasicBlock *block = allocMir->block();
+
+    // Skip the allocation instruction.
+    MInstructionIterator iter = block->begin(allocMir);
+    MOZ_ASSERT(*iter == allocMir);
+    iter++;
+
+    while (true) {
+        for (; iter != block->end(); iter++) {
+            if (iter->isNop() || iter->isConstant() || iter->isPostWriteBarrier()) {
+                // These instructions won't trigger a GC or read object slots.
+                continue;
+            }
+
+            if (iter->isStoreFixedSlot()) {
+                MStoreFixedSlot *store = iter->toStoreFixedSlot();
+                if (store->object() != allocMir)
+                    return true;
+
+                // We may not initialize this object slot on allocation, so the
+                // pre-barrier could read uninitialized memory. Simply disable
+                // the barrier for this store: the object was just initialized
+                // so the barrier is not necessary.
+                store->setNeedsBarrier(false);
+
+                uint32_t slot = store->slot();
+                MOZ_ASSERT(slot < nfixed);
+                if ((initializedSlots & (1 << slot)) == 0) {
+                    numInitialized++;
+                    initializedSlots |= (1 << slot);
+
+                    if (numInitialized == nfixed) {
+                        // All fixed slots will be initialized.
+                        MOZ_ASSERT(mozilla::CountPopulation32(initializedSlots) == nfixed);
+                        return false;
+                    }
+                }
+                continue;
+            }
+
+            if (iter->isGoto()) {
+                block = iter->toGoto()->target();
+                if (block->numPredecessors() != 1)
+                    return true;
+                break;
+            }
+
+            // Unhandled instruction, assume it bails or reads object slots.
+            return true;
+        }
+        iter = block->begin();
+    }
+
+    MOZ_ASSUME_UNREACHABLE("Shouldn't get here");
+}
+
 bool
 CodeGenerator::visitNewObject(LNewObject *lir)
 {
     JS_ASSERT(gen->info().executionMode() == SequentialExecution);
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
     JSObject *templateObject = lir->mir()->templateObject();
 
     if (lir->mir()->shouldUseVM())
         return visitNewObjectVMCall(lir);
 
     OutOfLineNewObject *ool = new(alloc()) OutOfLineNewObject(lir);
     if (!addOutOfLineCode(ool))
         return false;
 
-    masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry());
+    bool initFixedSlots = ShouldInitFixedSlots(lir, templateObject);
+    masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry(),
+                        initFixedSlots);
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 bool
 CodeGenerator::visitOutOfLineNewObject(OutOfLineNewObject *ool)
 {
@@ -3657,17 +3745,19 @@ CodeGenerator::visitNewDeclEnvObject(LNe
     // If we have a template object, we can inline call object creation.
     OutOfLineCode *ool = oolCallVM(NewDeclEnvObjectInfo, lir,
                                    (ArgList(), ImmGCPtr(info.funMaybeLazy()),
                                     Imm32(gc::DefaultHeap)),
                                    StoreRegisterTo(objReg));
     if (!ool)
         return false;
 
-    masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry());
+    bool initFixedSlots = ShouldInitFixedSlots(lir, templateObj);
+    masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry(),
+                        initFixedSlots);
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleShape, HandleTypeObject);
 static const VMFunction NewCallObjectInfo =
     FunctionInfo<NewCallObjectFn>(NewCallObject);
@@ -3683,17 +3773,19 @@ CodeGenerator::visitNewCallObject(LNewCa
     OutOfLineCode *ool = oolCallVM(NewCallObjectInfo, lir,
                                    (ArgList(), ImmGCPtr(templateObj->lastProperty()),
                                                ImmGCPtr(templateObj->type())),
                                    StoreRegisterTo(objReg));
     if (!ool)
         return false;
 
     // Inline call object creation, using the OOL path only for tricky cases.
-    masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry());
+    bool initFixedSlots = ShouldInitFixedSlots(lir, templateObj);
+    masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry(),
+                        initFixedSlots);
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 typedef JSObject *(*NewSingletonCallObjectFn)(JSContext *, HandleShape);
 static const VMFunction NewSingletonCallObjectInfo =
     FunctionInfo<NewSingletonCallObjectFn>(NewSingletonCallObject);
@@ -4033,17 +4125,19 @@ CodeGenerator::visitCreateThisWithTempla
     if (!ool)
         return false;
 
     // Allocate. If the FreeList is empty, call to VM, which may GC.
     masm.newGCThing(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry());
 
     // Initialize based on the templateObject.
     masm.bind(ool->rejoin());
-    masm.initGCThing(objReg, tempReg, templateObject);
+
+    bool initFixedSlots = ShouldInitFixedSlots(lir, templateObject);
+    masm.initGCThing(objReg, tempReg, templateObject, initFixedSlots);
 
     return true;
 }
 
 typedef JSObject *(*NewIonArgumentsObjectFn)(JSContext *cx, IonJSFrameLayout *frame, HandleObject);
 static const VMFunction NewIonArgumentsObjectInfo =
     FunctionInfo<NewIonArgumentsObjectFn>((NewIonArgumentsObjectFn) ArgumentsObject::createForIon);
 
@@ -4575,75 +4669,73 @@ static const VMFunctionsModal StringsEqu
     FunctionInfo<StringCompareFn>(jit::StringsEqual<true>),
     FunctionInfo<StringCompareParFn>(jit::StringsEqualPar));
 static const VMFunctionsModal StringsNotEqualInfo = VMFunctionsModal(
     FunctionInfo<StringCompareFn>(jit::StringsEqual<false>),
     FunctionInfo<StringCompareParFn>(jit::StringsUnequalPar));
 
 bool
 CodeGenerator::emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
-                            Register output, Register temp)
+                            Register output)
 {
     JS_ASSERT(lir->isCompareS() || lir->isCompareStrictS());
 
     OutOfLineCode *ool = nullptr;
 
     if (op == JSOP_EQ || op == JSOP_STRICTEQ) {
         ool = oolCallVM(StringsEqualInfo, lir, (ArgList(), left, right),  StoreRegisterTo(output));
     } else {
         JS_ASSERT(op == JSOP_NE || op == JSOP_STRICTNE);
         ool = oolCallVM(StringsNotEqualInfo, lir, (ArgList(), left, right), StoreRegisterTo(output));
     }
     if (!ool)
         return false;
 
-    masm.compareStrings(op, left, right, output, temp, ool->entry());
+    masm.compareStrings(op, left, right, output, ool->entry());
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 bool
 CodeGenerator::visitCompareStrictS(LCompareStrictS *lir)
 {
     JSOp op = lir->mir()->jsop();
     JS_ASSERT(op == JSOP_STRICTEQ || op == JSOP_STRICTNE);
 
     const ValueOperand leftV = ToValue(lir, LCompareStrictS::Lhs);
     Register right = ToRegister(lir->right());
     Register output = ToRegister(lir->output());
-    Register temp = ToRegister(lir->temp());
     Register tempToUnbox = ToTempUnboxRegister(lir->tempToUnbox());
 
     Label string, done;
 
     masm.branchTestString(Assembler::Equal, leftV, &string);
     masm.move32(Imm32(op == JSOP_STRICTNE), output);
     masm.jump(&done);
 
     masm.bind(&string);
     Register left = masm.extractString(leftV, tempToUnbox);
-    if (!emitCompareS(lir, op, left, right, output, temp))
+    if (!emitCompareS(lir, op, left, right, output))
         return false;
 
     masm.bind(&done);
 
     return true;
 }
 
 bool
 CodeGenerator::visitCompareS(LCompareS *lir)
 {
     JSOp op = lir->mir()->jsop();
     Register left = ToRegister(lir->left());
     Register right = ToRegister(lir->right());
     Register output = ToRegister(lir->output());
-    Register temp = ToRegister(lir->temp());
-
-    return emitCompareS(lir, op, left, right, output, temp);
+
+    return emitCompareS(lir, op, left, right, output);
 }
 
 typedef bool (*CompareFn)(JSContext *, MutableHandleValue, MutableHandleValue, bool *);
 typedef bool (*CompareParFn)(ForkJoinContext *, MutableHandleValue, MutableHandleValue, bool *);
 static const VMFunctionsModal EqInfo = VMFunctionsModal(
     FunctionInfo<CompareFn>(jit::LooselyEqual<true>),
     FunctionInfo<CompareParFn>(jit::LooselyEqualPar));
 static const VMFunctionsModal NeInfo = VMFunctionsModal(
@@ -5061,19 +5153,18 @@ JitCompartment::generateStringConcatStub
         masm.pop(temp2);
         masm.pop(temp1);
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("No such execution mode");
     }
 
     // Store lengthAndFlags.
-    JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
-    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
-    masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
+    masm.store32(Imm32(JSString::ROPE_FLAGS), Address(output, JSString::offsetOfFlags()));
+    masm.store32(temp2, Address(output, JSString::offsetOfLength()));
 
     // Store left and right nodes.
     masm.storePtr(lhs, Address(output, JSRope::offsetOfLeft()));
     masm.storePtr(rhs, Address(output, JSRope::offsetOfRight()));
     masm.ret();
 
     masm.bind(&leftEmpty);
     masm.mov(rhs, output);
@@ -5082,22 +5173,19 @@ JitCompartment::generateStringConcatStub
     masm.bind(&rightEmpty);
     masm.mov(lhs, output);
     masm.ret();
 
     masm.bind(&isFatInline);
 
     // State: lhs length in temp1, result length in temp2.
 
-    // Ensure both strings are linear (flags != 0).
-    JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
-    masm.branchTestPtr(Assembler::Zero, Address(lhs, JSString::offsetOfLengthAndFlags()),
-                       Imm32(JSString::FLAGS_MASK), &failure);
-    masm.branchTestPtr(Assembler::Zero, Address(rhs, JSString::offsetOfLengthAndFlags()),
-                       Imm32(JSString::FLAGS_MASK), &failure);
+    // Ensure both strings are linear.
+    masm.branchIfRope(lhs, &failure);
+    masm.branchIfRope(rhs, &failure);
 
     // Allocate a JSFatInlineString.
     switch (mode) {
       case SequentialExecution:
         masm.newGCFatInlineString(output, temp3, &failure);
         break;
       case ParallelExecution:
         masm.push(temp1);
@@ -5105,41 +5193,39 @@ JitCompartment::generateStringConcatStub
         masm.newGCFatInlineStringPar(output, forkJoinContext, temp1, temp2, &failurePopTemps);
         masm.pop(temp2);
         masm.pop(temp1);
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("No such execution mode");
     }
 
-    // Set lengthAndFlags.
-    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
-    masm.orPtr(Imm32(JSString::FIXED_FLAGS), temp2);
-    masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
-
-    // Set chars pointer, keep in temp2 for copy loop below.
-    masm.computeEffectiveAddress(Address(output, JSFatInlineString::offsetOfInlineStorage()), temp2);
-    masm.storePtr(temp2, Address(output, JSFatInlineString::offsetOfChars()));
+    // Set length and flags.
+    masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS), Address(output, JSString::offsetOfFlags()));
+    masm.store32(temp2, Address(output, JSString::offsetOfLength()));
+
+    // Load chars pointer in temp2.
+    masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), temp2);
 
     {
         // We use temp3 in this block, which in parallel execution also holds
         // a live ForkJoinContext pointer. If we are compiling for parallel
         // execution, be sure to save and restore the ForkJoinContext.
         if (mode == ParallelExecution)
             masm.push(temp3);
 
         // Copy lhs chars. Temp1 still holds the lhs length. Note that this
         // advances temp2 to point to the next char. Note that this also
         // repurposes the lhs register.
-        masm.loadPtr(Address(lhs, JSString::offsetOfChars()), lhs);
+        masm.loadStringChars(lhs, lhs);
         CopyStringChars(masm, temp2, lhs, temp1, temp3);
 
         // Copy rhs chars.
         masm.loadStringLength(rhs, temp1);
-        masm.loadPtr(Address(rhs, JSString::offsetOfChars()), rhs);
+        masm.loadStringChars(rhs, rhs);
         CopyStringChars(masm, temp2, rhs, temp1, temp3);
 
         if (mode == ParallelExecution)
             masm.pop(temp3);
     }
 
     // Null-terminate.
     masm.store16(Imm32(0), Address(temp2, 0));
@@ -5236,22 +5322,19 @@ CodeGenerator::visitCharCodeAt(LCharCode
     Register str = ToRegister(lir->str());
     Register index = ToRegister(lir->index());
     Register output = ToRegister(lir->output());
 
     OutOfLineCode *ool = oolCallVM(CharCodeAtInfo, lir, (ArgList(), str, index), StoreRegisterTo(output));
     if (!ool)
         return false;
 
-    Address lengthAndFlagsAddr(str, JSString::offsetOfLengthAndFlags());
-    masm.branchTest32(Assembler::Zero, lengthAndFlagsAddr, Imm32(JSString::FLAGS_MASK), ool->entry());
-
-    // getChars
-    Address charsAddr(str, JSString::offsetOfChars());
-    masm.loadPtr(charsAddr, output);
+    masm.branchIfRope(str, ool->entry());
+
+    masm.loadStringChars(str, output);
     masm.load16ZeroExtend(BaseIndex(output, index, TimesTwo, 0), output);
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 typedef JSFlatString *(*StringFromCharCodeFn)(JSContext *, int32_t);
 static const VMFunction StringFromCharCodeInfo = FunctionInfo<StringFromCharCodeFn>(jit::StringFromCharCode);
@@ -6440,20 +6523,28 @@ CodeGenerator::link(JSContext *cx, types
                         /* resetUses */ false, /* cancelOffThread*/ false))
         {
             return false;
         }
     }
 
     // Check to make sure we didn't have a mid-build invalidation. If so, we
     // will trickle to jit::Compile() and return Method_Skipped.
+    uint32_t useCount = script->getUseCount();
     types::RecompileInfo recompileInfo;
     if (!types::FinishCompilation(cx, script, executionMode, constraints, &recompileInfo))
         return true;
 
+    // IonMonkey could have inferred better type information during
+    // compilation. Since adding the new information to the actual type
+    // information can reset the usecount, increase it back to what it was
+    // before.
+    if (useCount > script->getUseCount())
+        script->incUseCount(useCount - script->getUseCount());
+
     uint32_t scriptFrameSize = frameClass_ == FrameSizeClass::None()
                            ? frameDepth_
                            : FrameSizeClass::FromDepth(frameDepth_).frameSize();
 
     // We encode safepoints after the OSI-point offsets have been determined.
     encodeSafepoints();
 
     // List of possible scripts that this graph may call. Currently this is
@@ -8574,28 +8665,41 @@ CodeGenerator::visitAssertRangeV(LAssert
     masm.assumeUnreachable("Incorrect range for Value.");
     masm.bind(&done);
     return true;
 }
 
 typedef bool (*RecompileFn)(JSContext *);
 static const VMFunction RecompileFnInfo = FunctionInfo<RecompileFn>(Recompile);
 
+typedef bool (*ForcedRecompileFn)(JSContext *);
+static const VMFunction ForcedRecompileFnInfo = FunctionInfo<ForcedRecompileFn>(ForcedRecompile);
+
 bool
 CodeGenerator::visitRecompileCheck(LRecompileCheck *ins)
 {
     Label done;
     Register tmp = ToRegister(ins->scratch());
-    OutOfLineCode *ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
+    OutOfLineCode *ool;
+    if (ins->mir()->forceRecompilation())
+        ool = oolCallVM(ForcedRecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
+    else
+        ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
 
     // Check if usecount is high enough.
-    masm.movePtr(ImmPtr(ins->mir()->script()->addressOfUseCount()), tmp);
-    Address ptr(tmp, 0);
-    masm.add32(Imm32(1), tmp);
-    masm.branch32(Assembler::BelowOrEqual, ptr, Imm32(ins->mir()->recompileThreshold()), &done);
+    AbsoluteAddress useCount = AbsoluteAddress(ins->mir()->script()->addressOfUseCount());
+    if (ins->mir()->increaseUseCount()) {
+        masm.load32(useCount, tmp);
+        masm.add32(Imm32(1), tmp);
+        masm.store32(tmp, useCount);
+        masm.branch32(Assembler::BelowOrEqual, tmp, Imm32(ins->mir()->recompileThreshold()), &done);
+    } else {
+        masm.branch32(Assembler::BelowOrEqual, useCount, Imm32(ins->mir()->recompileThreshold()),
+                      &done);
+    }
 
     // Check if not yet recompiling.
     CodeOffsetLabel label = masm.movWithPatch(ImmWord(uintptr_t(-1)), tmp);
     if (!ionScriptLabels_.append(label))
         return false;
     masm.branch32(Assembler::Equal,
                   Address(tmp, IonScript::offsetOfRecompiling()),
                   Imm32(0),
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -198,18 +198,17 @@ class CodeGenerator : public CodeGenerat
     bool visitPowI(LPowI *lir);
     bool visitPowD(LPowD *lir);
     bool visitRandom(LRandom *lir);
     bool visitMathFunctionD(LMathFunctionD *ins);
     bool visitMathFunctionF(LMathFunctionF *ins);
     bool visitModD(LModD *ins);
     bool visitMinMaxI(LMinMaxI *lir);
     bool visitBinaryV(LBinaryV *lir);
-    bool emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
-                      Register output, Register temp);
+    bool emitCompareS(LInstruction *lir, JSOp op, Register left, Register right, Register output);
     bool visitCompareS(LCompareS *lir);
     bool visitCompareStrictS(LCompareStrictS *lir);
     bool visitCompareVM(LCompareVM *lir);
     bool visitIsNullOrLikeUndefined(LIsNullOrLikeUndefined *lir);
     bool visitIsNullOrLikeUndefinedAndBranch(LIsNullOrLikeUndefinedAndBranch *lir);
     bool visitEmulatesUndefined(LEmulatesUndefined *lir);
     bool visitEmulatesUndefinedAndBranch(LEmulatesUndefinedAndBranch *lir);
     bool emitConcat(LInstruction *lir, Register lhs, Register rhs, Register output);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1510,16 +1510,29 @@ OptimizeMIR(MIRGenerator *mir)
             return false;
         IonSpewPass("DCE");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("DCE"))
             return false;
     }
 
+    // Make loops contiguious. We do this after GVN/UCE and range analysis,
+    // which can remove CFG edges, exposing more blocks that can be moved.
+    {
+        AutoTraceLog log(logger, TraceLogger::MakeLoopsContiguous);
+        if (!MakeLoopsContiguous(graph))
+            return false;
+        IonSpewPass("Make loops contiguous");
+        AssertExtendedGraphCoherency(graph);
+
+        if (mir->shouldCancel("Make loops contiguous"))
+            return false;
+    }
+
     // Passes after this point must not move instructions; these analyses
     // depend on knowing the final order in which instructions will execute.
 
     if (mir->optimizationInfo().edgeCaseAnalysisEnabled()) {
         AutoTraceLog log(logger, TraceLogger::EdgeCaseAnalysis);
         EdgeCaseAnalysis edgeCaseAnalysis(mir, graph);
         if (!edgeCaseAnalysis.analyzeLate())
             return false;
@@ -2059,17 +2072,17 @@ GetOptimizationLevel(HandleScript script
 
     JS_ASSERT(executionMode == SequentialExecution);
 
     return js_IonOptimizations.levelForScript(script, pc);
 }
 
 static MethodStatus
 Compile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
-        bool constructing, ExecutionMode executionMode)
+        bool constructing, ExecutionMode executionMode, bool forceRecompile = false)
 {
     JS_ASSERT(jit::IsIonEnabled(cx));
     JS_ASSERT(jit::IsBaselineEnabled(cx));
     JS_ASSERT_IF(osrPc != nullptr, LoopEntryCanIonOsr(osrPc));
     JS_ASSERT_IF(executionMode == ParallelExecution, !osrFrame && !osrPc);
     JS_ASSERT_IF(executionMode == ParallelExecution, !HasIonScript(script, executionMode));
 
     if (!script->hasBaselineScript())
@@ -2096,45 +2109,27 @@ Compile(JSContext *cx, HandleScript scri
     if (optimizationLevel == Optimization_DontCompile)
         return Method_Skipped;
 
     IonScript *scriptIon = GetIonScript(script, executionMode);
     if (scriptIon) {
         if (!scriptIon->method())
             return Method_CantCompile;
 
-        MethodStatus failedState = Method_Compiled;
-
-        // If we keep failing to enter the script due to an OSR pc mismatch,
-        // recompile with the right pc.
-        if (osrPc && script->ionScript()->osrPc() != osrPc) {
-            uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
-            if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
-                return Method_Skipped;
-
-            failedState = Method_Skipped;
-        }
-
         // Don't recompile/overwrite higher optimized code,
         // with a lower optimization level.
-        if (optimizationLevel < scriptIon->optimizationLevel())
-            return failedState;
-
-        if (optimizationLevel == scriptIon->optimizationLevel() &&
-            (!osrPc || script->ionScript()->osrPc() == osrPc))
-        {
-            return failedState;
-        }
+        if (optimizationLevel <= scriptIon->optimizationLevel() && !forceRecompile)
+            return Method_Compiled;
 
         // Don't start compiling if already compiling
         if (scriptIon->isRecompiling())
-            return failedState;
+            return Method_Compiled;
 
         if (osrPc)
-            script->ionScript()->resetOsrPcMismatchCounter();
+            scriptIon->resetOsrPcMismatchCounter();
 
         recompile = true;
     }
 
     AbortReason reason = IonCompile(cx, script, osrFrame, osrPc, constructing, executionMode,
                                     recompile, optimizationLevel);
     if (reason == AbortReason_Error)
         return Method_Error;
@@ -2143,21 +2138,18 @@ Compile(JSContext *cx, HandleScript scri
         return Method_CantCompile;
 
     if (reason == AbortReason_Alloc) {
         js_ReportOutOfMemory(cx);
         return Method_Error;
     }
 
     // Compilation succeeded or we invalidated right away or an inlining/alloc abort
-    if (HasIonScript(script, executionMode)) {
-        if (osrPc && script->ionScript()->osrPc() != osrPc)
-            return Method_Skipped;
+    if (HasIonScript(script, executionMode))
         return Method_Compiled;
-    }
     return Method_Skipped;
 }
 
 } // namespace jit
 } // namespace js
 
 // Decide if a transition from interpreter execution to Ion code should occur.
 // May compile or recompile the target JSScript.
@@ -2186,29 +2178,45 @@ jit::CanEnterAtBranch(JSContext *cx, JSS
         return Method_Skipped;
 
     // Mark as forbidden if frame can't be handled.
     if (!CheckFrame(osrFrame)) {
         ForbidCompilation(cx, script);
         return Method_CantCompile;
     }
 
+    // By default a recompilation doesn't happen on osr mismatch.
+    // Decide if we want to force a recompilation if this happens too much.
+    bool force = false;
+    if (script->hasIonScript() && pc != script->ionScript()->osrPc()) {
+        uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
+        if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
+            return Method_Skipped;
+        force = true;
+    }
+
     // Attempt compilation.
     // - Returns Method_Compiled if the right ionscript is present
     //   (Meaning it was present or a sequantial compile finished)
-    // - Returns Method_Skipped if pc doesn't match
-    //   (This means a background thread compilation with that pc could have started or not.)
     RootedScript rscript(cx, script);
-    MethodStatus status = Compile(cx, rscript, osrFrame, pc, isConstructing, SequentialExecution);
+    MethodStatus status =
+        Compile(cx, rscript, osrFrame, pc, isConstructing, SequentialExecution, force);
     if (status != Method_Compiled) {
         if (status == Method_CantCompile)
             ForbidCompilation(cx, script);
         return status;
     }
 
+    // Return the compilation was skipped when the osr pc wasn't adjusted.
+    // This can happen when there was still an IonScript available and a
+    // background compilation started, but hasn't finished yet.
+    // Or when we didn't force a recompile.
+    if (pc != script->ionScript()->osrPc())
+        return Method_Skipped;
+
     return Method_Compiled;
 }
 
 MethodStatus
 jit::CanEnter(JSContext *cx, RunState &state)
 {
     JS_ASSERT(jit::IsIonEnabled(cx));
 
@@ -2309,24 +2317,24 @@ jit::CompileFunctionForBaseline(JSContex
         return status;
     }
 
     return Method_Compiled;
 }
 
 MethodStatus
 jit::Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
-               bool constructing)
+               bool constructing, bool force)
 {
     JS_ASSERT(script->hasIonScript());
     if (script->ionScript()->isRecompiling())
         return Method_Compiled;
 
     MethodStatus status =
-        Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution);
+        Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution, force);
     if (status != Method_Compiled) {
         if (status == Method_CantCompile)
             ForbidCompilation(cx, script);
         return status;
     }
 
     return Method_Compiled;
 }
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -90,17 +90,17 @@ MethodStatus CanEnter(JSContext *cx, Run
 MethodStatus CompileFunctionForBaseline(JSContext *cx, HandleScript script, BaselineFrame *frame,
                                         bool isConstructing);
 MethodStatus CanEnterUsingFastInvoke(JSContext *cx, HandleScript script, uint32_t numActualArgs);
 
 MethodStatus CanEnterInParallel(JSContext *cx, HandleScript script);
 
 MethodStatus
 Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
-          bool constructing);
+          bool constructing, bool force);
 
 enum IonExecStatus
 {
     // The method call had to be aborted due to a stack limit check. This
     // error indicates that Ion never attempted to clean up frames.
     IonExec_Aborted,
 
     // The method call resulted in an error, and IonMonkey has cleaned up
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2558,8 +2558,101 @@ jit::AnalyzeArgumentsUsage(JSContext *cx
     // arguments. The compiler can then assume that accesses through
     // arguments[i] will be on unaliased variables.
     if (script->funHasAnyAliasedFormal() && argumentsContentsObserved)
         return true;
 
     script->setNeedsArgsObj(false);
     return true;
 }
+
+// Reorder the blocks in the loop starting at the given header to be contiguous.
+static void
+MakeLoopContiguous(MIRGraph &graph, MBasicBlock *header, MBasicBlock *backedge, size_t numMarked)
+{
+    MOZ_ASSERT(header->isMarked(), "Loop header is not part of loop");
+    MOZ_ASSERT(backedge->isMarked(), "Loop backedge is not part of loop");
+
+    // If there are any blocks between the loop header and the loop backedge
+    // that are not part of the loop, prepare to move them to the end. We keep
+    // them in order, which preserves RPO.
+    ReversePostorderIterator insertIter = graph.rpoBegin(backedge);
+    insertIter++;
+    MBasicBlock *insertPt = *insertIter;
+
+    // Visit all the blocks from the loop header to the loop backedge.
+    size_t headerId = header->id();
+    size_t inLoopId = headerId;
+    size_t afterLoopId = inLoopId + numMarked;
+    ReversePostorderIterator i = graph.rpoBegin(header);
+    for (;;) {
+        MBasicBlock *block = *i++;
+        MOZ_ASSERT(block->id() >= header->id() && block->id() <= backedge->id(),
+                   "Loop backedge should be last block in loop");
+
+        if (block->isMarked()) {
+            // This block is in the loop.
+            block->unmark();
+            block->setId(inLoopId++);
+            // If we've reached the loop backedge, we're done!
+            if (block == backedge)
+                break;
+        } else {
+            // This block is not in the loop. Move it to the end.
+            graph.moveBlockBefore(insertPt, block);
+            block->setId(afterLoopId++);
+        }
+    }
+    MOZ_ASSERT(header->id() == headerId, "Loop header id changed");
+    MOZ_ASSERT(inLoopId == headerId + numMarked, "Wrong number of blocks kept in loop");
+    MOZ_ASSERT(afterLoopId == (insertIter != graph.rpoEnd() ? insertPt->id() : graph.numBlocks()),
+               "Wrong number of blocks moved out of loop");
+}
+
+// Reorder the blocks in the graph so that loops are contiguous.
+bool
+jit::MakeLoopsContiguous(MIRGraph &graph)
+{
+    MBasicBlock *osrBlock = graph.osrBlock();
+    Vector<MBasicBlock *, 1, IonAllocPolicy> inlooplist(graph.alloc());
+
+    // Visit all loop headers (in any order).
+    for (MBasicBlockIterator i(graph.begin()); i != graph.end(); i++) {
+        MBasicBlock *header = *i;
+        if (!header->isLoopHeader())
+            continue;
+
+        // Mark all the blocks in the loop by marking all blocks in a path
+        // between the backedge and the loop header.
+        MBasicBlock *backedge = header->backedge();
+        size_t numMarked = 1;
+        backedge->mark();
+        if (!inlooplist.append(backedge))
+            return false;
+        do {
+            MBasicBlock *block = inlooplist.popCopy();
+            MOZ_ASSERT(block->id() >= header->id() && block->id() <= backedge->id(),
+                       "Non-OSR predecessor of loop block not between header and backedge");
+            if (block == header)
+                continue;
+            for (size_t p = 0; p < block->numPredecessors(); p++) {
+                MBasicBlock *pred = block->getPredecessor(p);
+                if (pred->isMarked())
+                    continue;
+                // Ignore paths entering the loop in the middle from an OSR
+                // entry. They won't pass through the loop header and they
+                // aren't part of the loop.
+                if (osrBlock && osrBlock->dominates(pred) && !osrBlock->dominates(header))
+                    continue;
+                ++numMarked;
+                pred->mark();
+                if (!inlooplist.append(pred))
+                    return false;
+            }
+        } while (!inlooplist.empty());
+
+        // Move all blocks between header and backedge that aren't marked to
+        // the end of the loop, making the loop itself contiguous.
+        MakeLoopContiguous(graph, header, backedge, numMarked);
+    }
+
+    return true;
+}
--- a/js/src/jit/IonAnalysis.h
+++ b/js/src/jit/IonAnalysis.h
@@ -27,16 +27,19 @@ enum Observability {
     ConservativeObservability,
     AggressiveObservability
 };
 
 bool
 EliminatePhis(MIRGenerator *mir, MIRGraph &graph, Observability observe);
 
 bool
+MakeLoopsContiguous(MIRGraph &graph);
+
+bool
 EliminateDeadResumePointOperands(MIRGenerator *mir, MIRGraph &graph);
 
 bool
 EliminateDeadCode(MIRGenerator *mir, MIRGraph &graph);
 
 bool
 ApplyTypeInformation(MIRGenerator *mir, MIRGraph &graph);
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -4171,17 +4171,19 @@ IonBuilder::makeInliningDecision(JSFunct
             return DontInline(targetScript, "Vetoed: callee excessively large");
 
         // Callee must have been called a few times to have somewhat stable
         // type information, except for definite properties analysis,
         // as the caller has not run yet.
         if (targetScript->getUseCount() < optimizationInfo().usesBeforeInlining() &&
             info().executionMode() != DefinitePropertiesAnalysis)
         {
-            return DontInline(targetScript, "Vetoed: callee is insufficiently hot.");
+            IonSpew(IonSpew_Inlining, "Cannot inline %s:%u: callee is insufficiently hot.",
+                    targetScript->filename(), targetScript->lineno());
+            return InliningDecision_UseCountTooLow;
         }
     }
 
     // TI calls ObjectStateChange to trigger invalidation of the caller.
     types::TypeObjectKey *targetType = types::TypeObjectKey::get(target);
     targetType->watchStateChangeForInlinedCall(constraints());
 
     return InliningDecision_Inline;
@@ -4206,16 +4208,17 @@ IonBuilder::selectInliningTargets(Object
     for (size_t i = 0; i < targets.length(); i++) {
         JSFunction *target = &targets[i]->as<JSFunction>();
         bool inlineable;
         InliningDecision decision = makeInliningDecision(target, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
+          case InliningDecision_UseCountTooLow:
             inlineable = false;
             break;
           case InliningDecision_Inline:
             inlineable = true;
             break;
           default:
             MOZ_ASSUME_UNREACHABLE("Unhandled InliningDecision value!");
         }
@@ -4326,16 +4329,18 @@ IonBuilder::inlineCallsite(ObjectVector 
     if (!propCache && targets.length() == 1) {
         JSFunction *target = &targets[0]->as<JSFunction>();
         InliningDecision decision = makeInliningDecision(target, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return InliningStatus_Error;
           case InliningDecision_DontInline:
             return InliningStatus_NotInlined;
+          case InliningDecision_UseCountTooLow:
+            return InliningStatus_UseCountTooLow;
           case InliningDecision_Inline:
             break;
         }
 
         // Inlining will elminate uses of the original callee, but it needs to
         // be preserved in phis if we bail out.  Mark the old callee definition as
         // implicitly used to ensure this happens.
         callInfo.fun()->setImplicitlyUsedUnchecked();
@@ -4950,16 +4955,17 @@ IonBuilder::jsop_funcall(uint32_t argc)
 
     // Try to inline the call.
     if (!zeroArguments) {
         InliningDecision decision = makeInliningDecision(target, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
+          case InliningDecision_UseCountTooLow:
             break;
           case InliningDecision_Inline:
             if (target->isInterpreted())
                 return inlineScriptedCall(callInfo, target);
             break;
         }
     }
 
@@ -5090,16 +5096,17 @@ IonBuilder::jsop_funapplyarguments(uint3
     nativeFunc->setImplicitlyUsedUnchecked();
 
     // Try to inline the call.
     InliningDecision decision = makeInliningDecision(target, callInfo);
     switch (decision) {
       case InliningDecision_Error:
         return false;
       case InliningDecision_DontInline:
+      case InliningDecision_UseCountTooLow:
         break;
       case InliningDecision_Inline:
         if (target->isInterpreted())
             return inlineScriptedCall(callInfo, target);
     }
 
     return makeCall(target, callInfo, false);
 }
@@ -5160,16 +5167,23 @@ IonBuilder::jsop_call(uint32_t argc, boo
     if (status == InliningStatus_Error)
         return false;
 
     // No inline, just make the call.
     JSFunction *target = nullptr;
     if (targets.length() == 1)
         target = &targets[0]->as<JSFunction>();
 
+    if (target && status == InliningStatus_UseCountTooLow) {
+        MRecompileCheck *check = MRecompileCheck::New(alloc(), target->nonLazyScript(),
+                                                      optimizationInfo().usesBeforeInlining(),
+                                                      MRecompileCheck::RecompileCheck_Inlining);
+        current->add(check);
+    }
+
     return makeCall(target, callInfo, hasClones);
 }
 
 MDefinition *
 IonBuilder::makeCallsiteClone(JSFunction *target, MDefinition *fun)
 {
     // Bake in the clone eagerly if we have a known target. We have arrived here
     // because TI told us that the known target is a should-clone-at-callsite
@@ -6132,17 +6146,20 @@ IonBuilder::insertRecompileCheck()
     while (topBuilder->callerBuilder_)
         topBuilder = topBuilder->callerBuilder_;
 
     // Add recompile check to recompile when the usecount reaches the usecount
     // of the next optimization level.
     OptimizationLevel nextLevel = js_IonOptimizations.nextLevel(curLevel);
     const OptimizationInfo *info = js_IonOptimizations.get(nextLevel);
     uint32_t useCount = info->usesBeforeCompile(topBuilder->script());
-    current->add(MRecompileCheck::New(alloc(), topBuilder->script(), useCount));
+
+    MRecompileCheck *check = MRecompileCheck::New(alloc(), topBuilder->script(), useCount,
+                                MRecompileCheck::RecompileCheck_OptimizationLevel);
+    current->add(check);
 }
 
 JSObject *
 IonBuilder::testSingletonProperty(JSObject *obj, PropertyName *name)
 {
     // We would like to completely no-op property/global accesses which can
     // produce only a particular JSObject. When indicating the access result is
     // definitely an object, type inference does not account for the
@@ -8864,16 +8881,17 @@ IonBuilder::getPropTryCommonGetter(bool 
     // Inline if we can, otherwise, forget it and just generate a call.
     bool inlineable = false;
     if (commonGetter->isInterpreted()) {
         InliningDecision decision = makeInliningDecision(commonGetter, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
+          case InliningDecision_UseCountTooLow:
             break;
           case InliningDecision_Inline:
             inlineable = true;
             break;
         }
     }
 
     if (inlineable) {
@@ -9280,16 +9298,17 @@ IonBuilder::setPropTryCommonSetter(bool 
 
     // Inline the setter if we can.
     if (commonSetter->isInterpreted()) {
         InliningDecision decision = makeInliningDecision(commonSetter, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
+          case InliningDecision_UseCountTooLow:
             break;
           case InliningDecision_Inline:
             if (!inlineScriptedCall(callInfo, commonSetter))
                 return false;
             *emitted = true;
             return true;
         }
     }
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -628,24 +628,26 @@ class IonBuilder : public MIRGenerator
     bool jsop_setaliasedvar(ScopeCoordinate sc);
 
     /* Inlining. */
 
     enum InliningStatus
     {
         InliningStatus_Error,
         InliningStatus_NotInlined,
+        InliningStatus_UseCountTooLow,
         InliningStatus_Inlined
     };
 
     enum InliningDecision
     {
         InliningDecision_Error,
         InliningDecision_Inline,
-        InliningDecision_DontInline
+        InliningDecision_DontInline,
+        InliningDecision_UseCountTooLow
     };
 
     static InliningDecision DontInline(JSScript *targetScript, const char *reason);
 
     // Oracles.
     InliningDecision canInlineTarget(JSFunction *target, CallInfo &callInfo);
     InliningDecision makeInliningDecision(JSFunction *target, CallInfo &callInfo);
     bool selectInliningTargets(ObjectVector &targets, CallInfo &callInfo,
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -3003,22 +3003,22 @@ GetElementIC::attachGetProp(JSContext *c
     Register scratch = output().valueReg().scratchReg();
     masm.unboxString(val, scratch);
 
     Label equal;
     masm.branchPtr(Assembler::Equal, scratch, ImmGCPtr(name), &equal);
 
     // The pointers are not equal, so if the input string is also an atom it
     // must be a different string.
-    masm.loadPtr(Address(scratch, JSString::offsetOfLengthAndFlags()), scratch);
-    masm.branchTest32(Assembler::NonZero, scratch, Imm32(JSString::ATOM_BIT), &failures);
+    masm.branchTest32(Assembler::NonZero, Address(scratch, JSString::offsetOfFlags()),
+                      Imm32(JSString::ATOM_BIT), &failures);
 
     // Check the length.
-    masm.rshiftPtr(Imm32(JSString::LENGTH_SHIFT), scratch);
-    masm.branch32(Assembler::NotEqual, scratch, Imm32(name->length()), &failures);
+    masm.branch32(Assembler::NotEqual, Address(scratch, JSString::offsetOfLength()),
+                  Imm32(name->length()), &failures);
 
     // We have a non-atomized string with the same length. For now call a helper
     // function to do the comparison.
     RegisterSet volatileRegs = RegisterSet::Volatile();
     masm.PushRegsInMask(volatileRegs);
 
     Register objReg = object();
     JS_ASSERT(objReg != scratch);
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -586,24 +586,24 @@ MacroAssembler::newGCThing(Register resu
     gc::AllocKind allocKind = templateObj->tenuredGetAllocKind();
     JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
 
     allocateObject(result, temp, allocKind, templateObj->numDynamicSlots(), initialHeap, fail);
 }
 
 void
 MacroAssembler::createGCObject(Register obj, Register temp, JSObject *templateObj,
-                               gc::InitialHeap initialHeap, Label *fail)
+                               gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots)
 {
     uint32_t nDynamicSlots = templateObj->numDynamicSlots();
     gc::AllocKind allocKind = templateObj->tenuredGetAllocKind();
     JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
 
     allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail);
-    initGCThing(obj, temp, templateObj);
+    initGCThing(obj, temp, templateObj, initFixedSlots);
 }
 
 
 // Inlined equivalent of gc::AllocateNonObject, without failure case handling.
 // Non-object allocation does not need to worry about slots, so can take a
 // simpler path.
 void
 MacroAssembler::allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label *fail)
@@ -737,56 +737,60 @@ FindStartOfUndefinedSlots(JSObject *temp
     for (uint32_t first = nslots; first != 0; --first) {
         if (templateObj->getSlot(first - 1) != UndefinedValue())
             return first;
     }
     return 0;
 }
 
 void
-MacroAssembler::initGCSlots(Register obj, Register slots, JSObject *templateObj)
+MacroAssembler::initGCSlots(Register obj, Register slots, JSObject *templateObj,
+                            bool initFixedSlots)
 {
     // Slots of non-array objects are required to be initialized.
     // Use the values currently in the template object.
     uint32_t nslots = templateObj->lastProperty()->slotSpan(templateObj->getClass());
     if (nslots == 0)
         return;
 
-    uint32_t nfixed = Min(templateObj->numFixedSlots(), nslots);
+    uint32_t nfixed = templateObj->numUsedFixedSlots();
     uint32_t ndynamic = templateObj->numDynamicSlots();
 
     // Attempt to group slot writes such that we minimize the amount of
     // duplicated data we need to embed in code and load into registers. In
     // general, most template object slots will be undefined except for any
     // reserved slots. Since reserved slots come first, we split the object
     // logically into independent non-UndefinedValue writes to the head and
     // duplicated writes of UndefinedValue to the tail. For the majority of
     // objects, the "tail" will be the entire slot range.
     uint32_t startOfUndefined = FindStartOfUndefinedSlots(templateObj, nslots);
     JS_ASSERT(startOfUndefined <= nfixed); // Reserved slots must be fixed.
 
     // Copy over any preserved reserved slots.
     copySlotsFromTemplate(obj, templateObj, 0, startOfUndefined);
 
     // Fill the rest of the fixed slots with undefined.
-    fillSlotsWithUndefined(Address(obj, JSObject::getFixedSlotOffset(startOfUndefined)), slots,
-                           startOfUndefined, nfixed);
+    if (initFixedSlots) {
+        fillSlotsWithUndefined(Address(obj, JSObject::getFixedSlotOffset(startOfUndefined)), slots,
+                               startOfUndefined, nfixed);
+    }
 
     if (ndynamic) {
         // We are short one register to do this elegantly. Borrow the obj
         // register briefly for our slots base address.
         push(obj);
         loadPtr(Address(obj, JSObject::offsetOfSlots()), obj);
         fillSlotsWithUndefined(Address(obj, 0), slots, 0, ndynamic);
         pop(obj);
     }
 }
 
 void
-MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj)
+MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj,
+                            bool initFixedSlots)
 {
     // Fast initialization of an empty object returned by allocateObject().
 
     JS_ASSERT(!templateObj->hasDynamicElements());
 
     storePtr(ImmGCPtr(templateObj->lastProperty()), Address(obj, JSObject::offsetOfShape()));
     storePtr(ImmGCPtr(templateObj->type()), Address(obj, JSObject::offsetOfType()));
     if (templateObj->hasDynamicSlots())
@@ -813,63 +817,76 @@ MacroAssembler::initGCThing(Register obj
         store32(Imm32(templateObj->shouldConvertDoubleElements()
                       ? ObjectElements::CONVERT_DOUBLE_ELEMENTS
                       : 0),
                 Address(obj, elementsOffset + ObjectElements::offsetOfFlags()));
         JS_ASSERT(!templateObj->hasPrivate());
     } else {
         storePtr(ImmPtr(emptyObjectElements), Address(obj, JSObject::offsetOfElements()));
 
-        initGCSlots(obj, slots, templateObj);
+        initGCSlots(obj, slots, templateObj, initFixedSlots);
 
         if (templateObj->hasPrivate()) {
             uint32_t nfixed = templateObj->numFixedSlots();
             storePtr(ImmPtr(templateObj->getPrivate()),
                      Address(obj, JSObject::getPrivateDataOffset(nfixed)));
         }
     }
 }
 
 void
 MacroAssembler::compareStrings(JSOp op, Register left, Register right, Register result,
-                               Register temp, Label *fail)
+                               Label *fail)
 {
     JS_ASSERT(IsEqualityOp(op));
 
     Label done;
     Label notPointerEqual;
     // Fast path for identical strings.
     branchPtr(Assembler::NotEqual, left, right, &notPointerEqual);
     move32(Imm32(op == JSOP_EQ || op == JSOP_STRICTEQ), result);
     jump(&done);
 
     bind(&notPointerEqual);
-    loadPtr(Address(left, JSString::offsetOfLengthAndFlags()), result);
-    loadPtr(Address(right, JSString::offsetOfLengthAndFlags()), temp);
 
     Label notAtom;
     // Optimize the equality operation to a pointer compare for two atoms.
     Imm32 atomBit(JSString::ATOM_BIT);
-    branchTest32(Assembler::Zero, result, atomBit, &notAtom);
-    branchTest32(Assembler::Zero, temp, atomBit, &notAtom);
+    branchTest32(Assembler::Zero, Address(left, JSString::offsetOfFlags()), atomBit, &notAtom);
+    branchTest32(Assembler::Zero, Address(right, JSString::offsetOfFlags()), atomBit, &notAtom);
 
     cmpPtrSet(JSOpToCondition(MCompare::Compare_String, op), left, right, result);
     jump(&done);
 
     bind(&notAtom);
     // Strings of different length can never be equal.
-    rshiftPtr(Imm32(JSString::LENGTH_SHIFT), result);
-    rshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp);
-    branchPtr(Assembler::Equal, result, temp, fail);
+    loadStringLength(left, result);
+    branch32(Assembler::Equal, Address(right, JSString::offsetOfLength()), result, fail);
     move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), result);
 
     bind(&done);
 }
 
 void
+MacroAssembler::loadStringChars(Register str, Register dest)
+{
+    Label isInline, done;
+    branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()),
+                 Imm32(JSString::INLINE_CHARS_BIT), &isInline);
+
+    loadPtr(Address(str, JSString::offsetOfNonInlineChars()), dest);
+    jump(&done);
+
+    bind(&isInline);
+    computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest);
+
+    bind(&done);
+}
+
+void
 MacroAssembler::checkInterruptFlagPar(Register tempReg, Label *fail)
 {
 #ifdef JS_THREADSAFE
     movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptPar()), tempReg);
     branch32(Assembler::NonZero, Address(tempReg, 0), Imm32(0), fail);
 #else
     MOZ_ASSUME_UNREACHABLE("JSRuntime::interruptPar doesn't exist on non-threadsafe builds.");
 #endif
--- a/js/src/jit/IonMacroAssembler.h
+++ b/js/src/jit/IonMacroAssembler.h
@@ -372,18 +372,24 @@ class MacroAssembler : public MacroAssem
     }
 
     void loadObjProto(Register obj, Register dest) {
         loadPtr(Address(obj, JSObject::offsetOfType()), dest);
         loadPtr(Address(dest, types::TypeObject::offsetOfProto()), dest);
     }
 
     void loadStringLength(Register str, Register dest) {
-        loadPtr(Address(str, JSString::offsetOfLengthAndFlags()), dest);
-        rshiftPtr(Imm32(JSString::LENGTH_SHIFT), dest);
+        load32(Address(str, JSString::offsetOfLength()), dest);
+    }
+
+    void loadStringChars(Register str, Register dest);
+
+    void branchIfRope(Register str, Label *label) {
+        Address flags(str, JSString::offsetOfFlags());
+        branch32(Assembler::Equal, flags, Imm32(JSString::ROPE_FLAGS), label);
     }
 
     void loadSliceBounds(Register worker, Register dest) {
         loadPtr(Address(worker, ThreadPoolWorker::offsetOfSliceBounds()), dest);
     }
 
     void loadJSContext(Register dest) {
         loadPtr(AbsoluteAddress(GetIonContext()->runtime->addressOfJSContext()), dest);
@@ -797,27 +803,28 @@ class MacroAssembler : public MacroAssem
                          size_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail);
     void freeSpanAllocate(Register result, Register temp, gc::AllocKind allocKind, Label *fail);
     void allocateObject(Register result, Register slots, gc::AllocKind allocKind,
                         uint32_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail);
     void allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label *fail);
     void copySlotsFromTemplate(Register obj, const JSObject *templateObj,
                                uint32_t start, uint32_t end);
     void fillSlotsWithUndefined(Address addr, Register temp, uint32_t start, uint32_t end);
-    void initGCSlots(Register obj, Register temp, JSObject *templateObj);
+    void initGCSlots(Register obj, Register temp, JSObject *templateObj, bool initFixedSlots);
 
   public:
     void callMallocStub(size_t nbytes, Register result, Label *fail);
     void callFreeStub(Register slots);
     void createGCObject(Register result, Register temp, JSObject *templateObj,
-                        gc::InitialHeap initialHeap, Label *fail);
+                        gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots = true);
 
     void newGCThing(Register result, Register temp, JSObject *templateObj,
                      gc::InitialHeap initialHeap, Label *fail);
-    void initGCThing(Register obj, Register temp, JSObject *templateObj);
+    void initGCThing(Register obj, Register temp, JSObject *templateObj,
+                     bool initFixedSlots = true);
 
     void newGCString(Register result, Register temp, Label *fail);
     void newGCFatInlineString(Register result, Register temp, Label *fail);
 
     void newGCThingPar(Register result, Register cx, Register tempReg1, Register tempReg2,
                        gc::AllocKind allocKind, Label *fail);
     void newGCThingPar(Register result, Register cx, Register tempReg1, Register tempReg2,
                        JSObject *templateObject, Label *fail);
@@ -825,17 +832,17 @@ class MacroAssembler : public MacroAssem
                         Label *fail);
     void newGCFatInlineStringPar(Register result, Register cx, Register tempReg1, Register tempReg2,
                                  Label *fail);
 
 
     // Compares two strings for equality based on the JSOP.
     // This checks for identical pointers, atoms and length and fails for everything else.
     void compareStrings(JSOp op, Register left, Register right, Register result,
-                        Register temp, Label *fail);
+                        Label *fail);
 
     // Checks the flags that signal that parallel code may need to interrupt or
     // abort.  Branches to fail in that case.
     void checkInterruptFlagPar(Register tempReg, Label *fail);
 
     // If the JitCode that created this assembler needs to transition into the VM,
     // we want to store the JitCode on the stack in order to mark it during a GC.
     // This is a reference to a patch location where the JitCode* will be written.
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -1865,64 +1865,54 @@ class LCompareFAndBranch : public LContr
     MTest *mir() const {
         return mir_->toTest();
     }
     MCompare *cmpMir() const {
         return cmpMir_;
     }
 };
 
-class LCompareS : public LInstructionHelper<1, 2, 1>
+class LCompareS : public LInstructionHelper<1, 2, 0>
 {
   public:
     LIR_HEADER(CompareS)
-    LCompareS(const LAllocation &left, const LAllocation &right,
-              const LDefinition &temp) {
+    LCompareS(const LAllocation &left, const LAllocation &right) {
         setOperand(0, left);
         setOperand(1, right);
-        setTemp(0, temp);
     }
 
     const LAllocation *left() {
         return getOperand(0);
     }
     const LAllocation *right() {
         return getOperand(1);
     }
-    const LDefinition *temp() {
-        return getTemp(0);
-    }
     MCompare *mir() {
         return mir_->toCompare();
     }
 };
 
 // strict-equality between value and string.
-class LCompareStrictS : public LInstructionHelper<1, BOX_PIECES + 1, 2>
+class LCompareStrictS : public LInstructionHelper<1, BOX_PIECES + 1, 1>
 {
   public:
     LIR_HEADER(CompareStrictS)
-    LCompareStrictS(const LAllocation &rhs, const LDefinition &temp0,
-                    const LDefinition &temp1) {
+    LCompareStrictS(const LAllocation &rhs, const LDefinition &temp) {
         setOperand(BOX_PIECES, rhs);
-        setTemp(0, temp0);
-        setTemp(1, temp1);
+        setTemp(0, temp);
     }
 
     static const size_t Lhs = 0;
 
     const LAllocation *right() {
         return getOperand(BOX_PIECES);
     }
-    const LDefinition *temp() {
+    const LDefinition *tempToUnbox() {
         return getTemp(0);
     }
-    const LDefinition *tempToUnbox() {
-        return getTemp(1);
-    }
     MCompare *mir() {
         return mir_->toCompare();
     }
 };
 
 // Used for strict-equality comparisons where one side is a boolean
 // and the other is a value. Note that CompareI is used to compare
 // two booleans.
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -891,28 +891,28 @@ LIRGenerator::visitCompare(MCompare *com
     bool result;
     if (comp->tryFold(&result))
         return define(new(alloc()) LInteger(result), comp);
 
     // Move below the emitAtUses call if we ever implement
     // LCompareSAndBranch. Doing this now wouldn't be wrong, but doesn't
     // make sense and avoids confusion.
     if (comp->compareType() == MCompare::Compare_String) {
-        LCompareS *lir = new(alloc()) LCompareS(useRegister(left), useRegister(right), temp());
+        LCompareS *lir = new(alloc()) LCompareS(useRegister(left), useRegister(right));
         if (!define(lir, comp))
             return false;
         return assignSafepoint(lir, comp);
     }
 
     // Strict compare between value and string
     if (comp->compareType() == MCompare::Compare_StrictString) {
         JS_ASSERT(left->type() == MIRType_Value);
         JS_ASSERT(right->type() == MIRType_String);
 
-        LCompareStrictS *lir = new(alloc()) LCompareStrictS(useRegister(right), temp(), tempToUnbox());
+        LCompareStrictS *lir = new(alloc()) LCompareStrictS(useRegister(right), tempToUnbox());
         if (!useBox(lir, LCompareStrictS::Lhs, left))
             return false;
         if (!define(lir, comp))
             return false;
         return assignSafepoint(lir, comp);
     }
 
     // Unknown/unspecialized compare use a VM call.
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -6995,18 +6995,18 @@ class MStoreFixedSlot
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::FixedSlot);
     }
     bool needsBarrier() const {
         return needsBarrier_;
     }
-    void setNeedsBarrier() {
-        needsBarrier_ = true;
+    void setNeedsBarrier(bool needsBarrier = true) {
+        needsBarrier_ = needsBarrier;
     }
 };
 
 typedef Vector<JSObject *, 4, IonAllocPolicy> ObjectVector;
 typedef Vector<bool, 4, IonAllocPolicy> BoolVector;
 
 class InlinePropertyTable : public TempObject
 {
@@ -9864,41 +9864,73 @@ class MHasClass
     }
 };
 
 // Increase the usecount of the provided script upon execution and test if
 // the usecount surpasses the threshold. Upon hit it will recompile the
 // outermost script (i.e. not the inlined script).
 class MRecompileCheck : public MNullaryInstruction
 {
+  public:
+    enum RecompileCheckType {
+        RecompileCheck_OptimizationLevel,
+        RecompileCheck_Inlining
+    };
+
+  private:
     JSScript *script_;
     uint32_t recompileThreshold_;
-
-    MRecompileCheck(JSScript *script, uint32_t recompileThreshold)
+    bool forceRecompilation_;
+    bool increaseUseCount_;
+
+    MRecompileCheck(JSScript *script, uint32_t recompileThreshold, RecompileCheckType type)
       : script_(script),
         recompileThreshold_(recompileThreshold)
     {
+        switch (type) {
+          case RecompileCheck_OptimizationLevel:
+            forceRecompilation_ = false;
+            increaseUseCount_ = true;
+            break;
+          case RecompileCheck_Inlining:
+            forceRecompilation_ = true;
+            increaseUseCount_ = false;
+            break;
+          default:
+            MOZ_ASSUME_UNREACHABLE("Unexpected recompile check type");
+        }
+
         setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(RecompileCheck);
 
-    static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_, uint32_t useCount) {
-        return new(alloc) MRecompileCheck(script_, useCount);
+    static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_,
+                                uint32_t useCount, RecompileCheckType type)
+    {
+        return new(alloc) MRecompileCheck(script_, useCount, type);
     }
 
     JSScript *script() const {
         return script_;
     }
 
     uint32_t recompileThreshold() const {
         return recompileThreshold_;
     }
 
+    bool forceRecompilation() const {
+        return forceRecompilation_;
+    }
+
+    bool increaseUseCount() const {
+        return increaseUseCount_;
+    }
+
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 class MAsmJSNeg : public MUnaryInstruction
 {
     MAsmJSNeg(MDefinition *op, MIRType type)
--- a/js/src/jit/MIRGraph.h
+++ b/js/src/jit/MIRGraph.h
@@ -611,16 +611,21 @@ class MIRGraph
     }
     void removeBlocksAfter(MBasicBlock *block);
     void removeBlock(MBasicBlock *block);
     void moveBlockToEnd(MBasicBlock *block) {
         JS_ASSERT(block->id());
         blocks_.remove(block);
         blocks_.pushBack(block);
     }
+    void moveBlockBefore(MBasicBlock *at, MBasicBlock *block) {
+        JS_ASSERT(block->id());
+        blocks_.remove(block);
+        blocks_.insertBefore(at, block);
+    }
     size_t numBlocks() const {
         return numBlocks_;
     }
     uint32_t numBlockIds() const {
         return blockIdGen_;
     }
     void allocDefinitionId(MDefinition *ins) {
         ins->setId(idGen_++);
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1018,41 +1018,53 @@ StringReplace(JSContext *cx, HandleStrin
 
     RootedValue rval(cx);
     if (!str_replace_string_raw(cx, string, pattern, repl, &rval))
         return nullptr;
 
     return rval.toString();
 }
 
-bool
-Recompile(JSContext *cx)
+static bool
+RecompileImpl(JSContext *cx, bool force)
 {
     JS_ASSERT(cx->currentlyRunningInJit());
     JitActivationIterator activations(cx->runtime());
     JitFrameIterator iter(activations);
 
     JS_ASSERT(iter.type() == JitFrame_Exit);
     ++iter;
 
     bool isConstructing = iter.isConstructing();
     RootedScript script(cx, iter.script());
     JS_ASSERT(script->hasIonScript());
 
     if (!IsIonEnabled(cx))
         return true;
 
-    MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing);
+    MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing, force);
     if (status == Method_Error)
         return false;
 
     return true;
 }
 
 bool
+ForcedRecompile(JSContext *cx)
+{
+    return RecompileImpl(cx, /* force = */ true);
+}
+
+bool
+Recompile(JSContext *cx)
+{
+    return RecompileImpl(cx, /* force = */ false);
+}
+
+bool
 SetDenseElement(JSContext *cx, HandleObject obj, int32_t index, HandleValue value,
                 bool strict)
 {
     // This function is called from Ion code for StoreElementHole's OOL path.
     // In this case we know the object is native, has no indexed properties
     // and we can use setDenseElement instead of setDenseElementWithType.
 
     MOZ_ASSERT(obj->isNative());
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -671,16 +671,17 @@ bool InitBaselineFrameForOsr(BaselineFra
                              uint32_t numStackValues);
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject descr,
                                 HandleObject owner, int32_t offset);
 
 bool ArraySpliceDense(JSContext *cx, HandleObject obj, uint32_t start, uint32_t deleteCount);
 
 bool Recompile(JSContext *cx);
+bool ForcedRecompile(JSContext *cx);
 JSString *RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp,
                         HandleString repl);
 JSString *StringReplace(JSContext *cx, HandleString string, HandleString pattern,
                         HandleString repl);
 
 bool SetDenseElement(JSContext *cx, HandleObject obj, int32_t index, HandleValue value,
                      bool strict);
 
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -4079,24 +4079,19 @@ MacroAssemblerARMCompat::handleFailureWi
     ma_ldr(Operand(sp, offsetof(ResumeFromException, target)), r1);
     jump(r1);
 }
 
 Assembler::Condition
 MacroAssemblerARMCompat::testStringTruthy(bool truthy, const ValueOperand &value)
 {
     Register string = value.payloadReg();
-
-    size_t mask = (0xFFFFFFFF << JSString::LENGTH_SHIFT);
-    ma_dtr(IsLoad, string, Imm32(JSString::offsetOfLengthAndFlags()), ScratchRegister);
-    // Bit clear into the scratch register. This is done because there is performs the operation
-    // dest <- src1 & ~ src2. There is no instruction that does this without writing
-    // the result somewhere, so the Scratch Register is sacrificed.
-    ma_bic(Imm32(~mask), ScratchRegister, SetCond);
-    return truthy ? Assembler::NonZero : Assembler::Zero;
+    ma_dtr(IsLoad, string, Imm32(JSString::offsetOfLength()), ScratchRegister);
+    ma_cmp(ScratchRegister, Imm32(0));
+    return truthy ? Assembler::NotEqual : Assembler::Equal;
 }
 
 void
 MacroAssemblerARMCompat::floor(FloatRegister input, Register output, Label *bail)
 {
     Label handleZero;
     Label handleNeg;
     Label fin;
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1484,30 +1484,20 @@ Simulator::setCallResult(int64_t res)
 {
     set_register(r0, static_cast<int32_t>(res));
     set_register(r1, static_cast<int32_t>(res >> 32));
 }
 
 int
 Simulator::readW(int32_t addr, SimInstruction *instr)
 {
-#ifdef JS_YARR
-    // YARR emits unaligned loads, so we don't check for them here like the
-    // other methods below.
+    // The regexp engines emit unaligned loads, so we don't check for them here
+    // like the other methods below.
     intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
     return *ptr;
-#else // JS_YARR
-    if ((addr & 3) == 0) {
-        intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
-        return *ptr;
-    } else {
-        printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
-        MOZ_CRASH();
-    }
-#endif // JS_YARR
 }
 
 void
 Simulator::writeW(int32_t addr, int value, SimInstruction *instr)
 {
     if ((addr & 3) == 0) {
         intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
         *ptr = value;
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -1187,20 +1187,18 @@ class MacroAssemblerX64 : public MacroAs
         j(cond, label);
     }
     void branchTestBooleanTruthy(bool truthy, const ValueOperand &operand, Label *label) {
         testl(operand.valueReg(), operand.valueReg());
         j(truthy ? NonZero : Zero, label);
     }
     Condition testStringTruthy(bool truthy, const ValueOperand &value) {
         unboxString(value, ScratchReg);
-
-        Operand lengthAndFlags(ScratchReg, JSString::offsetOfLengthAndFlags());
-        testq(lengthAndFlags, Imm32(-1 << JSString::LENGTH_SHIFT));
-        return truthy ? Assembler::NonZero : Assembler::Zero;
+        cmpl(Operand(ScratchReg, JSString::offsetOfLength()), Imm32(0));
+        return truthy ? Assembler::NotEqual : Assembler::Equal;
     }
     void branchTestStringTruthy(bool truthy, const ValueOperand &value, Label *label) {
         Condition cond = testStringTruthy(truthy, value);
         j(cond, label);
     }
 
     void loadInt32OrDouble(const Operand &operand, FloatRegister dest) {
         Label notInt32, end;
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -948,21 +948,18 @@ class MacroAssemblerX86 : public MacroAs
         j(cond, label);
     }
     void branchTestBooleanTruthy(bool truthy, const ValueOperand &operand, Label *label) {
         testl(operand.payloadReg(), operand.payloadReg());
         j(truthy ? NonZero : Zero, label);
     }
     Condition testStringTruthy(bool truthy, const ValueOperand &value) {
         Register string = value.payloadReg();
-        Operand lengthAndFlags(string, JSString::offsetOfLengthAndFlags());
-
-        size_t mask = (0xFFFFFFFF << JSString::LENGTH_SHIFT);
-        testl(lengthAndFlags, Imm32(mask));
-        return truthy ? Assembler::NonZero : Assembler::Zero;
+        cmpl(Operand(string, JSString::offsetOfLength()), Imm32(0));
+        return truthy ? Assembler::NotEqual : Assembler::Equal;
     }
     void branchTestStringTruthy(bool truthy, const ValueOperand &value, Label *label) {
         Condition cond = testStringTruthy(truthy, value);
         j(cond, label);
     }
 
     void loadInt32OrDouble(const Operand &operand, FloatRegister dest) {
         Label notInt32, end;
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -67,16 +67,17 @@ UNIFIED_SOURCES += [
     'testSourcePolicy.cpp',
     'testStringBuffer.cpp',
     'testStructuredClone.cpp',
     'testToIntWidth.cpp',
     'testTrap.cpp',
     'testTypedArrays.cpp',
     'testUncaughtError.cpp',
     'testUTF8.cpp',
+    'testWeakMap.cpp',
     'testXDR.cpp',
 ]
 
 if CONFIG['ENABLE_ION']:
     UNIFIED_SOURCES += [
         'testJitRValueAlloc.cpp',
     ]
 
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+* vim: set ts=8 sts=4 et sw=4 tw=99:
+*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gc/Zone.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+#ifdef JSGC_USE_EXACT_ROOTING
+
+BEGIN_TEST(testWeakMap_basicOperations)
+{
+    RootedObject map(cx, NewWeakMapObject(cx));
+    CHECK(IsWeakMapObject(map));
+
+    RootedObject key(cx, newKey());
+    CHECK(key);
+    CHECK(!IsWeakMapObject(key));
+
+    RootedValue r(cx);
+    CHECK(GetWeakMapEntry(cx, map, key, &r));
+    CHECK(r.isUndefined());
+
+    CHECK(checkSize(map, 0));
+
+    RootedValue val(cx, Int32Value(1));
+    CHECK(SetWeakMapEntry(cx, map, key, val));
+
+    CHECK(GetWeakMapEntry(cx, map, key, &r));
+    CHECK(r == val);
+    CHECK(checkSize(map, 1));
+
+    JS_GC(rt);
+
+    CHECK(GetWeakMapEntry(cx, map, key, &r));
+    CHECK(r == val);
+    CHECK(checkSize(map, 1));
+
+    key = nullptr;
+    JS_GC(rt);
+
+    CHECK(checkSize(map, 0));
+
+    return true;
+}
+
+JSObject *newKey()
+{
+    return JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+}
+
+bool
+checkSize(HandleObject map, uint32_t expected)
+{
+    RootedObject keys(cx);
+    CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+    uint32_t length;
+    CHECK(JS_GetArrayLength(cx, keys, &length));
+    CHECK(length == expected);
+
+    return true;
+}
+END_TEST(testWeakMap_basicOperations)
+
+BEGIN_TEST(testWeakMap_keyDelegates)
+{
+    JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+    JS_GC(rt);
+
+    RootedObject map(cx, NewWeakMapObject(cx));
+    CHECK(map);
+
+    RootedObject key(cx, newKey());
+    CHECK(key);
+
+    RootedObject delegate(cx, newDelegate());
+    CHECK(delegate);
+
+    SetKeyDelegate(key, delegate);
+
+    /*
+     * Perform an incremental GC, introducing an unmarked CCW to force the map
+     * zone to finish marking before the delegate zone.
+     */
+    CHECK(newCCW(map, delegate));
+    GCDebugSlice(rt, true, 1000000);
+#ifdef DEBUG
+    CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex());
+#endif
+
+    /* Add our entry to the weakmap. */
+    RootedValue val(cx, Int32Value(1));
+    CHECK(SetWeakMapEntry(cx, map, key, val));
+    CHECK(checkSize(map, 1));
+
+    /* Check the delegate keeps the entry alive even if the key is not reachable. */
+    key = nullptr;
+    CHECK(newCCW(map, delegate));
+    GCDebugSlice(rt, true, 100000);
+    CHECK(checkSize(map, 1));
+
+    /*
+     * Check that the zones finished marking at the same time, which is
+     * neccessary because of the presence of the delegate and the CCW.
+     */
+#ifdef DEBUG
+    CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex());
+#endif
+
+    /* Check that when the delegate becomes unreacable the entry is removed. */
+    delegate = nullptr;
+    JS_GC(rt);
+    CHECK(checkSize(map, 0));
+
+    return true;
+}
+
+static void SetKeyDelegate(JSObject *key, JSObject *delegate)
+{
+    JS_SetPrivate(key, delegate);
+}
+
+static JSObject *GetKeyDelegate(JSObject *obj)
+{
+    return static_cast<JSObject*>(JS_GetPrivate(obj));
+}
+
+JSObject *newKey()
+{
+    static const js::Class keyClass = {
+        "keyWithDelgate",
+        JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
+        JS_PropertyStub,         /* addProperty */
+        JS_DeletePropertyStub,   /* delProperty */
+        JS_PropertyStub,         /* getProperty */
+        JS_StrictPropertyStub,   /* setProperty */
+        JS_EnumerateStub,
+        JS_ResolveStub,
+        JS_ConvertStub,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        JS_NULL_CLASS_SPEC,
+        {
+            nullptr,
+            nullptr,
+            nullptr,
+            false,
+            GetKeyDelegate
+        },
+        JS_NULL_OBJECT_OPS
+    };
+
+    RootedObject key(cx);
+    key = JS_NewObject(cx,
+                       reinterpret_cast<const JSClass *>(&keyClass),
+                       JS::NullPtr(),
+                       JS::NullPtr());
+    if (!key)
+        return nullptr;
+
+    SetKeyDelegate(key, nullptr);
+
+    return key;
+}
+
+JSObject *newCCW(HandleObject sourceZone, HandleObject destZone)
+{
+    /*
+     * Now ensure that this zone will be swept first by adding a cross
+     * compartment wrapper to a new objct in the same zone as the
+     * delegate obejct.
+     */
+    RootedObject object(cx);
+    {
+        JSAutoCompartment ac(cx, destZone);
+        object = JS_NewObject(cx, nullptr, NullPtr(), NullPtr());
+        if (!object)
+            return nullptr;
+    }
+    {
+        JSAutoCompartment ac(cx, sourceZone);
+        if (!JS_WrapObject(cx, &object))
+            return nullptr;
+    }
+    return object;
+}
+
+JSObject *newDelegate()
+{
+    static const JSClass delegateClass = {
+        "delegate",
+        JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
+        JS_PropertyStub,
+        JS_DeletePropertyStub,
+        JS_PropertyStub,
+        JS_StrictPropertyStub,
+        JS_EnumerateStub,
+        JS_ResolveStub,
+        JS_ConvertStub,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        JS_GlobalObjectTraceHook
+    };
+
+    /* Create the global object. */
+    JS::CompartmentOptions options;
+    options.setVersion(JSVERSION_LATEST);
+    JS::RootedObject global(cx);
+    global = JS_NewGlobalObject(cx, &delegateClass, nullptr, JS::FireOnNewGlobalHook, options);
+    JS_SetReservedSlot(global, 0, Int32Value(42));
+
+    /*
+     * Ensure the delegate is not in the nursery because for the purpose of this
+     * test we're going to put it in a private slot where it won't get updated.
+     */
+    JS_GC(rt);
+
+    return global;
+}
+
+bool
+checkSize(HandleObject map, uint32_t expected)
+{
+    RootedObject keys(cx);
+    CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+    uint32_t length;
+    CHECK(JS_GetArrayLength(cx, keys, &length));
+    CHECK(length == expected);
+
+    return true;
+}
+END_TEST(testWeakMap_keyDelegates)
+
+#endif // JSGC_USE_EXACT_ROOTING
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -581,19 +581,23 @@ struct Function {
     uint16_t flags;
     /* Used only for natives */
     JSNative native;
     const JSJitInfo *jitinfo;
     void *_1;
 };
 
 struct Atom {
-    static const size_t LENGTH_SHIFT = 4;
-    size_t lengthAndFlags;
-    const jschar *chars;
+    static const uint32_t INLINE_CHARS_BIT = JS_BIT(2);
+    uint32_t flags;
+    uint32_t length;
+    union {
+        const jschar *nonInlineChars;
+        char inlineStorage[1];
+    };
 };
 
 } /* namespace shadow */
 
 // This is equal to |&JSObject::class_|.  Use it in places where you don't want
 // to #include jsobj.h.
 extern JS_FRIEND_DATA(const js::Class* const) ObjectClassPtr;
 
@@ -762,24 +766,29 @@ GetObjectSlot(JSObject *obj, size_t slot
 {
     JS_ASSERT(slot < GetObjectSlotSpan(obj));
     return reinterpret_cast<const shadow::Object *>(obj)->slotRef(slot);
 }
 
 inline const jschar *
 GetAtomChars(JSAtom *atom)
 {
-    return reinterpret_cast<shadow::Atom *>(atom)->chars;
+    using shadow::Atom;
+    Atom *atom_ = reinterpret_cast<Atom *>(atom);
+    if (atom_->flags & Atom::INLINE_CHARS_BIT) {
+        char *p = reinterpret_cast<char *>(atom);
+        return reinterpret_cast<const jschar *>(p + offsetof(Atom, inlineStorage));
+    }
+    return atom_->nonInlineChars;
 }
 
 inline size_t
 GetAtomLength(JSAtom *atom)
 {
-    using shadow::Atom;
-    return reinterpret_cast<Atom*>(atom)->lengthAndFlags >> Atom::LENGTH_SHIFT;
+    return reinterpret_cast<shadow::Atom*>(atom)->length;
 }
 
 inline JSLinearString *
 AtomToLinearString(JSAtom *atom)
 {
     return reinterpret_cast<JSLinearString *>(atom);
 }
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2967,18 +2967,18 @@ GCRuntime::beginMarkPhase()
     gcstats::AutoPhase ap2(stats, gcstats::PHASE_MARK_ROOTS);
 
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
         /* Unmark everything in the zones being collected. */
         zone->allocator.arenas.unmarkAll();
     }
 
     for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
-        /* Reset weak map list for the compartments being collected. */
-        WeakMapBase::resetCompartmentWeakMapList(c);
+        /* Unmark all weak maps in the compartments being collected. */
+        WeakMapBase::unmarkCompartment(c);
     }
 
     if (isFull)
         UnmarkScriptData(rt);
 
     MarkRuntime(gcmarker);
     if (isIncremental)
         BufferGrayRoots(gcmarker);
@@ -3168,38 +3168,41 @@ js::gc::MarkingValidator::nonIncremental
             return;
 
         memcpy((void *)entry->bitmap, (void *)bitmap->bitmap, sizeof(bitmap->bitmap));
         if (!map.putNew(r.front(), entry))
             return;
     }
 
     /*
-     * Temporarily clear the lists of live weakmaps and array buffers for the
-     * compartments we are collecting.
+     * Temporarily clear the weakmaps' mark flags and the lists of live array
+     * buffers for the compartments we are collecting.
      */
 
-    WeakMapVector weakmaps;
+    WeakMapSet markedWeakMaps;
+    if (!markedWeakMaps.init())
+        return;
+
     ArrayBufferVector arrayBuffers;
     for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
-        if (!WeakMapBase::saveCompartmentWeakMapList(c, weakmaps) ||
+        if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps) ||
             !ArrayBufferObject::saveArrayBufferList(c, arrayBuffers))
         {
             return;
         }
     }
 
     /*
      * After this point, the function should run to completion, so we shouldn't
      * do anything fallible.
      */
     initialized = true;
 
     for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
-        WeakMapBase::resetCompartmentWeakMapList(c);
+        WeakMapBase::unmarkCompartment(c);
         ArrayBufferObject::resetArrayBufferList(c);
     }
 
     /* Re-do all the marking, but non-incrementally. */
     js::gc::State state = gc->incrementalState;
     gc->incrementalState = MARK_ROOTS;
 
     JS_ASSERT(gcmarker->isDrained());
@@ -3245,20 +3248,20 @@ js::gc::MarkingValidator::nonIncremental
     for (GCChunkSet::Range r(gc->chunkSet.all()); !r.empty(); r.popFront()) {
         Chunk *chunk = r.front();
         ChunkBitmap *bitmap = &chunk->bitmap;
         ChunkBitmap *entry = map.lookup(chunk)->value();
         Swap(*entry, *bitmap);
     }
 
     for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
-        WeakMapBase::resetCompartmentWeakMapList(c);
+        WeakMapBase::unmarkCompartment(c);
         ArrayBufferObject::resetArrayBufferList(c);
     }
-    WeakMapBase::restoreCompartmentWeakMapLists(weakmaps);
+    WeakMapBase::restoreCompartmentMarkedWeakMaps(markedWeakMaps);
     ArrayBufferObject::restoreArrayBufferLists(arrayBuffers);
 
     gc->incrementalState = state;
 }
 
 void
 js::gc::MarkingValidator::validate()
 {
@@ -3434,23 +3437,48 @@ Zone::findOutgoingEdges(ComponentFinder<
      * compartment, and these aren't in the cross compartment map.
      */
     JSRuntime *rt = runtimeFromMainThread();
     if (rt->atomsCompartment()->zone()->isGCMarking())
         finder.addEdgeTo(rt->atomsCompartment()->zone());
 
     for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next())
         comp->findOutgoingEdges(finder);
+
+    for (ZoneSet::Range r = gcZoneGroupEdges.all(); !r.empty(); r.popFront())
+        finder.addEdgeTo(r.front());
+    gcZoneGroupEdges.clear();
+}
+
+bool
+GCRuntime::findZoneEdgesForWeakMaps()
+{
+    /*
+     * Weakmaps which have keys with delegates in a different zone introduce the
+     * need for zone edges from the delegate's zone to the weakmap zone.
+     *
+     * Since the edges point into and not away from the zone the weakmap is in
+     * we must find these edges in advance and store them in a set on the Zone.
+     * If we run out of memory, we fall back to sweeping everything in one
+     * group.
+     */
+
+    for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) {
+        if (!WeakMapBase::findZoneEdgesForCompartment(comp))
+            return false;
+    }
+
+    return true;
 }
 
 void
 GCRuntime::findZoneGroups()
 {
     ComponentFinder<Zone> finder(rt->mainThread.nativeStackLimit[StackForSystemCode]);
-    if (!isIncremental)
+    if (!isIncremental || !findZoneEdgesForWeakMaps())
         finder.useOneComponent();
 
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
         JS_ASSERT(zone->isGCMarking());
         finder.addNode(zone);
     }
     zoneGroups = finder.getResultsList();
     currentZoneGroup = zoneGroups;
@@ -3782,16 +3810,18 @@ GCRuntime::beginSweepingZoneGroup()
         /* Purge the ArenaLists before sweeping. */
         zone->allocator.arenas.purge();
 
         if (rt->isAtomsZone(zone))
             sweepingAtoms = true;
 
         if (rt->sweepZoneCallback)
             rt->sweepZoneCallback(zone);
+
+        zone->gcLastZoneGroupIndex = zoneGroupIndex;
     }
 
     validateIncrementalMarking();
 
     FreeOp fop(rt, sweepOnBackgroundThread);
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
@@ -5014,16 +5044,19 @@ js::NewCompartment(JSContext *cx, Zone *
     ScopedJSDeletePtr<Zone> zoneHolder;
     if (!zone) {
         zone = cx->new_<Zone>(rt);
         if (!zone)
             return nullptr;
 
         zoneHolder.reset(zone);
 
+        if (!zone->init())
+            return nullptr;
+
         zone->setGCLastBytes(8192, GC_NORMAL);
 
         const JSPrincipals *trusted = rt->trustedPrincipals();
         zone->isSystem = principals && principals == trusted;
     }
 
     ScopedJSDeletePtr<JSCompartment> compartment(cx->new_<JSCompartment>(zone, options));
     if (!compartment || !compartment->init(cx))
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -15,109 +15,182 @@
 #include "jswrapper.h"
 
 #include "vm/GlobalObject.h"
 #include "vm/WeakMapObject.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
+using namespace js::gc;
 
 WeakMapBase::WeakMapBase(JSObject *memOf, JSCompartment *c)
   : memberOf(memOf),
     compartment(c),
-    next(WeakMapNotInList)
+    next(WeakMapNotInList),
+    marked(false)
 {
     JS_ASSERT_IF(memberOf, memberOf->compartment() == c);
 }
 
 WeakMapBase::~WeakMapBase()
 {
-    JS_ASSERT(next == WeakMapNotInList);
+    JS_ASSERT(!isInList());
+}
+
+void
+WeakMapBase::trace(JSTracer *tracer)
+{
+    JS_ASSERT(isInList());
+    if (IS_GC_MARKING_TRACER(tracer)) {
+        // We don't trace any of the WeakMap entries at this time, just record
+        // record the fact that the WeakMap has been marked. Enties are marked
+        // in the iterative marking phase by markAllIteratively(), which happens
+        // when many keys as possible have been marked already.
+        JS_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps);
+        marked = true;
+    } else {
+        // If we're not actually doing garbage collection, the keys won't be marked
+        // nicely as needed by the true ephemeral marking algorithm --- custom tracers
+        // such as the cycle collector must use their own means for cycle detection.
+        // So here we do a conservative approximation: pretend all keys are live.
+        if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps)
+            return;
+
+        nonMarkingTraceValues(tracer);
+        if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues)
+            nonMarkingTraceKeys(tracer);
+    }
+}
+
+void
+WeakMapBase::unmarkCompartment(JSCompartment *c)
+{
+    for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
+        m->marked = false;
 }
 
 bool
 WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer)
 {
     bool markedAny = false;
     for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
-        if (m->markIteratively(tracer))
+        if (m->marked && m->markIteratively(tracer))
             markedAny = true;
     }
     return markedAny;
 }
 
+bool
+WeakMapBase::findZoneEdgesForCompartment(JSCompartment *c)
+{
+    for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
+        if (!m->findZoneEdges())
+            return false;
+    }
+    return true;
+}
+
 void
 WeakMapBase::sweepCompartment(JSCompartment *c)
 {
+    WeakMapBase **tailPtr = &c->gcWeakMapList;
+    for (WeakMapBase *m = c->gcWeakMapList, *next; m; m = next) {
+        next = m->next;
+        if (m->marked) {
+            m->sweep();
+            *tailPtr = m;
+            tailPtr = &m->next;
+        } else {
+            /* Destroy the hash map now to catch any use after this point. */
+            m->finish();
+            m->next = WeakMapNotInList;
+        }
+    }
+    *tailPtr = nullptr;
+
+#ifdef DEBUG
     for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
-        m->sweep();
+        JS_ASSERT(m->isInList() && m->marked);
+#endif
 }
 
 void
 WeakMapBase::traceAllMappings(WeakMapTracer *tracer)
 {
     JSRuntime *rt = tracer->runtime;
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
             m->traceMappings(tracer);
     }
 }
 
-void
-WeakMapBase::resetCompartmentWeakMapList(JSCompartment *c)
+bool
+WeakMapBase::saveCompartmentMarkedWeakMaps(JSCompartment *c, WeakMapSet &markedWeakMaps)
 {
-    JS_ASSERT(WeakMapNotInList != nullptr);
-
-    WeakMapBase *m = c->gcWeakMapList;
-    c->gcWeakMapList = nullptr;
-    while (m) {
-        WeakMapBase *n = m->next;
-        m->next = WeakMapNotInList;
-        m = n;
-    }
-}
-
-bool
-WeakMapBase::saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector)
-{
-    WeakMapBase *m = c->gcWeakMapList;
-    while (m) {
-        if (!vector.append(m))
+    for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
+        if (m->marked && !markedWeakMaps.put(m))
             return false;
-        m = m->next;
     }
     return true;
 }
 
 void
-WeakMapBase::restoreCompartmentWeakMapLists(WeakMapVector &vector)
+WeakMapBase::restoreCompartmentMarkedWeakMaps(WeakMapSet &markedWeakMaps)
 {
-    for (WeakMapBase **p = vector.begin(); p != vector.end(); p++) {
-        WeakMapBase *m = *p;
-        JS_ASSERT(m->next == WeakMapNotInList);
-        JSCompartment *c = m->compartment;
-        m->next = c->gcWeakMapList;
-        c->gcWeakMapList = m;
+    for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) {
+        WeakMapBase *map = r.front();
+        JS_ASSERT(map->compartment->zone()->isGCMarking());
+        JS_ASSERT(!map->marked);
+        map->marked = true;
     }
 }
 
 void
 WeakMapBase::removeWeakMapFromList(WeakMapBase *weakmap)
 {
     JSCompartment *c = weakmap->compartment;
     for (WeakMapBase **p = &c->gcWeakMapList; *p; p = &(*p)->next) {
         if (*p == weakmap) {
             *p = (*p)->next;
             weakmap->next = WeakMapNotInList;
             break;
         }
     }
 }
 
+bool
+ObjectValueMap::findZoneEdges()
+{
+    /*
+     * For unmarked weakmap keys with delegates in a different zone, add a zone
+     * edge to ensure that the delegate zone does finish marking after the key
+     * zone.
+     */
+    JS::AutoAssertNoGC nogc;
+    Zone *mapZone = compartment->zone();
+    for (Range r = all(); !r.empty(); r.popFront()) {
+        JSObject *key = r.front().key();
+        if (key->isMarked(BLACK) && !key->isMarked(GRAY))
+            continue;
+        JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp;
+        if (!op)
+            continue;
+        JSObject *delegate = op(key);
+        if (!delegate)
+            continue;
+        Zone *delegateZone = delegate->zone();
+        if (delegateZone == mapZone)
+            continue;
+        if (!delegateZone->gcZoneGroupEdges.put(key->zone()))
+            return false;
+    }
+    return true;
+}
+
 static JSObject *
 GetKeyArg(JSContext *cx, CallArgs &args)
 {
     if (args[0].isPrimitive()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
         return nullptr;
     }
     return &args[0].toObject();
@@ -161,18 +234,18 @@ WeakMap_has(JSContext *cx, unsigned argc
     return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
 WeakMap_clear_impl(JSContext *cx, CallArgs args)
 {
     JS_ASSERT(IsWeakMap(args.thisv()));
 
-    // We can't js_delete the weakmap because the data gathered during GC
-    // is used by the Cycle Collector
+    // We can't js_delete the weakmap because the data gathered during GC is
+    // used by the Cycle Collector.
     if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap())
         map->clear();
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
@@ -281,17 +354,17 @@ WeakMapPostWriteBarrier(JSRuntime *rt, O
      * WeakMap's multiple inheritace, We need to do this in two stages, first to
      * the HashMap base class and then to the unbarriered version.
      */
     ObjectValueMap::Base *baseHashMap = static_cast<ObjectValueMap::Base *>(weakMap);
 
     typedef HashMap<JSObject *, Value> UnbarrieredMap;
     UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap);
 
-    typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref;
+    typedef HashKeyRef<UnbarrieredMap, JSObject *> Ref;
     if (key && IsInsideNursery(rt, key))
         rt->gc.storeBuffer.putGeneric(Ref((unbarrieredMap), key));
 #endif
 }
 
 MOZ_ALWAYS_INLINE bool
 SetWeakMapEntryInternal(JSContext *cx, Handle<WeakMapObject*> mapObj,
                         HandleObject key, HandleValue value)
@@ -368,17 +441,17 @@ JS_NondeterministicGetWeakMapKeys(JSCont
         return true;
     }
     RootedObject arr(cx, NewDenseEmptyArray(cx));
     if (!arr)
         return false;
     ObjectValueMap *map = obj->as<WeakMapObject>().getMap();
     if (map) {
         // Prevent GC from mutating the weakmap while iterating.
-        gc::AutoSuppressGC suppress(cx);
+        AutoSuppressGC suppress(cx);
         for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
             RootedObject key(cx, r.front().key());
             if (!cx->compartment()->wrap(cx, &key))
                 return false;
             if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
                 return false;
         }
     }
@@ -392,17 +465,16 @@ WeakMap_mark(JSTracer *trc, JSObject *ob
     if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap())
         map->trace(trc);
 }
 
 static void
 WeakMap_finalize(FreeOp *fop, JSObject *obj)
 {
     if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap()) {
-        map->check();
 #ifdef DEBUG
         map->~ObjectValueMap();
         memset(static_cast<void *>(map), 0xdc, sizeof(*map));
         fop->free_(map);
 #else
         fop->delete_(map);
 #endif
     }
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -28,121 +28,107 @@ namespace js {
 // You must call this table's 'trace' method when the object of which it is a part is
 // reached by the garbage collection tracer. Once a table is known to be live, the
 // implementation takes care of the iterative marking needed for weak tables and removing
 // table entries when collection is complete.
 
 // The value for the next pointer for maps not in the map list.
 static WeakMapBase * const WeakMapNotInList = reinterpret_cast<WeakMapBase *>(1);
 
-typedef Vector<WeakMapBase *, 0, SystemAllocPolicy> WeakMapVector;
+typedef HashSet<WeakMapBase *, DefaultHasher<WeakMapBase *>, SystemAllocPolicy> WeakMapSet;
 
 // Common base class for all WeakMap specializations. The collector uses this to call
 // their markIteratively and sweep methods.
 class WeakMapBase {
   public:
     WeakMapBase(JSObject *memOf, JSCompartment *c);
     virtual ~WeakMapBase();
 
-    void trace(JSTracer *tracer) {
-        if (IS_GC_MARKING_TRACER(tracer)) {
-            // We don't do anything with a WeakMap at trace time. Rather, we wait until as
-            // many keys as possible have been marked, and add ourselves to the list of
-            // known-live WeakMaps to be scanned in the iterative marking phase, by
-            // markAllIteratively.
-            JS_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps);
-
-            // Add ourselves to the list if we are not already in the list. We can already
-            // be in the list if the weak map is marked more than once due delayed marking.
-            if (next == WeakMapNotInList) {
-                next = compartment->gcWeakMapList;
-                compartment->gcWeakMapList = this;
-            }
-        } else {
-            // If we're not actually doing garbage collection, the keys won't be marked
-            // nicely as needed by the true ephemeral marking algorithm --- custom tracers
-            // such as the cycle collector must use their own means for cycle detection.
-            // So here we do a conservative approximation: pretend all keys are live.
-            if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps)
-                return;
-
-            nonMarkingTraceValues(tracer);
-            if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues)
-                nonMarkingTraceKeys(tracer);
-        }
-    }
+    void trace(JSTracer *tracer);
 
     // Garbage collector entry points.
 
+    // Unmark all weak maps in a compartment.
+    static void unmarkCompartment(JSCompartment *c);
+
     // Check all weak maps in a compartment that have been marked as live in this garbage
     // collection, and mark the values of all entries that have become strong references
     // to them. Return true if we marked any new values, indicating that we need to make
     // another pass. In other words, mark my marked maps' marked members' mid-collection.
     static bool markCompartmentIteratively(JSCompartment *c, JSTracer *tracer);
 
-    // Remove entries whose keys are dead from all weak maps in a compartment marked as
-    // live in this garbage collection.
+    // Add zone edges for weakmaps with key delegates in a different zone.
+    static bool findZoneEdgesForCompartment(JSCompartment *c);
+
+    // Sweep the weak maps in a compartment, removing dead weak maps and removing
+    // entries of live weak maps whose keys are dead.
     static void sweepCompartment(JSCompartment *c);
 
     // Trace all delayed weak map bindings. Used by the cycle collector.
     static void traceAllMappings(WeakMapTracer *tracer);
 
     bool isInList() { return next != WeakMapNotInList; }
-    void check() { JS_ASSERT(!isInList()); }
 
-    // Remove everything from the weak map list for a compartment.
-    static void resetCompartmentWeakMapList(JSCompartment *c);
+    // Save information about which weak maps are marked for a compartment.
+    static bool saveCompartmentMarkedWeakMaps(JSCompartment *c, WeakMapSet &markedWeakMaps);
 
-    // Save the live weak map list for a compartment, appending the data to a vector.
-    static bool saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector);
+    // Restore information about which weak maps are marked for many compartments.
+    static void restoreCompartmentMarkedWeakMaps(WeakMapSet &markedWeakMaps);
 
-    // Restore live weak map lists for multiple compartments from a vector.
-    static void restoreCompartmentWeakMapLists(WeakMapVector &vector);
-
-    // Remove a weakmap from the live weakmaps list
+    // Remove a weakmap from its compartment's weakmaps list.
     static void removeWeakMapFromList(WeakMapBase *weakmap);
 
   protected:
     // Instance member functions called by the above. Instantiations of WeakMap override
     // these with definitions appropriate for their Key and Value types.
     virtual void nonMarkingTraceKeys(JSTracer *tracer) = 0;
     virtual void nonMarkingTraceValues(JSTracer *tracer) = 0;
     virtual bool markIteratively(JSTracer *tracer) = 0;
+    virtual bool findZoneEdges() = 0;
     virtual void sweep() = 0;
     virtual void traceMappings(WeakMapTracer *tracer) = 0;
+    virtual void finish() = 0;
 
     // Object that this weak map is part of, if any.
     JSObject *memberOf;
 
     // Compartment that this weak map is part of.
     JSCompartment *compartment;
 
-  private:
-    // Link in a list of WeakMaps to mark iteratively and sweep in this garbage
-    // collection, headed by JSCompartment::gcWeakMapList. The last element of
-    // the list has nullptr as its next. Maps not in the list have
-    // WeakMapNotInList as their next.  We must distinguish these cases to
-    // avoid creating infinite lists when a weak map gets traced twice due to
-    // delayed marking.
+    // Link in a list of all WeakMaps in a compartment, headed by
+    // JSCompartment::gcWeakMapList. The last element of the list has nullptr as
+    // its next. Maps not in the list have WeakMapNotInList as their next.
     WeakMapBase *next;
+
+    // Whether this object has been traced during garbage collection.
+    bool marked;
 };
 
 template <class Key, class Value,
           class HashPolicy = DefaultHasher<Key> >
 class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, public WeakMapBase
 {
   public:
     typedef HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy> Base;
     typedef typename Base::Enum Enum;
     typedef typename Base::Lookup Lookup;
     typedef typename Base::Range Range;
 
     explicit WeakMap(JSContext *cx, JSObject *memOf = nullptr)
         : Base(cx->runtime()), WeakMapBase(memOf, cx->compartment()) { }
 
+    bool init(uint32_t len = 16) {
+        if (!Base::init(len))
+            return false;
+        next = compartment->gcWeakMapList;
+        compartment->gcWeakMapList = this;
+        marked = JS::IsIncrementalGCInProgress(compartment->runtimeFromMainThread());
+        return true;
+    }
+
   private:
     bool markValue(JSTracer *trc, Value *x) {
         if (gc::IsMarked(x))
             return false;
         gc::Mark(trc, x, "WeakMap entry value");
         JS_ASSERT(gc::IsMarked(x));
         return true;
     }
@@ -195,32 +181,41 @@ class WeakMap : public HashMap<Key, Valu
                     entryMoved(e, key);
                 markedAny = true;
             }
             key.unsafeSet(nullptr);
         }
         return markedAny;
     }
 
+    bool findZoneEdges() {
+        // This is overridden by ObjectValueMap.
+        return true;
+    }
+
     void sweep() {
         /* Remove all entries whose keys remain unmarked. */
         for (Enum e(*this); !e.empty(); e.popFront()) {
             Key k(e.front().key());
             if (gc::IsAboutToBeFinalized(&k))
                 e.removeFront();
             else if (k != e.front().key())
                 entryMoved(e, k);
         }
         /*
          * Once we've swept, all remaining edges should stay within the
          * known-live part of the graph.
          */
         assertEntriesNotAboutToBeFinalized();
     }
 
+    void finish() {
+        Base::finish();
+    }
+
     /* memberOf can be nullptr, which means that the map is not part of a JSObject. */
     void traceMappings(WeakMapTracer *tracer) {
         for (Range r = Base::all(); !r.empty(); r.popFront()) {
             gc::Cell *key = gc::ToMarkable(r.front().key());
             gc::Cell *value = gc::ToMarkable(r.front().value());
             if (key && value) {
                 tracer->callback(tracer, memberOf,
                                  key, gc::TraceKind(r.front().key()),
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -566,16 +566,20 @@ class ObjectImpl : public gc::BarrieredC
 
     types::TypeObject *typeRaw() const {
         return type_;
     }
 
     uint32_t numFixedSlots() const {
         return reinterpret_cast<const shadow::Object *>(this)->numFixedSlots();
     }
+    uint32_t numUsedFixedSlots() const {
+        uint32_t nslots = lastProperty()->slotSpan(getClass());
+        return Min(nslots, numFixedSlots());
+    }
 
     /*
      * Whether this is the only object which has its specified type. This
      * object will have its type constructed lazily as needed by analysis.
      */
     bool hasSingletonType() const {
         return !!type_->singleton();
     }
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -292,17 +292,17 @@ JSRuntime::init(uint32_t maxbytes)
     if (!gc.init(maxbytes))
         return false;
 
     const char *size = getenv("JSGC_MARK_STACK_LIMIT");
     if (size)
         SetMarkStackLimit(this, atoi(size));
 
     ScopedJSDeletePtr<Zone> atomsZone(new_<Zone>(this));
-    if (!atomsZone)
+    if (!atomsZone || !atomsZone->init())
         return false;
 
     JS::CompartmentOptions options;
     ScopedJSDeletePtr<JSCompartment> atomsCompartment(new_<JSCompartment>(atomsZone.get(), options));
     if (!atomsCompartment || !atomsCompartment->init(nullptr))
         return false;
 
     gc.zones.append(atomsZone.get());
--- a/js/src/vm/String-inl.h
+++ b/js/src/vm/String-inl.h
@@ -20,23 +20,32 @@
 namespace js {
 
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE JSInlineString *
 NewFatInlineString(ThreadSafeContext *cx, JS::Latin1Chars chars)
 {
     size_t len = chars.length();
     JS_ASSERT(JSFatInlineString::lengthFits(len));
-    JSInlineString *str = JSInlineString::lengthFits(len)
-                          ? JSInlineString::new_<allowGC>(cx)
-                          : JSFatInlineString::new_<allowGC>(cx);
-    if (!str)
-        return nullptr;
 
-    jschar *p = str->init(len);
+    JSInlineString *str;
+    jschar *p;
+    if (JSInlineString::lengthFits(len)) {
+        str = JSInlineString::new_<allowGC>(cx);
+        if (!str)
+            return nullptr;
+        p = str->init(len);
+    } else {
+        JSFatInlineString *fatstr = JSFatInlineString::new_<allowGC>(cx);
+        if (!fatstr)
+            return nullptr;
+        p = fatstr->init(len);
+        str = fatstr;
+    }
+
     for (size_t i = 0; i < len; ++i)
         p[i] = static_cast<jschar>(chars[i]);
     p[len] = '\0';
     return str;
 }
 
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE JSInlineString *
@@ -44,23 +53,32 @@ NewFatInlineString(ExclusiveContext *cx,
 {
     size_t len = chars.length();
 
     /*
      * Don't bother trying to find a static atom; measurement shows that not
      * many get here (for one, Atomize is catching them).
      */
     JS_ASSERT(JSFatInlineString::lengthFits(len));
-    JSInlineString *str = JSInlineString::lengthFits(len)
-                          ? JSInlineString::new_<allowGC>(cx)
-                          : JSFatInlineString::new_<allowGC>(cx);
-    if (!str)
-        return nullptr;
 
-    jschar *storage = str->init(len);
+    JSInlineString *str;
+    jschar *storage;
+    if (JSInlineString::lengthFits(len)) {
+        str = JSInlineString::new_<allowGC>(cx);
+        if (!str)
+            return nullptr;
+        storage = str->init(len);
+    } else {
+        JSFatInlineString *fatstr = JSFatInlineString::new_<allowGC>(cx);
+        if (!fatstr)
+            return nullptr;
+        storage = fatstr->init(len);
+        str = fatstr;
+    }
+
     mozilla::PodCopy(storage, chars.start().get(), len);
     storage[len] = 0;
     return str;
 }
 
 static inline void
 StringWriteBarrierPost(js::ThreadSafeContext *maybecx, JSString **strp)
 {
@@ -82,21 +100,22 @@ JSString::validateLength(js::ThreadSafeC
     }
 
     return true;
 }
 
 MOZ_ALWAYS_INLINE void
 JSRope::init(js::ThreadSafeContext *cx, JSString *left, JSString *right, size_t length)
 {
-    d.u0.lengthAndFlags = buildLengthAndFlags(length, ROPE_FLAGS);
-    d.u1.left = left;
-    d.s.u2.right = right;
-    js::StringWriteBarrierPost(cx, &d.u1.left);
-    js::StringWriteBarrierPost(cx, &d.s.u2.right);
+    d.u1.length = length;
+    d.u1.flags = ROPE_FLAGS;
+    d.s.u2.left = left;
+    d.s.u3.right = right;
+    js::StringWriteBarrierPost(cx, &d.s.u2.left);
+    js::StringWriteBarrierPost(cx, &d.s.u3.right);
 }
 
 template <js::AllowGC allowGC>
 MOZ_ALWAYS_INLINE JSRope *
 JSRope::new_(js::ThreadSafeContext *cx,
              typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
              typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
              size_t length)
@@ -108,29 +127,30 @@ JSRope::new_(js::ThreadSafeContext *cx,
         return nullptr;
     str->init(cx, left, right, length);
     return str;
 }
 
 inline void
 JSRope::markChildren(JSTracer *trc)
 {
-    js::gc::MarkStringUnbarriered(trc, &d.u1.left, "left child");
-    js::gc::MarkStringUnbarriered(trc, &d.s.u2.right, "right child");
+    js::gc::MarkStringUnbarriered(trc, &d.s.u2.left, "left child");
+    js::gc::MarkStringUnbarriered(trc, &d.s.u3.right, "right child");
 }
 
 MOZ_ALWAYS_INLINE void
 JSDependentString::init(js::ThreadSafeContext *cx, JSLinearString *base, const jschar *chars,
                         size_t length)
 {
     JS_ASSERT(!js::IsPoisonedPtr(base));
-    d.u0.lengthAndFlags = buildLengthAndFlags(length, DEPENDENT_FLAGS);
-    d.u1.chars = chars;
-    d.s.u2.base = base;
-    js::StringWriteBarrierPost(cx, reinterpret_cast<JSString **>(&d.s.u2.base));
+    d.u1.length = length;
+    d.u1.flags = DEPENDENT_FLAGS;
+    d.s.u2.nonInlineChars = chars;
+    d.s.u3.base = base;
+    js::StringWriteBarrierPost(cx, reinterpret_cast<JSString **>(&d.s.u3.base));
 }
 
 MOZ_ALWAYS_INLINE JSLinearString *
 JSDependentString::new_(js::ExclusiveContext *cx,
                         JSLinearString *baseArg, const jschar *chars, size_t length)
 {
     /* Try to avoid long chains of dependent strings. */
     while (baseArg->isDependent())
@@ -174,24 +194,25 @@ JSDependentString::new_(js::ExclusiveCon
     str->init(cx, base, chars, length);
     return str;
 }
 
 inline void
 JSString::markBase(JSTracer *trc)
 {
     JS_ASSERT(hasBase());
-    js::gc::MarkStringUnbarriered(trc, &d.s.u2.base, "base");
+    js::gc::MarkStringUnbarriered(trc, &d.s.u3.base, "base");
 }
 
 MOZ_ALWAYS_INLINE void
 JSFlatString::init(const jschar *chars, size_t length)
 {
-    d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
-    d.u1.chars = chars;
+    d.u1.length = length;
+    d.u1.flags = FLAT_BIT;
+    d.s.u2.nonInlineChars = chars;
 }
 
 template <js::AllowGC allowGC>
 MOZ_ALWAYS_INLINE JSFlatString *
 JSFlatString::new_(js::ThreadSafeContext *cx, const jschar *chars, size_t length)
 {
     JS_ASSERT(chars[length] == jschar(0));
 
@@ -224,44 +245,47 @@ MOZ_ALWAYS_INLINE JSInlineString *
 JSInlineString::new_(js::ThreadSafeContext *cx)
 {
     return (JSInlineString *)js_NewGCString<allowGC>(cx);
 }
 
 MOZ_ALWAYS_INLINE jschar *
 JSInlineString::init(size_t length)
 {
-    d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
-    d.u1.chars = d.inlineStorage;
-    JS_ASSERT(lengthFits(length) || (isFatInline() && JSFatInlineString::lengthFits(length)));
+    JS_ASSERT(lengthFits(length));
+    d.u1.length = length;
+    d.u1.flags = INIT_INLINE_FLAGS;
     return d.inlineStorage;
 }
 
-MOZ_ALWAYS_INLINE void
-JSInlineString::resetLength(size_t length)
+MOZ_ALWAYS_INLINE jschar *
+JSFatInlineString::init(size_t length)
 {
-    d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
-    JS_ASSERT(lengthFits(length) || (isFatInline() && JSFatInlineString::lengthFits(length)));
+    JS_ASSERT(lengthFits(length));
+    d.u1.length = length;
+    d.u1.flags = INIT_FAT_INLINE_FLAGS;
+    return d.inlineStorage;
 }
 
 template <js::AllowGC allowGC>
 MOZ_ALWAYS_INLINE JSFatInlineString *
 JSFatInlineString::new_(js::ThreadSafeContext *cx)
 {
     return js_NewGCFatInlineString<allowGC>(cx);
 }
 
 MOZ_ALWAYS_INLINE void
 JSExternalString::init(const jschar *chars, size_t length, const JSStringFinalizer *fin)
 {
     JS_ASSERT(fin);
     JS_ASSERT(fin->finalize);
-    d.u0.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
-    d.u1.chars = chars;
-    d.s.u2.externalFinalizer = fin;
+    d.u1.length = length;
+    d.u1.flags = EXTERNAL_FLAGS;
+    d.s.u2.nonInlineChars = chars;
+    d.s.u3.externalFinalizer = fin;
 }
 
 MOZ_ALWAYS_INLINE JSExternalString *
 JSExternalString::new_(JSContext *cx, const jschar *chars, size_t length,
                        const JSStringFinalizer *fin)
 {
     JS_ASSERT(chars[length] == 0);
 
@@ -309,39 +333,39 @@ JSString::finalize(js::FreeOp *fop)
         JS_ASSERT(isDependent() || isRope());
 }
 
 inline void
 JSFlatString::finalize(js::FreeOp *fop)
 {
     JS_ASSERT(getAllocKind() != js::gc::FINALIZE_FAT_INLINE_STRING);
 
-    if (chars() != d.inlineStorage)
-        fop->free_(const_cast<jschar *>(chars()));
+    if (!isInline())
+        fop->free_(const_cast<jschar *>(nonInlineChars()));
 }
 
 inline void
 JSFatInlineString::finalize(js::FreeOp *fop)
 {
     JS_ASSERT(getAllocKind() == js::gc::FINALIZE_FAT_INLINE_STRING);
 
-    if (chars() != d.inlineStorage)
-        fop->free_(const_cast<jschar *>(chars()));
+    if (!isInline())
+        fop->free_(const_cast<jschar *>(nonInlineChars()));
 }
 
 inline void
 JSAtom::finalize(js::FreeOp *fop)
 {
     JS_ASSERT(JSString::isAtom());
     JS_ASSERT(JSString::isFlat());
 
-    if (chars() != d.inlineStorage)
-        fop->free_(const_cast<jschar *>(chars()));
+    if (!isInline())
+        fop->free_(const_cast<jschar *>(nonInlineChars()));
 }
 
 inline void
 JSExternalString::finalize(js::FreeOp *fop)
 {
     const JSStringFinalizer *fin = externalFinalizer();
-    fin->finalize(fin, const_cast<jschar *>(chars()));
+    fin->finalize(fin, const_cast<jschar *>(nonInlineChars()));
 }
 
 #endif /* vm_String_inl_h */
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -17,35 +17,16 @@
 #include "jscompartmentinlines.h"
 
 using namespace js;
 
 using mozilla::PodCopy;
 using mozilla::RangedPtr;
 using mozilla::RoundUpPow2;
 
-bool
-JSString::isFatInline() const
-{
-    // It's possible for fat-inline strings to be converted to flat strings;
-    // as a result, checking just for the arena isn't enough to determine if a
-    // string is fat-inline.  Hence the isInline() check.
-    bool is_FatInline = (getAllocKind() == gc::FINALIZE_FAT_INLINE_STRING) && isInline();
-    JS_ASSERT_IF(is_FatInline, isFlat());
-    return is_FatInline;
-}
-
-bool
-JSString::isExternal() const
-{
-    bool is_external = (getAllocKind() == gc::FINALIZE_EXTERNAL_STRING);
-    JS_ASSERT_IF(is_external, isFlat());
-    return is_external;
-}
-
 size_t
 JSString::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
     // JSRope: do nothing, we'll count all children chars when we hit the leaf strings.
     if (isRope())
         return 0;
 
     JS_ASSERT(isLinear());
@@ -54,17 +35,17 @@ JSString::sizeOfExcludingThis(mozilla::M
     if (isDependent())
         return 0;
 
     JS_ASSERT(isFlat());
 
     // JSExtensibleString: count the full capacity, not just the used space.
     if (isExtensible()) {
         JSExtensibleString &extensible = asExtensible();
-        return mallocSizeOf(extensible.chars());
+        return mallocSizeOf(extensible.nonInlineChars());
     }
 
     // JSExternalString: don't count, the chars could be stored anywhere.
     if (isExternal())
         return 0;
 
     // JSInlineString, JSFatInlineString [JSInlineAtom, JSFatInlineAtom]: the chars are inline.
     if (isInline())
@@ -278,93 +259,94 @@ JSRope::flattenInternal(ExclusiveContext
         if (capacity >= wholeLength) {
             /*
              * Simulate a left-most traversal from the root to leftMost->leftChild()
              * via first_visit_node
              */
             JS_ASSERT(str->isRope());
             while (str != leftMostRope) {
                 if (b == WithIncrementalBarrier) {
-                    JSString::writeBarrierPre(str->d.u1.left);
-                    JSString::writeBarrierPre(str->d.s.u2.right);
+                    JSString::writeBarrierPre(str->d.s.u2.left);
+                    JSString::writeBarrierPre(str->d.s.u3.right);
                 }
-                JSString *child = str->d.u1.left;
+                JSString *child = str->d.s.u2.left;
                 JS_ASSERT(child->isRope());
-                str->d.u1.chars = left.chars();
-                child->d.u0.flattenData = uintptr_t(str) | Tag_VisitRightChild;
+                str->d.s.u2.nonInlineChars = left.nonInlineChars();
+                child->d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
                 str = child;
             }
             if (b == WithIncrementalBarrier) {
-                JSString::writeBarrierPre(str->d.u1.left);
-                JSString::writeBarrierPre(str->d.s.u2.right);
+                JSString::writeBarrierPre(str->d.s.u2.left);
+                JSString::writeBarrierPre(str->d.s.u3.right);
             }
-            str->d.u1.chars = left.chars();
+            str->d.s.u2.nonInlineChars = left.nonInlineChars();
             wholeCapacity = capacity;
-            wholeChars = const_cast<jschar *>(left.chars());
-            size_t bits = left.d.u0.lengthAndFlags;
-            pos = wholeChars + (bits >> LENGTH_SHIFT);
+            wholeChars = const_cast<jschar *>(left.nonInlineChars());
+            pos = wholeChars + left.d.u1.length;
             JS_STATIC_ASSERT(!(EXTENSIBLE_FLAGS & DEPENDENT_FLAGS));
-            left.d.u0.lengthAndFlags = bits ^ (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS);
-            left.d.s.u2.base = (JSLinearString *)this;  /* will be true on exit */
-            StringWriteBarrierPostRemove(maybecx, &left.d.u1.left);
-            StringWriteBarrierPost(maybecx, (JSString **)&left.d.s.u2.base);
+            left.d.u1.flags ^= (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS);
+            left.d.s.u3.base = (JSLinearString *)this;  /* will be true on exit */
+            StringWriteBarrierPostRemove(maybecx, &left.d.s.u2.left);
+            StringWriteBarrierPost(maybecx, (JSString **)&left.d.s.u3.base);
             goto visit_right_child;
         }
     }
 
     if (!AllocChars(maybecx, wholeLength, &wholeChars, &wholeCapacity))
         return nullptr;
 
     pos = wholeChars;
     first_visit_node: {
         if (b == WithIncrementalBarrier) {
-            JSString::writeBarrierPre(str->d.u1.left);
-            JSString::writeBarrierPre(str->d.s.u2.right);
+            JSString::writeBarrierPre(str->d.s.u2.left);
+            JSString::writeBarrierPre(str->d.s.u3.right);
         }
 
-        JSString &left = *str->d.u1.left;
-        str->d.u1.chars = pos;
-        StringWriteBarrierPostRemove(maybecx, &str->d.u1.left);
+        JSString &left = *str->d.s.u2.left;
+        str->d.s.u2.nonInlineChars = pos;
+        StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left);
         if (left.isRope()) {
             /* Return to this node when 'left' done, then goto visit_right_child. */
-            left.d.u0.flattenData = uintptr_t(str) | Tag_VisitRightChild;
+            left.d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
             str = &left;
             goto first_visit_node;
         }
         size_t len = left.length();
-        PodCopy(pos, left.d.u1.chars, len);
+        PodCopy(pos, left.asLinear().chars(), len);
         pos += len;
     }
     visit_right_child: {
-        JSString &right = *str->d.s.u2.right;
+        JSString &right = *str->d.s.u3.right;
         if (right.isRope()) {
             /* Return to this node when 'right' done, then goto finish_node. */
-            right.d.u0.flattenData = uintptr_t(str) | Tag_FinishNode;
+            right.d.u1.flattenData = uintptr_t(str) | Tag_FinishNode;
             str = &right;
             goto first_visit_node;
         }
         size_t len = right.length();
-        PodCopy(pos, right.d.u1.chars, len);
+        PodCopy(pos, right.asLinear().chars(), len);
         pos += len;
     }
     finish_node: {
         if (str == this) {
             JS_ASSERT(pos == wholeChars + wholeLength);
             *pos = '\0';
-            str->d.u0.lengthAndFlags = buildLengthAndFlags(wholeLength, EXTENSIBLE_FLAGS);
-            str->d.u1.chars = wholeChars;
-            str->d.s.u2.capacity = wholeCapacity;
-            StringWriteBarrierPostRemove(maybecx, &str->d.u1.left);
-            StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.right);
+            str->d.u1.length = wholeLength;
+            str->d.u1.flags = EXTENSIBLE_FLAGS;
+            str->d.s.u2.nonInlineChars = wholeChars;
+            str->d.s.u3.capacity = wholeCapacity;
+            StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left);
+            StringWriteBarrierPostRemove(maybecx, &str->d.s.u3.right);
             return &this->asFlat();
         }
-        uintptr_t flattenData = str->d.u0.flattenData;
-        str->d.u0.lengthAndFlags = buildLengthAndFlags(pos - str->d.u1.chars, DEPENDENT_FLAGS);
-        str->d.s.u2.base = (JSLinearString *)this;       /* will be true on exit */
-        StringWriteBarrierPost(maybecx, (JSString **)&str->d.s.u2.base);
+        uintptr_t flattenData = str->d.u1.flattenData;
+        str->d.u1.flags = DEPENDENT_FLAGS;
+        str->d.u1.length = pos - str->d.s.u2.nonInlineChars;
+        str->d.s.u3.base = (JSLinearString *)this;       /* will be true on exit */
+        StringWriteBarrierPost(maybecx, (JSString **)&str->d.s.u3.base);
         str = (JSString *)(flattenData & ~Tag_Mask);
         if ((flattenData & Tag_Mask) == Tag_VisitRightChild)
             goto visit_right_child;
         JS_ASSERT((flattenData & Tag_Mask) == Tag_FinishNode);
         goto finish_node;
     }
 }
 
@@ -434,17 +416,17 @@ JSDependentString::copyNonPureCharsZ(Thr
 {
     JS_ASSERT(JSString::isDependent());
 
     size_t n = length();
     jschar *s = cx->pod_malloc<jschar>(n + 1);
     if (!s)
         return false;
 
-    PodCopy(s, chars(), n);
+    PodCopy(s, nonInlineChars(), n);
     s[n] = 0;
 
     out.reset(s);
     return true;
 }
 
 JSFlatString *
 JSDependentString::undepend(ExclusiveContext *cx)
@@ -459,25 +441,25 @@ JSDependentString::undepend(ExclusiveCon
     JSString::writeBarrierPre(base());
 
     size_t n = length();
     size_t size = (n + 1) * sizeof(jschar);
     jschar *s = (jschar *) cx->malloc_(size);
     if (!s)
         return nullptr;
 
-    PodCopy(s, chars(), n);
+    PodCopy(s, nonInlineChars(), n);
     s[n] = 0;
-    d.u1.chars = s;
+    d.s.u2.nonInlineChars = s;
 
     /*
      * Transform *this into an undepended string so 'base' will remain rooted
      * for the benefit of any other dependent string that depends on *this.
      */
-    d.u0.lengthAndFlags = buildLengthAndFlags(n, UNDEPENDED_FLAGS);
+    d.u1.flags = UNDEPENDED_FLAGS;
 
     return &this->asFlat();
 }
 
 bool
 JSFlatString::isIndexSlow(uint32_t *indexp) const
 {
     const jschar *s = charsZ();
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -132,136 +132,150 @@ class JSString : public js::gc::Barriere
 {
   protected:
     static const size_t NUM_INLINE_CHARS = 2 * sizeof(void *) / sizeof(jschar);
 
     /* Fields only apply to string types commented on the right. */
     struct Data
     {
         union {
-            size_t                 lengthAndFlags;      /* JSString */
+            struct {
+                uint32_t           flags;               /* JSString */
+                uint32_t           length;              /* JSString */
+            };
             uintptr_t              flattenData;         /* JSRope (temporary while flattening) */
-        } u0;
-        union {
-            const jschar           *chars;              /* JSLinearString */
-            JSString               *left;               /* JSRope */
         } u1;
         union {
             jschar                 inlineStorage[NUM_INLINE_CHARS]; /* JS(Inline|FatInline)String */
             struct {
                 union {
+                    const jschar   *nonInlineChars;     /* JSLinearString, except JS(Inline|FatInline)String */
+                    JSString       *left;               /* JSRope */
+                } u2;
+                union {
                     JSLinearString *base;               /* JS(Dependent|Undepended)String */
                     JSString       *right;              /* JSRope */
                     size_t         capacity;            /* JSFlatString (extensible) */
                     const JSStringFinalizer *externalFinalizer;/* JSExternalString */
-                } u2;
+                } u3;
             } s;
         };
     } d;
 
   public:
     /* Flags exposed only for jits */
 
     /*
-     * The low LENGTH_SHIFT bits of lengthAndFlags are used to encode the type
-     * of the string. The remaining bits store the string length (which must be
-     * less or equal than MAX_LENGTH).
-     *
      * Instead of using a dense index to represent the most-derived type, string
      * types are encoded to allow single-op tests for hot queries (isRope,
      * isDependent, isFlat, isAtom) which, in view of subtyping, would require
      * slower (isX() || isY() || isZ()).
      *
      * The string type encoding can be summarized as follows. The "instance
      * encoding" entry for a type specifies the flag bits used to create a
      * string instance of that type. Abstract types have no instances and thus
      * have no such entry. The "subtype predicate" entry for a type specifies
      * the predicate used to query whether a JSString instance is subtype
      * (reflexively) of that type.
      *
-     *   Rope          0000       0000
-     *   Linear        -         !0000
-     *   HasBase       -          xxx1
-     *   Dependent     0001       0001
-     *   Flat          -          isLinear && !isDependent
-     *   Undepended    0011       0011
-     *   Extensible    0010       0010
-     *   Inline        0100       isFlat && !isExtensible && (u1.chars == inlineStorage)
-     *   FatInline     0100       isInline && header in FINALIZE_FAT_INLINE_STRING arena
-     *   External      0100       header in FINALIZE_EXTERNAL_STRING arena
-     *   Atom          -          1xxx
-     *   PermanentAtom 1100       1100
-     *   InlineAtom    -          isAtom && isInline
-     *   FatInlineAtom -          isAtom && isFatInline
+     *   String        Instance     Subtype
+     *   type          encoding     predicate
+     *   ------------------------------------
+     *   Rope          000000       000000
+     *   Linear        -           !000000
+     *   HasBase       -            xxxx1x
+     *   Dependent     000010       000010
+     *   Flat          -            xxxxx1
+     *   Undepended    000011       000011
+     *   Extensible    010001       010001
+     *   Inline        000101       xxx1xx
+     *   FatInline     010101       x1x1xx
+     *   External      100001       100001
+     *   Atom          001001       xx1xxx
+     *   PermanentAtom 101001       1x1xxx
+     *   InlineAtom    -            xx11xx
+     *   FatInlineAtom -            x111xx
+     *
+     * Note that the first 4 flag bits (from right to left in the previous table)
+     * have the following meaning and can be used for some hot queries:
+     *
+     *   Bit 0: IsFlat
+     *   Bit 1: HasBase (Dependent, Undepended)
+     *   Bit 2: IsInline (Inline, FatInline)
+     *   Bit 3: IsAtom (Atom, PermanentAtom)
      *
      *  "HasBase" here refers to the two string types that have a 'base' field:
      *  JSDependentString and JSUndependedString.
      *  A JSUndependedString is a JSDependentString which has been 'fixed' (by ensureFixed)
      *  to be null-terminated.  In such cases, the string must keep marking its base since
      *  there may be any number of *other* JSDependentStrings transitively depending on it.
      *
      */
 
-    static const size_t LENGTH_SHIFT          = 4;
-    static const size_t FLAGS_MASK            = JS_BITMASK(LENGTH_SHIFT);
-
-    static const size_t ROPE_FLAGS            = 0;
-    static const size_t DEPENDENT_FLAGS       = JS_BIT(0);
-    static const size_t UNDEPENDED_FLAGS      = JS_BIT(0) | JS_BIT(1);
-    static const size_t EXTENSIBLE_FLAGS      = JS_BIT(1);
-    static const size_t FIXED_FLAGS           = JS_BIT(2);
-
-    static const size_t INT32_MASK            = JS_BITMASK(3);
-    static const size_t INT32_FLAGS           = JS_BIT(1) | JS_BIT(2);
+    static const uint32_t FLAT_BIT              = JS_BIT(0);
+    static const uint32_t HAS_BASE_BIT          = JS_BIT(1);
+    static const uint32_t INLINE_CHARS_BIT      = JS_BIT(2);
+    static const uint32_t ATOM_BIT              = JS_BIT(3);
 
-    static const size_t HAS_BASE_BIT          = JS_BIT(0);
-    static const size_t PERMANENT_BIT         = JS_BIT(2);
-    static const size_t ATOM_BIT              = JS_BIT(3);
-
-    static const size_t PERMANENT_ATOM_FLAGS  = JS_BIT(2) | JS_BIT(3);
+    static const uint32_t ROPE_FLAGS            = 0;
+    static const uint32_t DEPENDENT_FLAGS       = HAS_BASE_BIT;
+    static const uint32_t UNDEPENDED_FLAGS      = FLAT_BIT | HAS_BASE_BIT;
+    static const uint32_t EXTENSIBLE_FLAGS      = FLAT_BIT | JS_BIT(4);
+    static const uint32_t EXTERNAL_FLAGS        = FLAT_BIT | JS_BIT(5);
 
-    static const size_t MAX_LENGTH            = JS_BIT(32 - LENGTH_SHIFT) - 1;
+    static const uint32_t FAT_INLINE_MASK       = INLINE_CHARS_BIT | JS_BIT(4);
+    static const uint32_t PERMANENT_ATOM_MASK   = ATOM_BIT | JS_BIT(5);
 
-    size_t buildLengthAndFlags(size_t length, size_t flags) {
-        JS_ASSERT(length <= MAX_LENGTH);
-        JS_ASSERT(flags <= FLAGS_MASK);
-        return (length << LENGTH_SHIFT) | flags;
-    }
+    /* Initial flags for inline and fat inline strings. */
+    static const uint32_t INIT_INLINE_FLAGS     = FLAT_BIT | INLINE_CHARS_BIT;
+    static const uint32_t INIT_FAT_INLINE_FLAGS = FLAT_BIT | FAT_INLINE_MASK;
+
+    static const uint32_t MAX_LENGTH            = JS_BIT(28) - 1;
 
     /*
      * Helper function to validate that a string of a given length is
      * representable by a JSString. An allocation overflow is reported if false
      * is returned.
      */
     static inline bool validateLength(js::ThreadSafeContext *maybecx, size_t length);
 
     static void staticAsserts() {
-        JS_STATIC_ASSERT(JS_BITS_PER_WORD >= 32);
-        JS_STATIC_ASSERT(((JSString::MAX_LENGTH << JSString::LENGTH_SHIFT) >>
-                           JSString::LENGTH_SHIFT) == JSString::MAX_LENGTH);
-        JS_STATIC_ASSERT(sizeof(JSString) ==
-                         offsetof(JSString, d.inlineStorage) + NUM_INLINE_CHARS * sizeof(jschar));
-        JS_STATIC_ASSERT(offsetof(JSString, d.u1.chars) ==
-                         offsetof(js::shadow::Atom, chars));
+        static_assert(JSString::MAX_LENGTH < UINT32_MAX, "Length must fit in 32 bits");
+        static_assert(sizeof(JSString) ==
+                      offsetof(JSString, d.inlineStorage) + NUM_INLINE_CHARS * sizeof(jschar),
+                      "NUM_INLINE_CHARS inline chars must fit in a JSString");
+
+        /* Ensure js::shadow::Atom has the same layout. */
+        using js::shadow::Atom;
+        static_assert(offsetof(JSString, d.u1.length) == offsetof(Atom, length),
+                      "shadow::Atom length offset must match JSString");
+        static_assert(offsetof(JSString, d.u1.flags) == offsetof(Atom, flags),
+                      "shadow::Atom flags offset must match JSString");
+        static_assert(offsetof(JSString, d.s.u2.nonInlineChars) == offsetof(Atom, nonInlineChars),
+                      "shadow::Atom nonInlineChars offset must match JSString");
+        static_assert(offsetof(JSString, d.inlineStorage) == offsetof(Atom, inlineStorage),
+                      "shadow::Atom inlineStorage offset must match JSString");
+        static_assert(INLINE_CHARS_BIT == Atom::INLINE_CHARS_BIT,
+                      "shadow::Atom::INLINE_CHARS_BIT must match JSString::INLINE_CHARS_BIT");
     }
 
     /* Avoid lame compile errors in JSRope::flatten */
     friend class JSRope;
 
   public:
     /* All strings have length. */
 
     MOZ_ALWAYS_INLINE
     size_t length() const {
-        return d.u0.lengthAndFlags >> LENGTH_SHIFT;
+        return d.u1.length;
     }
 
     MOZ_ALWAYS_INLINE
     bool empty() const {
-        return d.u0.lengthAndFlags <= FLAGS_MASK;
+        return d.u1.length == 0;
     }
 
     /*
      * All strings have a fallible operation to get an array of chars.
      * getCharsZ additionally ensures the array is null terminated.
      */
 
     inline const jschar *getChars(js::ExclusiveContext *cx);
@@ -293,17 +307,17 @@ class JSString : public js::gc::Barriere
     static bool ensureLinear(js::ExclusiveContext *cx, JSString *str) {
         return str->ensureLinear(cx) != nullptr;
     }
 
     /* Type query and debug-checked casts */
 
     MOZ_ALWAYS_INLINE
     bool isRope() const {
-        return (d.u0.lengthAndFlags & FLAGS_MASK) == ROPE_FLAGS;
+        return d.u1.flags == ROPE_FLAGS;
     }
 
     MOZ_ALWAYS_INLINE
     JSRope &asRope() const {
         JS_ASSERT(isRope());
         return *(JSRope *)this;
     }
 
@@ -315,117 +329,124 @@ class JSString : public js::gc::Barriere
     MOZ_ALWAYS_INLINE
     JSLinearString &asLinear() const {
         JS_ASSERT(JSString::isLinear());
         return *(JSLinearString *)this;
     }
 
     MOZ_ALWAYS_INLINE
     bool isDependent() const {
-        return (d.u0.lengthAndFlags & FLAGS_MASK) == DEPENDENT_FLAGS;
+        return d.u1.flags == DEPENDENT_FLAGS;
     }
 
     MOZ_ALWAYS_INLINE
     JSDependentString &asDependent() const {
         JS_ASSERT(isDependent());
         return *(JSDependentString *)this;
     }
 
     MOZ_ALWAYS_INLINE
     bool isFlat() const {
-        return isLinear() && !isDependent();
+        return d.u1.flags & FLAT_BIT;
     }
 
     MOZ_ALWAYS_INLINE
     JSFlatString &asFlat() const {
         JS_ASSERT(isFlat());
         return *(JSFlatString *)this;
     }
 
     MOZ_ALWAYS_INLINE
     bool isExtensible() const {
-        return (d.u0.lengthAndFlags & FLAGS_MASK) == EXTENSIBLE_FLAGS;
+        return d.u1.flags == EXTENSIBLE_FLAGS;
     }
 
     MOZ_ALWAYS_INLINE
     JSExtensibleString &asExtensible() const {
         JS_ASSERT(isExtensible());
         return *(JSExtensibleString *)this;
     }
 
     MOZ_ALWAYS_INLINE
     bool isInline() const {
-        return isFlat() && !isExtensible() && (d.u1.chars == d.inlineStorage);
+        return d.u1.flags & INLINE_CHARS_BIT;
     }
 
     MOZ_ALWAYS_INLINE
     JSInlineString &asInline() const {
         JS_ASSERT(isInline());
         return *(JSInlineString *)this;
     }
 
-    bool isFatInline() const;
+    MOZ_ALWAYS_INLINE
+    bool isFatInline() const {
+        return (d.u1.flags & FAT_INLINE_MASK) == FAT_INLINE_MASK;
+    }
 
     /* For hot code, prefer other type queries. */
-    bool isExternal() const;
+    bool isExternal() const {
+        return d.u1.flags == EXTERNAL_FLAGS;
+    }
 
     MOZ_ALWAYS_INLINE
     JSExternalString &asExternal() const {
         JS_ASSERT(isExternal());
         return *(JSExternalString *)this;
     }
 
     MOZ_ALWAYS_INLINE
     bool isUndepended() const {
-        return (d.u0.lengthAndFlags & FLAGS_MASK) == UNDEPENDED_FLAGS;
+        return d.u1.flags == UNDEPENDED_FLAGS;
     }
 
     MOZ_ALWAYS_INLINE
     bool isAtom() const {
-        return d.u0.lengthAndFlags & ATOM_BIT;
+        return d.u1.flags & ATOM_BIT;
     }
 
     MOZ_ALWAYS_INLINE
     bool isPermanentAtom() const {
-        return (d.u0.lengthAndFlags & FLAGS_MASK) == PERMANENT_ATOM_FLAGS;
+        return (d.u1.flags & PERMANENT_ATOM_MASK) == PERMANENT_ATOM_MASK;
     }
 
     MOZ_ALWAYS_INLINE
     JSAtom &asAtom() const {
         JS_ASSERT(isAtom());
         return *(JSAtom *)this;
     }
 
     /* Only called by the GC for dependent or undepended strings. */
 
     inline bool hasBase() const {
-        JS_STATIC_ASSERT((DEPENDENT_FLAGS | JS_BIT(1)) == UNDEPENDED_FLAGS);
-        return d.u0.lengthAndFlags & HAS_BASE_BIT;
+        return d.u1.flags & HAS_BASE_BIT;
     }
 
     inline JSLinearString *base() const;
 
     inline void markBase(JSTracer *trc);
 
     /* Only called by the GC for strings with the FINALIZE_STRING kind. */
 
     inline void finalize(js::FreeOp *fop);
 
     /* Gets the number of bytes that the chars take on the heap. */
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     /* Offsets for direct field from jit code. */
 
-    static size_t offsetOfLengthAndFlags() {
-        return offsetof(JSString, d.u0.lengthAndFlags);
+    static size_t offsetOfLength() {
+        return offsetof(JSString, d.u1.length);
+    }
+    static size_t offsetOfFlags() {
+        return offsetof(JSString, d.u1.flags);
     }
 
-    static size_t offsetOfChars() {
-        return offsetof(JSString, d.u1.chars);
+    static size_t offsetOfNonInlineChars() {
+        return offsetof(JSString, d.s.u2.nonInlineChars);
     }
 
     js::gc::AllocKind getAllocKind() const { return tenuredGetAllocKind(); }
 
     static inline js::ThingRootKind rootKind() { return js::THING_ROOT_STRING; }
 
 #ifdef DEBUG
     void dump();
@@ -478,55 +499,58 @@ class JSRope : public JSString
     template <js::AllowGC allowGC>
     static inline JSRope *new_(js::ThreadSafeContext *cx,
                                typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
                                typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
                                size_t length);
 
     inline JSString *leftChild() const {
         JS_ASSERT(isRope());
-        return d.u1.left;
+        return d.s.u2.left;
     }
 
     inline JSString *rightChild() const {
         JS_ASSERT(isRope());
-        return d.s.u2.right;
+        return d.s.u3.right;
     }
 
     inline void markChildren(JSTracer *trc);
 
     inline static size_t offsetOfLeft() {
-        return offsetof(JSRope, d.u1.left);
+        return offsetof(JSRope, d.s.u2.left);
     }
     inline static size_t offsetOfRight() {
-        return offsetof(JSRope, d.s.u2.right);
+        return offsetof(JSRope, d.s.u3.right);
     }
 };
 
 JS_STATIC_ASSERT(sizeof(JSRope) == sizeof(JSString));
 
 class JSLinearString : public JSString
 {
     friend class JSString;
 
     /* Vacuous and therefore unimplemented. */
     JSLinearString *ensureLinear(JSContext *cx) MOZ_DELETE;
     bool isLinear() const MOZ_DELETE;
     JSLinearString &asLinear() const MOZ_DELETE;
 
   public:
     MOZ_ALWAYS_INLINE
-    const jschar *chars() const {
-        JS_ASSERT(JSString::isLinear());
-        return d.u1.chars;
+    const jschar *nonInlineChars() const {
+        JS_ASSERT(!isInline());
+        return d.s.u2.nonInlineChars;
     }
 
+    MOZ_ALWAYS_INLINE
+    const jschar *chars() const;
+
     JS::TwoByteChars range() const {
         JS_ASSERT(JSString::isLinear());
-        return JS::TwoByteChars(d.u1.chars, length());
+        return JS::TwoByteChars(chars(), length());
     }
 };
 
 JS_STATIC_ASSERT(sizeof(JSLinearString) == sizeof(JSString));
 
 class JSDependentString : public JSLinearString
 {
     bool copyNonPureCharsZ(js::ThreadSafeContext *cx, js::ScopedJSFreePtr<jschar> &out) const;
@@ -536,16 +560,19 @@ class JSDependentString : public JSLinea
 
     void init(js::ThreadSafeContext *cx, JSLinearString *base, const jschar *chars,
               size_t length);
 
     /* Vacuous and therefore unimplemented. */
     bool isDependent() const MOZ_DELETE;
     JSDependentString &asDependent() const MOZ_DELETE;
 
+    /* Hide chars(), nonInlineChars() is more efficient. */
+    const jschar *chars() const MOZ_DELETE;
+
   public:
     static inline JSLinearString *new_(js::ExclusiveContext *cx, JSLinearString *base,
                                        const jschar *chars, size_t length);
 };
 
 JS_STATIC_ASSERT(sizeof(JSDependentString) == sizeof(JSString));
 
 class JSFlatString : public JSLinearString
@@ -588,58 +615,70 @@ class JSFlatString : public JSLinearStri
      */
     inline js::PropertyName *toPropertyName(JSContext *cx);
 
     /*
      * Once a JSFlatString sub-class has been added to the atom state, this
      * operation changes the string to the JSAtom type, in place.
      */
     MOZ_ALWAYS_INLINE JSAtom *morphAtomizedStringIntoAtom() {
-        d.u0.lengthAndFlags = buildLengthAndFlags(length(), ATOM_BIT);
+        d.u1.flags |= ATOM_BIT;
         return &asAtom();
     }
     MOZ_ALWAYS_INLINE JSAtom *morphAtomizedStringIntoPermanentAtom() {
-        d.u0.lengthAndFlags = buildLengthAndFlags(length(), PERMANENT_ATOM_FLAGS);
+        d.u1.flags |= PERMANENT_ATOM_MASK;
         return &asAtom();
     }
 
     inline void finalize(js::FreeOp *fop);
 };
 
 JS_STATIC_ASSERT(sizeof(JSFlatString) == sizeof(JSString));
 
 class JSExtensibleString : public JSFlatString
 {
     /* Vacuous and therefore unimplemented. */
     bool isExtensible() const MOZ_DELETE;
     JSExtensibleString &asExtensible() const MOZ_DELETE;
 
+    /* Hide chars(), nonInlineChars() is more efficient. */
+    const jschar *chars() const MOZ_DELETE;
+
   public:
     MOZ_ALWAYS_INLINE
     size_t capacity() const {
         JS_ASSERT(JSString::isExtensible());
-        return d.s.u2.capacity;
+        return d.s.u3.capacity;
     }
 };
 
 JS_STATIC_ASSERT(sizeof(JSExtensibleString) == sizeof(JSString));
 
 /* On 32-bit platforms, MAX_INLINE_LENGTH is 4. On 64-bit platforms it is 8. */
 class JSInlineString : public JSFlatString
 {
     static const size_t MAX_INLINE_LENGTH = NUM_INLINE_CHARS - 1;
 
+    /* Hide chars(), inlineChars() is more efficient. */
+    const jschar *chars() const MOZ_DELETE;
+
   public:
     template <js::AllowGC allowGC>
     static inline JSInlineString *new_(js::ThreadSafeContext *cx);
 
     inline jschar *init(size_t length);
 
     inline void resetLength(size_t length);
 
+    MOZ_ALWAYS_INLINE
+    const jschar *inlineChars() const {
+        const char *p = reinterpret_cast<const char *>(this);
+        return reinterpret_cast<const jschar *>(p + offsetOfInlineStorage());
+    }
+
     static bool lengthFits(size_t length) {
         return length <= MAX_INLINE_LENGTH;
     }
 
     static size_t offsetOfInlineStorage() {
         return offsetof(JSInlineString, d.inlineStorage);
     }
 };
@@ -663,27 +702,32 @@ class JSFatInlineString : public JSInlin
 
     static void staticAsserts() {
         JS_STATIC_ASSERT((INLINE_EXTENSION_CHARS * sizeof(jschar)) % js::gc::CellSize == 0);
         JS_STATIC_ASSERT(MAX_FAT_INLINE_LENGTH + 1 ==
                          (sizeof(JSFatInlineString) -
                           offsetof(JSFatInlineString, d.inlineStorage)) / sizeof(jschar));
     }
 
+    /* Hide chars(), inlineChars() is more efficient. */
+    const jschar *chars() const MOZ_DELETE;
+
   protected: /* to fool clang into not warning this is unused */
     jschar inlineStorageExtension[INLINE_EXTENSION_CHARS];
 
   public:
     template <js::AllowGC allowGC>
     static inline JSFatInlineString *new_(js::ThreadSafeContext *cx);
 
     static const size_t MAX_FAT_INLINE_LENGTH = JSString::NUM_INLINE_CHARS +
                                                 INLINE_EXTENSION_CHARS
                                                 -1 /* null terminator */;
 
+    inline jschar *init(size_t length);
+
     static bool lengthFits(size_t length) {
         return length <= MAX_FAT_INLINE_LENGTH;
     }
 
     /* Only called by the GC for strings with the FINALIZE_FAT_INLINE_STRING kind. */
 
     MOZ_ALWAYS_INLINE void finalize(js::FreeOp *fop);
 };
@@ -693,23 +737,26 @@ JS_STATIC_ASSERT(sizeof(JSFatInlineStrin
 class JSExternalString : public JSFlatString
 {
     void init(const jschar *chars, size_t length, const JSStringFinalizer *fin);
 
     /* Vacuous and therefore unimplemented. */
     bool isExternal() const MOZ_DELETE;
     JSExternalString &asExternal() const MOZ_DELETE;
 
+    /* Hide chars(), nonInlineChars() is more efficient. */
+    const jschar *chars() const MOZ_DELETE;
+
   public:
     static inline JSExternalString *new_(JSContext *cx, const jschar *chars, size_t length,
                                          const JSStringFinalizer *fin);
 
     const JSStringFinalizer *externalFinalizer() const {
         JS_ASSERT(JSString::isExternal());
-        return d.s.u2.externalFinalizer;
+        return d.s.u3.externalFinalizer;
     }
 
     /* Only called by the GC for strings with the FINALIZE_EXTERNAL_STRING kind. */
 
     inline void finalize(js::FreeOp *fop);
 };
 
 JS_STATIC_ASSERT(sizeof(JSExternalString) == sizeof(JSString));
@@ -734,23 +781,23 @@ class JSAtom : public JSFlatString
   public:
     /* Returns the PropertyName for this.  isIndex() must be false. */
     inline js::PropertyName *asPropertyName();
 
     inline void finalize(js::FreeOp *fop);
 
     MOZ_ALWAYS_INLINE
     bool isPermanent() const {
-        return d.u0.lengthAndFlags & PERMANENT_BIT;
+        return JSString::isPermanentAtom();
     }
 
     // Transform this atom into a permanent atom. This is only done during
     // initialization of the runtime.
     MOZ_ALWAYS_INLINE void morphIntoPermanentAtom() {
-        d.u0.lengthAndFlags = buildLengthAndFlags(length(), PERMANENT_ATOM_FLAGS);
+        d.u1.flags |= PERMANENT_ATOM_MASK;
     }
 
 #ifdef DEBUG
     void dump();
 #endif
 };
 
 JS_STATIC_ASSERT(sizeof(JSAtom) == sizeof(JSString));
@@ -1043,18 +1090,25 @@ JSString::ensureFlat(js::ExclusiveContex
              ? asDependent().undepend(cx)
              : asRope().flatten(cx);
 }
 
 inline JSLinearString *
 JSString::base() const
 {
     JS_ASSERT(hasBase());
-    JS_ASSERT(!d.s.u2.base->isInline());
-    return d.s.u2.base;
+    JS_ASSERT(!d.s.u3.base->isInline());
+    return d.s.u3.base;
+}
+
+MOZ_ALWAYS_INLINE const jschar *
+JSLinearString::chars() const
+{
+    JS_ASSERT(JSString::isLinear());
+    return isInline() ? asInline().inlineChars() : nonInlineChars();
 }
 
 inline js::PropertyName *
 JSAtom::asPropertyName()
 {
 #ifdef DEBUG
     uint32_t dummy;
     JS_ASSERT(!isIndex(&dummy));
--- a/js/src/vm/TraceLogging.h
+++ b/js/src/vm/TraceLogging.h
@@ -132,16 +132,17 @@ namespace jit {
     _(IrregexpExecute)                                \
     _(VM)                                             \
                                                       \
     /* Specific passes during ion compilation */      \
     _(SplitCriticalEdges)                             \
     _(RenumberBlocks)                                 \
     _(DominatorTree)                                  \
     _(PhiAnalysis)                                    \
+    _(MakeLoopsContiguous)                            \
     _(ApplyTypes)                                     \
     _(ParallelSafetyAnalysis)                         \
     _(AliasAnalysis)                                  \
     _(GVN)                                            \
     _(UCE)                                            \
     _(LICM)                                           \
     _(RangeAnalysis)                                  \
     _(EffectiveAddressAnalysis)                       \
--- a/js/src/vm/WeakMapObject.h
+++ b/js/src/vm/WeakMapObject.h
@@ -7,17 +7,24 @@
 #ifndef vm_WeakMapObject_h
 #define vm_WeakMapObject_h
 
 #include "jsobj.h"
 #include "jsweakmap.h"
 
 namespace js {
 
-typedef WeakMap<PreBarrieredObject, RelocatableValue> ObjectValueMap;
+class ObjectValueMap : public WeakMap<PreBarrieredObject, RelocatableValue>
+{
+  public:
+    ObjectValueMap(JSContext *cx, JSObject *obj)
+      : WeakMap<PreBarrieredObject, RelocatableValue>(cx, obj) {}
+
+    virtual bool findZoneEdges();
+};
 
 class WeakMapObject : public JSObject
 {
   public:
     static const Class class_;
 
     ObjectValueMap *getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
 };
--- a/js/src/vm/WeakMapPtr.cpp
+++ b/js/src/vm/WeakMapPtr.cpp
@@ -53,17 +53,16 @@ void
 JS::WeakMapPtr<K, V>::destroy()
 {
     MOZ_ASSERT(initialized());
     auto map = Utils<K, V>::cast(ptr);
     // If this destruction happens mid-GC, we might be in the compartment's list
     // of known live weakmaps. If we are, remove ourselves before deleting.
     if (map->isInList())
         WeakMapBase::removeWeakMapFromList(map);
-    map->check();
     js_delete(map);
     ptr = nullptr;
 }
 
 template <typename K, typename V>
 bool
 JS::WeakMapPtr<K, V>::init(JSContext *cx)
 {
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -2126,22 +2126,21 @@ nsListControlFrame::KeyDown(nsIDOMEvent*
     case NS_VK_DOWN:
     case NS_VK_RIGHT:
       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
                                 static_cast<int32_t>(numOptions),
                                 1, 1);
       break;
     case NS_VK_RETURN:
       if (IsInDropDownMode()) {
-        // If the select element is a dropdown style, Enter key should be
-        // consumed everytime since Enter key may be pressed accidentally after
-        // the dropdown is closed by Enter key press.
-        aKeyEvent->PreventDefault();
+        if (mComboboxFrame->IsDroppedDown()) {
+          // If the select element is a dropdown style, Enter key should be
+          // consumed while the dropdown is open for security.
+          aKeyEvent->PreventDefault();
 
-        if (mComboboxFrame->IsDroppedDown()) {
           nsWeakFrame weakFrame(this);
           ComboboxFinish(mEndSelectionIndex);
           if (!weakFrame.IsAlive()) {
             return NS_OK;
           }
         }
         // XXX This is strange. On other browsers, "change" event is fired
         //     immediately after the selected item is changed rather than
--- a/layout/forms/test/test_bug935876.html
+++ b/layout/forms/test/test_bug935876.html
@@ -316,17 +316,17 @@ function runTests()
     for (var i = 0; i < 2; i++) {
       reset()
       synthesizeKey("VK_HOME", {});
       check(true, "Home key on combobox #" + i);
     }
 
     reset()
     synthesizeKey("VK_RETURN", {});
-    check(true, "Enter key on combobox");
+    check(false, "Enter key on combobox");
 
     reset()
     synthesizeKey("VK_ESCAPE", {});
     check(true, "Esc key on combobox");
 
     if (!kIsWin) {
       reset()
       synthesizeKey("VK_F4", {});
--- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp
@@ -128,16 +128,18 @@ public:
   }
 
   void Stop() {
     MonitorAutoLock lock(mMonitor);
     mEnding = true;
     lock.NotifyAll(); // In case Run() is waiting.
 
     if (mThread != nullptr) {
+      MonitorAutoUnlock unlock(mMonitor);
+      CODEC_LOGD("OMXOutputDrain thread shutdown");
       mThread->Shutdown();
       mThread = nullptr;
     }
     CODEC_LOGD("OMXOutputDrain stopped");
   }
 
   void QueueInput(const EncodedFrame& aFrame)
   {
@@ -686,16 +688,21 @@ WebrtcOMXH264VideoEncoder::Encode(const 
   if (!mOMXConfigured) {
     mOMX->Configure(mWidth, mHeight, mFrameRate,
                     OMXVideoEncoder::BlobFormat::AVC_NAL);
     mOMXConfigured = true;
     CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p start OMX with image size:%ux%u",
                this, mWidth, mHeight);
   }
 
+  if (aFrameTypes && aFrameTypes->size() &&
+      ((*aFrameTypes)[0] == webrtc::kKeyFrame)) {
+    mOMX->RequestIDRFrame();
+  }
+
   // Wrap I420VideoFrame input with PlanarYCbCrImage for OMXVideoEncoder.
   layers::PlanarYCbCrData yuvData;
   yuvData.mYChannel = const_cast<uint8_t*>(aInputImage.buffer(webrtc::kYPlane));
   yuvData.mYSize = gfx::IntSize(aInputImage.width(), aInputImage.height());
   yuvData.mYStride = aInputImage.stride(webrtc::kYPlane);
   MOZ_ASSERT(aInputImage.stride(webrtc::kUPlane) == aInputImage.stride(webrtc::kVPlane));
   yuvData.mCbCrStride = aInputImage.stride(webrtc::kUPlane);
   yuvData.mCbChannel = const_cast<uint8_t*>(aInputImage.buffer(webrtc::kUPlane));
--- a/mobile/android/base/preferences/PanelsPreference.java
+++ b/mobile/android/base/preferences/PanelsPreference.java
@@ -25,43 +25,40 @@ public class PanelsPreference extends Cu
     protected String LOGTAG = "PanelsPreference";
 
     // Position state of this Preference in enclosing category.
     private static final int STATE_IS_FIRST = 0;
     private static final int STATE_IS_LAST = 1;
 
     /**
      * Index of the context menu button for controlling display options.
-     * For (removable) Dynamic panels, this button removes the panel.
-     * For built-in panels, this button toggles showing or hiding the panel.
+     * This button toggles showing or hiding the panel.
      */
     private static final int INDEX_DISPLAY_BUTTON = 1;
     private static final int INDEX_REORDER_BUTTON = 2;
 
     // Indices of buttons in context menu for reordering.
     private static final int INDEX_MOVE_UP_BUTTON = 0;
     private static final int INDEX_MOVE_DOWN_BUTTON = 1;
 
     private String LABEL_HIDE;
     private String LABEL_SHOW;
 
     private View preferenceView;
     protected boolean mIsHidden = false;
-    private boolean mIsRemovable;
 
     private boolean mAnimate;
     private static final int ANIMATION_DURATION_MS = 400;
 
     // State for reordering.
     private int mPositionState = -1;
     private final int mIndex;
 
-    public PanelsPreference(Context context, CustomListCategory parentCategory, boolean isRemovable, int index, boolean animate) {
+    public PanelsPreference(Context context, CustomListCategory parentCategory, int index, boolean animate) {
         super(context, parentCategory);
-        mIsRemovable = isRemovable;
         mIndex = index;
         mAnimate = animate;
     }
 
     @Override
     protected int getPreferenceLayoutResource() {
         return R.layout.preference_panels;
     }
@@ -93,21 +90,16 @@ public class PanelsPreference extends Cu
         }
     }
 
     @Override
     protected String[] createDialogItems() {
         final Resources res = getContext().getResources();
         final String labelReorder = res.getString(R.string.pref_panels_reorder);
 
-        if (mIsRemovable) {
-            return new String[] { LABEL_SET_AS_DEFAULT, LABEL_REMOVE, labelReorder };
-        }
-
-        // Built-in panels can't be removed, so use show/hide options.
         LABEL_HIDE = res.getString(R.string.pref_panels_hide);
         LABEL_SHOW = res.getString(R.string.pref_panels_show);
 
         return new String[] { LABEL_SET_AS_DEFAULT, LABEL_HIDE, labelReorder };
     }
 
     @Override
     public void setIsDefault(boolean isDefault) {
@@ -126,24 +118,18 @@ public class PanelsPreference extends Cu
     @Override
     protected void onDialogIndexClicked(int index) {
         switch(index) {
             case INDEX_SET_DEFAULT_BUTTON:
                 mParentCategory.setDefault(this);
                 break;
 
             case INDEX_DISPLAY_BUTTON:
-                // Handle display options for the panel.
-                if (mIsRemovable) {
-                    // For removable panels, the button displays text for removing the panel.
-                    mParentCategory.uninstall(this);
-                } else {
-                    // Otherwise, the button toggles between text for showing or hiding the panel.
-                    ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
-                }
+                // The button toggles between text for showing or hiding the panel.
+                ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
                 break;
 
             case INDEX_REORDER_BUTTON:
                 // Display dialog for changing preference order.
                 final Dialog orderDialog = makeReorderDialog();
                 orderDialog.show();
                 break;
 
@@ -152,20 +138,18 @@ public class PanelsPreference extends Cu
         }
     }
 
     @Override
     protected void configureShownDialog() {
         super.configureShownDialog();
 
         // Handle Show/Hide buttons.
-        if (!mIsRemovable) {
-            final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_DISPLAY_BUTTON);
-            hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
-        }
+        final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_DISPLAY_BUTTON);
+        hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
     }
 
 
     private Dialog makeReorderDialog() {
         final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
 
         final Resources res = getContext().getResources();
         final String labelUp = res.getString(R.string.pref_panels_move_up);
--- a/mobile/android/base/preferences/PanelsPreferenceCategory.java
+++ b/mobile/android/base/preferences/PanelsPreferenceCategory.java
@@ -17,19 +17,16 @@ import android.text.TextUtils;
 import android.util.AttributeSet;
 
 public class PanelsPreferenceCategory extends CustomListCategory {
     public static final String LOGTAG = "PanelsPrefCategory";
 
     protected HomeConfig mHomeConfig;
     protected HomeConfig.Editor mConfigEditor;
 
-    // Account for the fake "Add Panel" preference in preference counting.
-    private static final int PANEL_PREFS_OFFSET = 1;
-
     protected UiAsyncTask<Void, Void, HomeConfig.State> mLoadTask;
 
     public PanelsPreferenceCategory(Context context) {
         super(context);
         initConfig(context);
     }
 
     public PanelsPreferenceCategory(Context context, AttributeSet attrs) {
@@ -82,41 +79,34 @@ public class PanelsPreferenceCategory ex
     /**
      * Refresh the Home Panels list and animate a panel, if specified.
      * If null, load from HomeConfig.
      *
      * @param State HomeConfig.State to rebuild Home Panels list from.
      * @param String panelId of panel to be animated.
      */
     public void refresh(State state, String animatePanelId) {
-        // Clear all the existing home panels, but leave the
-        // first item (Add panels).
-        int prefCount = getPreferenceCount();
-        while (prefCount > 1) {
-            removePreference(getPreference(1));
-            prefCount--;
-        }
+        // Clear all the existing home panels.
+        removeAll();
 
         if (state == null) {
             loadHomeConfig(animatePanelId);
         } else {
             displayHomeConfig(state, animatePanelId);
         }
     }
 
     private void displayHomeConfig(HomeConfig.State configState, String animatePanelId) {
         int index = 0;
         for (PanelConfig panelConfig : configState) {
-            final boolean isRemovable = panelConfig.isDynamic();
-
             // Create and add the pref.
             final String panelId = panelConfig.getId();
             final boolean animate = TextUtils.equals(animatePanelId, panelId);
 
-            final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this, isRemovable, index, animate);
+            final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this, index, animate);
             pref.setTitle(panelConfig.getTitle());
             pref.setKey(panelConfig.getId());
             // XXX: Pull icon from PanelInfo.
             addPreference(pref);
 
             if (panelConfig.isDisabled()) {
                 pref.setHidden(true);
             }
@@ -127,34 +117,33 @@ public class PanelsPreferenceCategory ex
         setPositionState();
         setDefaultFromConfig();
     }
 
     private void setPositionState() {
         final int prefCount = getPreferenceCount();
 
         // Pass in position state to first and last preference.
-        final PanelsPreference firstPref = (PanelsPreference) getPreference(PANEL_PREFS_OFFSET);
+        final PanelsPreference firstPref = (PanelsPreference) getPreference(0);
         firstPref.setIsFirst();
 
         final PanelsPreference lastPref = (PanelsPreference) getPreference(prefCount - 1);
         lastPref.setIsLast();
     }
 
     private void setDefaultFromConfig() {
         final String defaultPanelId = mConfigEditor.getDefaultPanelId();
         if (defaultPanelId == null) {
             mDefaultReference = null;
             return;
         }
 
         final int prefCount = getPreferenceCount();
 
-        // First preference (index 0) is Preference to add panels.
-        for (int i = 1; i < prefCount; i++) {
+        for (int i = 0; i < prefCount; i++) {
             final PanelsPreference pref = (PanelsPreference) getPreference(i);
 
             if (defaultPanelId.equals(pref.getKey())) {
                 super.setDefault(pref);
                 break;
             }
         }
     }
--- a/mobile/android/base/resources/xml/preferences_home.xml
+++ b/mobile/android/base/resources/xml/preferences_home.xml
@@ -4,23 +4,17 @@
    - 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">
-
-        <Preference android:key="android.not_a_preference.home.add_panel"
-                    android:title="@string/pref_home_add_panel"
-                    android:icon="@drawable/icon_new_home_panel" />
-
-    </org.mozilla.gecko.preferences.PanelsPreferenceCategory>
+        android:title="@string/pref_category_home_panels"/>
 
     <PreferenceCategory android:title="@string/pref_category_home_content_settings">
 
         <ListPreference android:key="home.sync.updateMode"
                         android:title="@string/pref_home_updates"
                         android:entries="@array/pref_home_updates_entries"
                         android:entryValues="@array/pref_home_updates_values"
                         android:persistent="false" />
--- a/mobile/android/chrome/content/aboutDownloads.js
+++ b/mobile/android/chrome/content/aboutDownloads.js
@@ -1,354 +1,617 @@
 /* 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/. */
+ * 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 Cu = Components.utils;
+let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "strings",
-                            () => Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties"));
+let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties");
 
-function deleteDownload(download) {
-  download.finalize(true).then(null, Cu.reportError);
-  OS.File.remove(download.target.path).then(null, ex => {
-    if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
-      Cu.reportError(ex);
-    }
-  });
-}
-
-let contextMenu = {
-  _items: [],
-  _targetDownload: null,
+let downloadTemplate =
+"<li downloadGUID='{guid}' class='list-item' role='button' state='{state}' contextmenu='downloadmenu'>" +
+  "<img class='icon' src='{icon}'/>" +
+  "<div class='details'>" +
+     "<div class='row'>" +
+       // This is a hack so that we can crop this label in its center
+       "<xul:label class='title' crop='center' value='{target}'/>" +
+       "<div class='date'>{date}</div>" +
+     "</div>" +
+     "<div class='size'>{size}</div>" +
+     "<div class='domain'>{domain}</div>" +
+     "<div class='displayState'>{displayState}</div>" +
+  "</div>" +
+"</li>";
 
-  init: function () {
-    let element = document.getElementById("downloadmenu");
-    element.addEventListener("click",
-                             event => event.download = this._targetDownload,
-                             true);
+XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
+  window.QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIWebNavigation)
+    .QueryInterface(Ci.nsIDocShellTreeItem)
+    .rootTreeItem
+    .QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIDOMWindow)
+    .QueryInterface(Ci.nsIDOMChromeWindow));
+
+
+var ContextMenus = {
+  target: null,
 
-    this._items = [
-      new ContextMenuItem("open",
-                          download => download.succeeded,
-                          download => download.launch().then(null, Cu.reportError)),
-      new ContextMenuItem("retry",
-                          download => download.error ||
-                                      (download.canceled && !download.hasPartialData),
-                          download => download.start().then(null, Cu.reportError)),
-      new ContextMenuItem("remove",
-                          download => download.stopped,
-                          download => {
-                            Downloads.getList(Downloads.ALL)
-                                     .then(list => list.remove(download))
-                                     .then(null, Cu.reportError);
-                            deleteDownload(download);
-                          }),
-      new ContextMenuItem("pause",
-                          download => !download.stopped,
-                          download => download.cancel().then(null, Cu.reportError)),
-      new ContextMenuItem("resume",
-                          download => download.canceled && download.hasPartialData,
-                          download => download.start().then(null, Cu.reportError)),
-      new ContextMenuItem("cancel",
-                          download => !download.stopped ||
-                                      (download.canceled && download.hasPartialData),
-                          download => {
-                            download.cancel().then(null, Cu.reportError);
-                            download.removePartialData().then(null, Cu.reportError);
-                          }),
-      // following menu item is a global action
-      new ContextMenuItem("removeall",
-                          () => downloadLists.finished.length > 0,
-                          () => downloadLists.removeFinished())
+  init: function() {
+    document.addEventListener("contextmenu", this, false);
+    document.getElementById("contextmenu-open").addEventListener("click", this.open.bind(this), false);
+    document.getElementById("contextmenu-retry").addEventListener("click", this.retry.bind(this), false);
+    document.getElementById("contextmenu-remove").addEventListener("click", this.remove.bind(this), false);
+    document.getElementById("contextmenu-pause").addEventListener("click", this.pause.bind(this), false);
+    document.getElementById("contextmenu-resume").addEventListener("click", this.resume.bind(this), false);
+    document.getElementById("contextmenu-cancel").addEventListener("click", this.cancel.bind(this), false);
+    document.getElementById("contextmenu-removeall").addEventListener("click", this.removeAll.bind(this), false);
+    this.items = [
+      { name: "open", states: [Downloads._dlmgr.DOWNLOAD_FINISHED] },
+      { name: "retry", states: [Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
+      { name: "remove", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
+      { name: "removeall", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
+      { name: "pause", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING] },
+      { name: "resume", states: [Downloads._dlmgr.DOWNLOAD_PAUSED] },
+      { name: "cancel", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING, Downloads._dlmgr.DOWNLOAD_NOTSTARTED, Downloads._dlmgr.DOWNLOAD_QUEUED, Downloads._dlmgr.DOWNLOAD_PAUSED] },
     ];
   },
 
-  addContextMenuEventListener: function (element) {
-    element.addEventListener("contextmenu", this.onContextMenu.bind(this));
+  handleEvent: function(event) {
+    // store the target of context menu events so that we know which app to act on
+    this.target = event.target;
+    while (!this.target.hasAttribute("contextmenu")) {
+      this.target = this.target.parentNode;
+    }
+    if (!this.target)
+      return;
+
+    let state = parseInt(this.target.getAttribute("state"));
+    for (let i = 0; i < this.items.length; i++) {
+      var item = this.items[i];
+      let enabled = (item.states.indexOf(state) > -1);
+      if (enabled)
+        document.getElementById("contextmenu-" + item.name).removeAttribute("hidden");
+      else
+        document.getElementById("contextmenu-" + item.name).setAttribute("hidden", "true");
+    }
+  },
+
+  // Open shown only for downloads that completed successfully
+  open: function(event) {
+    Downloads.openDownload(this.target);
+    this.target = null;
   },
 
-  onContextMenu: function (event) {
-    let target = event.target;
-    while (target && !target.download) {
-      target = target.parentNode;
-    }
-    if (!target) {
-      Cu.reportError("No download found for context menu target");
-      event.preventDefault();
-      return;
-    }
+  // Retry shown when its failed, canceled, blocked(covered in failed, see _getState())
+  retry: function (event) {
+    Downloads.retryDownload(this.target);
+    this.target = null;
+  },
+
+  // Remove shown when its canceled, finished, failed(failed includes blocked and dirty, see _getState())
+  remove: function (event) {
+    Downloads.removeDownload(this.target);
+    this.target = null;
+  },
 
-    // capture the target download for menu items to use in a click event
-    this._targetDownload = target.download;
-    for (let item of this._items) {
-      item.updateVisibility(target.download);
-    }
+  // Pause shown when item is currently downloading
+  pause: function (event) {
+    Downloads.pauseDownload(this.target);
+    this.target = null;
+  },
+
+  // Resume shown for paused items only
+  resume: function (event) {
+    Downloads.resumeDownload(this.target);
+    this.target = null;
+  },
+
+  // Cancel shown when its downloading, notstarted, queued or paused
+  cancel: function (event) {
+    Downloads.cancelDownload(this.target);
+    this.target = null;
+  },
+
+  removeAll: function(event) {
+    Downloads.removeAll();
+    this.target = null;
   }
-};
-
-function ContextMenuItem(name, isVisible, action) {
-  this.element = document.getElementById("contextmenu-" + name);
-  this.isVisible = isVisible;
-
-  this.element.addEventListener("click", event => action(event.download));
 }
 
-ContextMenuItem.prototype = {
-  updateVisibility: function (download) {
-    this.element.hidden = !this.isVisible(download);
-  }
-};
+
+let Downloads = {
+  init: function dl_init() {
+    function onClick(evt) {
+      let target = evt.target;
+      while (target.nodeName != "li") {
+        target = target.parentNode;
+        if (!target)
+          return;
+      }
+
+      Downloads.openDownload(target);
+    }
+
+    this._normalList = document.getElementById("normal-downloads-list");
+    this._privateList = document.getElementById("private-downloads-list");
 
-function DownloadListView(type, listElementId) {
-  this.listElement = document.getElementById(listElementId);
-  contextMenu.addContextMenuEventListener(this.listElement);
+    this._normalList.addEventListener("click", onClick, false);
+    this._privateList.addEventListener("click", onClick, false);
+
+    this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+    this._dlmgr.addPrivacyAwareListener(this);
+
+    Services.obs.addObserver(this, "last-pb-context-exited", false);
+    Services.obs.addObserver(this, "download-manager-remove-download-guid", false);
 
-  this.items = new Map();
+    // If we have private downloads, show them all immediately. If we were to
+    // add them asynchronously, there's a small chance we could get a
+    // "last-pb-context-exited" notification before downloads are added to the
+    // list, meaning we'd show private downloads without any private tabs open.
+    let privateEntries = this.getDownloads({ isPrivate: true });
+    this._stepAddEntries(privateEntries, this._privateList, privateEntries.length);
+
+    // Add non-private downloads
+    let normalEntries = this.getDownloads({ isPrivate: false });    
+    this._stepAddEntries(normalEntries, this._normalList, 1, this._scrollToSelectedDownload.bind(this));    
+    ContextMenus.init();    
+  },
 
-  Downloads.getList(type)
-           .then(list => list.addView(this))
-           .then(null, Cu.reportError);
+  uninit: function dl_uninit() {
+    let contextmenus = gChromeWin.NativeWindow.contextmenus;
+    contextmenus.remove(this.openMenuItem);
+    contextmenus.remove(this.removeMenuItem);
+    contextmenus.remove(this.pauseMenuItem);
+    contextmenus.remove(this.resumeMenuItem);
+    contextmenus.remove(this.retryMenuItem);
+    contextmenus.remove(this.cancelMenuItem);
+    contextmenus.remove(this.deleteAllMenuItem);
+
+    this._dlmgr.removeListener(this);
+    Services.obs.removeObserver(this, "last-pb-context-exited");
+    Services.obs.removeObserver(this, "download-manager-remove-download-guid");
+  },
 
-  window.addEventListener("unload", event => {
-    Downloads.getList(type)
-             .then(list => list.removeView(this))
-             .then(null, Cu.reportError);
-  });
-}
+  onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress,
+                             aCurTotalProgress, aMaxTotalProgress, aDownload) { },
+  onDownloadStateChange: function(aState, aDownload) {
+    switch (aDownload.state) {
+      case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
+      case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+      case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+      case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+      case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
+        // For all "completed" states, move them after active downloads
+        this._moveDownloadAfterActive(this._getElementForDownload(aDownload.guid));
 
-DownloadListView.prototype = {
-  get finished() {
-    let finished = [];
-    for (let download of this.items.keys()) {
-      if (download.stopped && (!download.hasPartialData || download.error)) {
-        finished.push(download);
+      // Fall-through the rest
+      case Ci.nsIDownloadManager.DOWNLOAD_SCANNING:
+      case Ci.nsIDownloadManager.DOWNLOAD_QUEUED:
+      case Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING:
+        let item = this._getElementForDownload(aDownload.guid);
+        if (item)
+          this._updateDownloadRow(item, aDownload);
+        else
+          this._insertDownloadRow(aDownload);
+        break;
+    }
+  },
+  onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
+  onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
+
+  observe: function (aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "last-pb-context-exited":
+        this._privateList.innerHTML = "";
+        break;
+      case "download-manager-remove-download-guid": {
+        let guid = aSubject.QueryInterface(Ci.nsISupportsCString).data;
+        this._removeItem(this._getElementForDownload(guid));
+        break;
       }
     }
+  },
 
-    return finished;
+  _moveDownloadAfterActive: function dl_moveDownloadAfterActive(aItem) {
+    // Move downloads that just reached a "completed" state below any active
+    try {
+      // Iterate down until we find a non-active download
+      let next = aItem.nextElementSibling;
+      while (next && this._inProgress(next.getAttribute("state")))
+        next = next.nextElementSibling;
+      // Move the item
+      aItem.parentNode.insertBefore(aItem, next);
+    } catch (ex) {
+      this.logError("_moveDownloadAfterActive() " + ex);
+    }
+  },
+
+  _inProgress: function dl_inProgress(aState) {
+    return [
+      this._dlmgr.DOWNLOAD_NOTSTARTED,
+      this._dlmgr.DOWNLOAD_QUEUED,
+      this._dlmgr.DOWNLOAD_DOWNLOADING,
+      this._dlmgr.DOWNLOAD_PAUSED,
+      this._dlmgr.DOWNLOAD_SCANNING,
+    ].indexOf(parseInt(aState)) != -1;
+  },
+
+  _insertDownloadRow: function dl_insertDownloadRow(aDownload) {
+    let updatedState = this._getState(aDownload.state);
+    let item = this._createItem(downloadTemplate, {
+      guid: aDownload.guid,
+      target: aDownload.displayName,
+      icon: "moz-icon://" + aDownload.displayName + "?size=64",
+      date: DownloadUtils.getReadableDates(new Date())[0],
+      domain: DownloadUtils.getURIHost(aDownload.source.spec)[0],
+      size: this._getDownloadSize(aDownload.size),
+      displayState: this._getStateString(updatedState),
+      state: updatedState
+    });
+    list = aDownload.isPrivate ? this._privateList : this._normalList;
+    list.insertAdjacentHTML("afterbegin", item);
+  },
+
+  _getDownloadSize: function dl_getDownloadSize(aSize) {
+    if (aSize > 0) {
+      let displaySize = DownloadUtils.convertByteUnits(aSize);
+      return displaySize.join(""); // [0] is size, [1] is units
+    }
+    return gStrings.GetStringFromName("downloadState.unknownSize");
+  },
+
+  // Not all states are displayed as-is on mobile, some are translated to a generic state
+  _getState: function dl_getState(aState) {
+    let str;
+    switch (aState) {
+      // Downloading and Scanning states show up as "Downloading"
+      case this._dlmgr.DOWNLOAD_DOWNLOADING:
+      case this._dlmgr.DOWNLOAD_SCANNING:
+        str = this._dlmgr.DOWNLOAD_DOWNLOADING;
+        break;
+
+      // Failed, Dirty and Blocked states show up as "Failed"
+      case this._dlmgr.DOWNLOAD_FAILED:
+      case this._dlmgr.DOWNLOAD_DIRTY:
+      case this._dlmgr.DOWNLOAD_BLOCKED_POLICY:
+      case this._dlmgr.DOWNLOAD_BLOCKED_PARENTAL:
+        str = this._dlmgr.DOWNLOAD_FAILED;
+        break;
+
+      /* QUEUED and NOTSTARTED are not translated as they
+         dont fall under a common state but we still need
+         to display a common "status" on the UI */
+
+      default:
+        str = aState;
+    }
+    return str;
   },
 
-  insertOrMoveItem: function (item) {
-    var compare = (a, b) => {
-      // active downloads always before stopped downloads
-      if (a.stopped != b.stopped) {
-        return b.stopped ? -1 : 1
-      }
-      // most recent downloads first
-      return b.startTime - a.startTime;
-    };
+  // Note: This doesn't cover all states as some of the states are translated in _getState()
+  _getStateString: function dl_getStateString(aState) {
+    let str;
+    switch (aState) {
+      case this._dlmgr.DOWNLOAD_DOWNLOADING:
+        str = "downloadState.downloading";
+        break;
+      case this._dlmgr.DOWNLOAD_CANCELED:
+        str = "downloadState.canceled";
+        break;
+      case this._dlmgr.DOWNLOAD_FAILED:
+        str = "downloadState.failed";
+        break;
+      case this._dlmgr.DOWNLOAD_PAUSED:
+        str = "downloadState.paused";
+        break;
 
-    let insertLocation = this.listElement.firstChild;
-    while (insertLocation && compare(item.download, insertLocation.download) > 0) {
-      insertLocation = insertLocation.nextElementSibling;
+      // Queued and Notstarted show up as "Starting..."
+      case this._dlmgr.DOWNLOAD_QUEUED:
+      case this._dlmgr.DOWNLOAD_NOTSTARTED:
+        str = "downloadState.starting";
+        break;
+
+      default:
+        return "";
     }
-    this.listElement.insertBefore(item.element, insertLocation);
+    return gStrings.GetStringFromName(str);
+  },
+
+  _updateItem: function dl_updateItem(aItem, aValues) {
+    for (let i in aValues) {
+      aItem.querySelector("." + i).textContent = aValues[i];
+    }
+  },
+
+  _initStatement: function dv__initStatement(aIsPrivate) {
+    let dbConn = aIsPrivate ? this._dlmgr.privateDBConnection : this._dlmgr.DBConnection;
+    return dbConn.createStatement(
+      "SELECT guid, name, source, state, startTime, endTime, referrer, " +
+             "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
+      "FROM moz_downloads " +
+      "ORDER BY isActive DESC, endTime DESC, startTime DESC");
   },
 
-  onDownloadAdded: function (download) {
-    let item = new DownloadItem(download);
-    this.items.set(download, item);
-    this.insertOrMoveItem(item);
+  _createItem: function _createItem(aTemplate, aValues) {
+    function htmlEscape(s) {
+      s = s.replace(/&/g, "&amp;");
+      s = s.replace(/>/g, "&gt;");
+      s = s.replace(/</g, "&lt;");
+      s = s.replace(/"/g, "&quot;");
+      s = s.replace(/'/g, "&apos;");
+      return s;
+    }
+
+    let t = aTemplate;
+    for (let key in aValues) {
+      if (aValues.hasOwnProperty(key)) {
+        let regEx = new RegExp("{" + key + "}", "g");
+        let value = htmlEscape(aValues[key].toString());
+        t = t.replace(regEx, value);
+      }
+    }
+    return t;
   },
 
-  onDownloadChanged: function (download) {
-    let item = this.items.get(download);
-    if (!item) {
-      Cu.reportError("No DownloadItem found for download");
+  _getEntry: function dv__getEntry(aStmt) {
+    try {
+      if (!aStmt.executeStep()) {
+        return null;
+      }
+
+      let updatedState = this._getState(aStmt.row.state);
+      // Try to get the attribute values from the statement
+
+      return {
+        guid: aStmt.row.guid,
+        target: aStmt.row.name,
+        icon: "moz-icon://" + aStmt.row.name + "?size=64",
+        date: DownloadUtils.getReadableDates(new Date(aStmt.row.endTime / 1000))[0],
+        domain: DownloadUtils.getURIHost(aStmt.row.source)[0],
+        size: this._getDownloadSize(aStmt.row.maxBytes),
+        displayState: this._getStateString(updatedState),
+        state: updatedState
+      };
+
+    } catch (e) {
+      // Something went wrong when stepping or getting values, so clear and quit
+      this.logError("_getEntry() " + e);
+      aStmt.reset();
+      return null;
+    }
+  },
+
+  _stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems, aCallback) {
+    
+    if (aEntries.length == 0){
+      if (aCallback)
+        aCallback();
+
       return;
     }
 
-    if (item.stateChanged) {
-      this.insertOrMoveItem(item);
+    let attrs = aEntries.shift();
+    let item = this._createItem(downloadTemplate, attrs);
+    aList.insertAdjacentHTML("beforeend", item);
+
+    // Add another item to the list if we should; otherwise, let the UI update
+    // and continue later
+    if (aNumItems > 1) {
+      this._stepAddEntries(aEntries, aList, aNumItems - 1, aCallback);
+    } else {
+      // Use a shorter delay for earlier downloads to display them faster
+      let delay = Math.min(aList.itemCount * 10, 300);
+      setTimeout(function () {
+        this._stepAddEntries(aEntries, aList, 5, aCallback);
+      }.bind(this), delay);
     }
-
-    item.onDownloadChanged();
   },
 
-  onDownloadRemoved: function (download) {
-    let item = this.items.get(download);
-    if (!item) {
-      Cu.reportError("No DownloadItem found for download");
-      return;
+  getDownloads: function dl_getDownloads(aParams) {
+    aParams = aParams || {};
+    let stmt = this._initStatement(aParams.isPrivate);
+
+    stmt.reset();
+    stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED);
+    stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING);
+    stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED);
+    stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED);
+    stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING);
+
+    let entries = [];
+    while (entry = this._getEntry(stmt)) {
+      entries.push(entry);
     }
 
-    this.items.delete(download);
-    this.listElement.removeChild(item.element);
-  }
-};
+    stmt.finalize();
+
+    return entries;
+  },
+
+  _getElementForDownload: function dl_getElementForDownload(aKey) {
+    return document.body.querySelector("li[downloadGUID='" + aKey + "']");
+  },
+
+  _getDownloadForElement: function dl_getDownloadForElement(aElement, aCallback) {
+    let guid = aElement.getAttribute("downloadGUID");
+    this._dlmgr.getDownloadByGUID(guid, function(status, download) {
+      if (!Components.isSuccessCode(status)) {
+        return;
+      }
+      aCallback(download);
+    });
+  },
+
+  _removeItem: function dl_removeItem(aItem) {
+    // Make sure we have an item to remove
+    if (!aItem)
+      return;
+
+    aItem.parentNode.removeChild(aItem);
+  },
 
-let downloadLists = {
-  init: function () {
-    this.publicDownloads = new DownloadListView(Downloads.PUBLIC, "public-downloads-list");
-    this.privateDownloads = new DownloadListView(Downloads.PRIVATE, "private-downloads-list");
+  openDownload: function dl_openDownload(aItem) {
+    this._getDownloadForElement(aItem, function(aDownload) {
+      if (aDownload.state !== Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
+        // Do not open unfinished downloads.
+        return;
+      }
+      try {
+        let f = aDownload.targetFile;
+        if (f) f.launch();
+      } catch (ex) {
+        this.logError("openDownload() " + ex, aDownload);
+      }
+    }.bind(this));
+  },
+
+  removeDownload: function dl_removeDownload(aItem) {
+    this._getDownloadForElement(aItem, function(aDownload) {
+      if (aDownload.targetFile) {
+        OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
+          if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
+            this.logError("removeDownload() " + reason, aDownload);
+          }
+        }.bind(this));
+      }
+
+      aDownload.remove();
+    }.bind(this));
+  },
+
+  removeAll: function dl_removeAll() {
+    let title = gStrings.GetStringFromName("downloadAction.deleteAll");
+    let messageForm = gStrings.GetStringFromName("downloadMessage.deleteAll");
+    let elements = document.body.querySelectorAll("li[state='" + this._dlmgr.DOWNLOAD_FINISHED + "']," +
+                                               "li[state='" + this._dlmgr.DOWNLOAD_CANCELED + "']," +
+                                               "li[state='" + this._dlmgr.DOWNLOAD_FAILED + "']");
+    let message = PluralForm.get(elements.length, messageForm)
+                            .replace("#1", elements.length);
+    let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_OK +
+                Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
+    let choice = Services.prompt.confirmEx(null, title, message, flags,
+                                           null, null, null, null, {});
+    if (choice == 0) {
+      for (let i = 0; i < elements.length; i++) {
+        this.removeDownload(elements[i]);
+      }
+    }
   },
 
-  get finished() {
-    return this.publicDownloads.finished.concat(this.privateDownloads.finished);
+  pauseDownload: function dl_pauseDownload(aItem) {
+    this._getDownloadForElement(aItem, function(aDownload) {
+      try {
+        aDownload.pause();
+        this._updateDownloadRow(aItem, aDownload);
+      } catch (ex) {
+        this.logError("Error: pauseDownload() " + ex, aDownload);
+      }
+    }.bind(this));
+  },
+
+  resumeDownload: function dl_resumeDownload(aItem) {
+    this._getDownloadForElement(aItem, function(aDownload) {
+      try {
+        aDownload.resume();
+        this._updateDownloadRow(aItem, aDownload);
+      } catch (ex) {
+        this.logError("resumeDownload() " + ex, aDownload);
+      }
+    }.bind(this));
+  },
+
+  retryDownload: function dl_retryDownload(aItem) {
+    this._getDownloadForElement(aItem, function(aDownload) {
+      try {
+        this._removeItem(aItem);
+        aDownload.retry();
+      } catch (ex) {
+        this.logError("retryDownload() " + ex, aDownload);
+      }
+    }.bind(this));
   },
 
-  removeFinished: function () {
-    let finished = this.finished;
-    if (finished.length == 0) {
+  cancelDownload: function dl_cancelDownload(aItem) {
+    this._getDownloadForElement(aItem, function(aDownload) {
+      OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
+        if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
+          this.logError("cancelDownload() " + reason, aDownload);
+        }
+      }.bind(this));
+
+      aDownload.cancel();
+
+      this._updateDownloadRow(aItem, aDownload);
+    }.bind(this));
+  },
+
+  _updateDownloadRow: function dl_updateDownloadRow(aItem, aDownload) {
+    try {
+      let updatedState = this._getState(aDownload.state);
+      aItem.setAttribute("state", updatedState);
+      this._updateItem(aItem, {
+        size: this._getDownloadSize(aDownload.size),
+        displayState: this._getStateString(updatedState),
+        date: DownloadUtils.getReadableDates(new Date())[0]
+      });
+    } catch (ex) {
+      this.logError("_updateDownloadRow() " + ex, aDownload);
+    }
+  },
+  
+  /**
+   * In case a specific downloadId was passed while opening, scrolls the list to 
+   * the given elemenet
+   */
+
+  _scrollToSelectedDownload : function dl_scrollToSelected() {
+    let spec = document.location.href;
+    let pos = spec.indexOf("?");
+    let query = "";
+    if (pos >= 0)
+      query = spec.substring(pos + 1);
+
+    // Just assume the query is "id=<id>"
+    let id = query.substring(3);
+    if (!id) {
+      return;
+    }    
+    downloadElement = this._getElementForDownload(id);
+    if (!downloadElement) {
       return;
     }
 
-    let title = strings.GetStringFromName("downloadAction.deleteAll");
-    let messageForm = strings.GetStringFromName("downloadMessage.deleteAll");
-    let message = PluralForm.get(finished.length, messageForm).replace("#1", finished.length);
-
-    if (Services.prompt.confirm(null, title, message)) {
-      Downloads.getList(Downloads.ALL)
-               .then(list => {
-                 for (let download of finished) {
-                   list.remove(download).then(null, Cu.reportError);
-                   deleteDownload(download);
-                 }
-               }, Cu.reportError);
-    }
-  }
-};
-
-function DownloadItem(download) {
-  this._download = download;
-  this._updateFromDownload();
-
-  this._domain = DownloadUtils.getURIHost(download.source.url)[0];
-  this._fileName = this._htmlEscape(OS.Path.basename(download.target.path));
-  this._iconUrl = "moz-icon://" + this._fileName + "?size=64";
-  this._startDate = this._htmlEscape(DownloadUtils.getReadableDates(download.startTime)[0]);
-
-  this._element = this.createElement();
-}
-
-const kDownloadStatePropertyNames = [
-  "stopped",
-  "succeeded",
-  "canceled",
-  "error",
-  "startTime"
-];
-
-DownloadItem.prototype = {
-  _htmlEscape : function (s) {
-    s = s.replace(/&/g, "&amp;");
-    s = s.replace(/>/g, "&gt;");
-    s = s.replace(/</g, "&lt;");
-    s = s.replace(/"/g, "&quot;");
-    s = s.replace(/'/g, "&apos;");
-    return s;
+    downloadElement.scrollIntoView();
   },
 
-  _updateFromDownload: function () {
-    this._state = {};
-    kDownloadStatePropertyNames.forEach(
-      name => this._state[name] = this._download[name],
-      this);
-  },
-
-  get stateChanged() {
-    return kDownloadStatePropertyNames.some(
-      name => this._state[name] != this._download[name],
-      this);
-  },
-
-  get download() this._download,
-  get element() this._element,
-
-  createElement: function() {
-    let template = document.getElementById("download-item");
-    // TODO: use this once <template> is working
-    // let element = document.importNode(template.content, true);
-
-    // simulate a <template> node...
-    let element = template.cloneNode(true);
-    element.removeAttribute("id");
-    element.removeAttribute("style");
-
-    // launch the download if clicked
-    element.addEventListener("click", this.onClick.bind(this));
-
-    // set download as an expando property for the context menu
-    element.download = this.download;
-
-    // fill in template placeholders
-    this.updateElement(element);
-
-    return element;
-  },
-
-  updateElement: function (element) {
-    element.querySelector(".date").textContent = this.startDate;
-    element.querySelector(".domain").textContent = this.domain;
-    element.querySelector(".icon").src = this.iconUrl;
-    element.querySelector(".size").textContent = this.size;
-    element.querySelector(".state").textContent = this.stateDescription;
-    element.querySelector(".title").setAttribute("value", this.fileName);
-  },
-
-  onClick: function (event) {
-    if (this.download.succeeded) {
-      this.download.launch().then(null, Cu.reportError);
+  /**
+   * Logs the error to the console.
+   *
+   * @param aMessage  error message to log
+   * @param aDownload (optional) if given, and if the download is private, the
+   *                  log message is suppressed
+   */
+  logError: function dl_logError(aMessage, aDownload) {
+    if (!aDownload || !aDownload.isPrivate) {
+      console.log("Error: " + aMessage);
     }
   },
 
-  onDownloadChanged: function () {
-    this._updateFromDownload();
-    this.updateElement(this.element);
-  },
-
-  // template properties below
-  get domain() this._domain,
-  get fileName() this._fileName,
-  get id() this._id,
-  get iconUrl() this._iconUrl,
-
-  get size() {
-    if (this.download.hasProgress) {
-      return DownloadUtils.convertByteUnits(this.download.totalBytes).join("");
-    }
-    return strings.GetStringFromName("downloadState.unknownSize");
-  },
-
-  get startDate() {
-    return this._startDate;
-  },
+  QueryInterface: function (aIID) {
+    if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
+        !aIID.equals(Ci.nsISupports))
+      throw Components.results.NS_ERROR_NO_INTERFACE;
+    return this;
+  }
+}
 
-  get stateDescription() {
-    let name;
-    if (this.download.error) {
-      name = "downloadState.failed";
-    } else if (this.download.canceled) {
-      if (this.download.hasPartialData) {
-        name = "downloadState.paused";
-      } else {
-        name = "downloadState.canceled";
-      }
-    } else if (!this.download.stopped) {
-      if (this.download.currentBytes > 0) {
-        name = "downloadState.downloading";
-      } else {
-        name = "downloadState.starting";
-      }
-    }
+document.addEventListener("DOMContentLoaded", Downloads.init.bind(Downloads), true);
+window.addEventListener("unload", Downloads.uninit.bind(Downloads), false);
 
-    if (name) {
-      return strings.GetStringFromName(name);
-    }
-    return "";
-  }
-};
 
-window.addEventListener("DOMContentLoaded", event => {
-    contextMenu.init();
-    downloadLists.init()
-});
--- a/mobile/android/chrome/content/aboutDownloads.xhtml
+++ b/mobile/android/chrome/content/aboutDownloads.xhtml
@@ -30,33 +30,17 @@
     <menuitem id="contextmenu-retry" label="&aboutDownloads.retry;"></menuitem>
     <menuitem id="contextmenu-remove" label="&aboutDownloads.remove;"></menuitem>
     <menuitem id="contextmenu-pause" label="&aboutDownloads.pause;"></menuitem>
     <menuitem id="contextmenu-resume" label="&aboutDownloads.resume;"></menuitem>
     <menuitem id="contextmenu-cancel" label="&aboutDownloads.cancel;"></menuitem>
     <menuitem id="contextmenu-removeall" label="&aboutDownloads.removeAll;"></menuitem>
   </menu>
 
-  <!--template id="download-item"-->
-    <li id="download-item" class="list-item" role="button" contextmenu="downloadmenu" style="display: none">
-      <img class="icon" src=""/>
-      <div class="details">
-        <div class="row">
-          <!-- This is a hack so that we can crop this label in its center -->
-          <xul:label class="title" crop="center" value=""/>
-          <div class="date"></div>
-        </div>
-        <div class="size"></div>
-        <div class="domain"></div>
-        <div class="state"></div>
-      </div>
-    </li>
-  <!--/template-->
-
   <div class="header">
     <div>&aboutDownloads.header;</div>
   </div>
   <ul id="private-downloads-list" class="list"></ul>
-  <ul id="public-downloads-list" class="list"></ul>
+  <ul id="normal-downloads-list" class="list"></ul>
   <span id="no-downloads-indicator">&aboutDownloads.empty;</span>
   <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutDownloads.js"/>
 </body>
 </html>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -8,17 +8,16 @@
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 let Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
-Cu.import("resource://gre/modules/DownloadNotifications.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/JNI.jsm");
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource://gre/modules/SpatialNavigation.jsm");
 Cu.import("resource://gre/modules/UITelemetry.jsm");
 
 #ifdef ACCESSIBILITY
@@ -365,17 +364,17 @@ var BrowserApp = {
     }, false);
 
     // When a restricted key is pressed in DOM full-screen mode, we should display
     // the "Press ESC to exit" warning message.
     window.addEventListener("MozShowFullScreenWarning", showFullScreenWarning, true);
 
     NativeWindow.init();
     LightWeightThemeWebInstaller.init();
-    DownloadNotifications.init();
+    Downloads.init();
     FormAssistant.init();
     IndexedDB.init();
     HealthReportStatusListener.init();
     XPInstallObserver.init();
     CharacterEncoding.init();
     ActivityObserver.init();
 #ifdef MOZ_ANDROID_SYNTHAPKS
     // TODO: replace with Android implementation of WebappOSUtils.isLaunchable.
@@ -653,17 +652,17 @@ var BrowserApp = {
         aTarget.mozRequestFullScreen();
       });
 
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.mute"),
       NativeWindow.contextmenus.mediaContext("media-unmuted"),
       function(aTarget) {
         aTarget.muted = true;
       });
-
+  
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.unmute"),
       NativeWindow.contextmenus.mediaContext("media-muted"),
       function(aTarget) {
         aTarget.muted = false;
       });
 
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyImageLocation"),
       NativeWindow.contextmenus.imageLocationCopyableContext,
@@ -746,17 +745,16 @@ var BrowserApp = {
     if (Services.prefs.prefHasUserValue("plugins.click_to_play")) {
       Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED);
       Services.prefs.clearUserPref("plugins.click_to_play");
     }
   },
 
   shutdown: function shutdown() {
     NativeWindow.uninit();
-    DownloadNotifications.uninit();
     LightWeightThemeWebInstaller.uninit();
     FormAssistant.uninit();
     IndexedDB.uninit();
     ViewportHandler.uninit();
     XPInstallObserver.uninit();
     HealthReportStatusListener.uninit();
     CharacterEncoding.uninit();
     SearchEngines.uninit();
@@ -1820,17 +1818,17 @@ var NativeWindow = {
       sendMessageToJava({ type: "Menu:Remove", id: aId });
     },
 
     update: function(aId, aOptions) {
       if (!aOptions)
         return;
 
       sendMessageToJava({
-        type: "Menu:Update",
+        type: "Menu:Update", 
         id: aId,
         options: aOptions
       });
     }
   },
 
   doorhanger: {
     _callbacks: {},
@@ -1846,17 +1844,17 @@ var NativeWindow = {
    *                     to -1, the doorhanger will never automatically dismiss.
    *        persistWhileVisible:
    *                     A boolean. If true, a visible notification will always
    *                     persist across location changes.
    *        timeout:     A time in milliseconds. The notification will not
    *                     automatically dismiss before this time.
    *        checkbox:    A string to appear next to a checkbox under the notification
    *                     message. The button callback functions will be called with
-   *                     the checked state as an argument.
+   *                     the checked state as an argument.                   
    */
     show: function(aMessage, aValue, aButtons, aTabID, aOptions) {
       if (aButtons == null) {
         aButtons = [];
       }
 
       aButtons.forEach((function(aButton) {
         this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId };
@@ -2248,17 +2246,17 @@ var NativeWindow = {
         this.menus = null;
         Services.obs.notifyObservers({target: target, x: x, y: y}, "context-menu-not-shown", "");
 
         if (SelectionHandler.canSelect(target)) {
           if (!SelectionHandler.startSelection(target, {
             mode: SelectionHandler.SELECT_AT_POINT,
             x: x,
             y: y
-          })) {
+          })) { 
             SelectionHandler.attachCaret(target);
           }
         }
       }
     },
 
     // Returns a title for a context menu. If no title attribute exists, will fall back to looking for a url
     _getTitle: function(node) {
@@ -3142,17 +3140,17 @@ Tab.prototype = {
 
     // We add in a bit of fudge just so that the end characters
     // don't accidentally get clipped. 15px is an arbitrary choice.
     gReflowPending = setTimeout(doChangeMaxLineBoxWidth,
                                 reflozTimeout,
                                 viewportWidth - 15);
   },
 
-  /**
+  /** 
    * Reloads the tab with the desktop mode setting.
    */
   reloadWithMode: function (aDesktopMode) {
     // Set desktop mode for tab and send change to Java
     if (this.desktopMode != aDesktopMode) {
       this.desktopMode = aDesktopMode;
       sendMessageToJava({
         type: "DesktopMode:Changed",
@@ -3777,34 +3775,31 @@ Tab.prototype = {
 
           // We use the sizes attribute if available
           // see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
           if (target.hasAttribute("sizes")) {
             let sizes = target.getAttribute("sizes").toLowerCase();
 
             if (sizes == "any") {
               // Since Java expects an integer, use -1 to represent icons with sizes="any"
-              maxSize = -1;
+              maxSize = -1; 
             } else {
               let tokens = sizes.split(" ");
               tokens.forEach(function(token) {
                 // TODO: check for invalid tokens
                 let [w, h] = token.split("x");
                 maxSize = Math.max(maxSize, Math.max(w, h));
               });
             }
           }
 
           let json = {
             type: "Link:Favicon",
             tabID: this.id,
             href: resolveGeckoURI(target.href),
-            charset: target.ownerDocument.characterSet,
-            title: target.title,
-            rel: list.join(" "),
             size: maxSize
           };
           sendMessageToJava(json);
         } else if (list.indexOf("[alternate]") != -1) {
           let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
           let isFeed = (type == "application/rss+xml" || type == "application/atom+xml");
 
           if (!isFeed)
@@ -6661,17 +6656,17 @@ var IdentityHandler = {
    * Determine the identity of the page being displayed by examining its SSL cert
    * (if available). Return the data needed to update the UI.
    */
   checkIdentity: function checkIdentity(aState, aBrowser) {
     this._lastStatus = aBrowser.securityUI
                                .QueryInterface(Components.interfaces.nsISSLStatusProvider)
                                .SSLStatus;
 
-    // Don't pass in the actual location object, since it can cause us to
+    // Don't pass in the actual location object, since it can cause us to 
     // hold on to the window object too long.  Just pass in the fields we
     // care about. (bug 424829)
     let locationObj = {};
     try {
       let location = aBrowser.contentWindow.location;
       locationObj.host = location.host;
       locationObj.hostname = location.hostname;
       locationObj.port = location.port;
@@ -6711,17 +6706,17 @@ var IdentityHandler = {
       else if (iData.state) // State only
         supplemental += iData.state;
       else if (iData.country) // Country only
         supplemental += iData.country;
       result.supplemental = supplemental;
 
       return result;
     }
-
+    
     // Otherwise, we don't know the cert owner
     result.owner = Strings.browser.GetStringFromName("identity.ownerUnknown3");
 
     // Cache the override service the first time we need to check it
     if (!this._overrideService)
       this._overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(Ci.nsICertOverrideService);
 
     // Check whether this site is a security exception. XPConnect does the right
@@ -7323,17 +7318,17 @@ var WebappsUI = {
       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
       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) {
       try {
         let shell = Cc["@mozilla.org/browser/shell-service;1"].createInstance(Ci.nsIShellService);
         shell.createShortcut(aTitle, aURL, icon, aType);
@@ -8523,26 +8518,8 @@ HTMLContextMenuItem.prototype = Object.c
         icon: elt.icon,
         label: elt.label,
         disabled: elt.disabled,
         menu: elt instanceof Ci.nsIDOMHTMLMenuElement
       };
     }
   },
 });
-
-/**
- * CID of Downloads.jsm's implementation of nsITransfer.
- */
-const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
-
-/**
- * Contract ID of the service implementing nsITransfer.
- */
-const kTransferContractId = "@mozilla.org/transfer;1";
-
-// Override Toolkit's nsITransfer implementation with the one from the
-// JavaScript API for downloads.  This will eventually be removed when
-// nsIDownloadManager will not be available anymore (bug 851471).  The
-// old code in this module will be removed in bug 899110.
-Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
-                  .registerFactory(kTransferCid, "",
-                                   kTransferContractId, null);
--- a/mobile/android/chrome/content/browser.xul
+++ b/mobile/android/chrome/content/browser.xul
@@ -6,12 +6,13 @@
 <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
 
 <window id="main-window"
         onload="BrowserApp.startup();"
         windowtype="navigator:browser"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript" src="chrome://browser/content/browser.js"/>
+  <script type="application/javascript" src="chrome://browser/content/downloads.js"/>
 
   <deck id="browsers" flex="1"/>
 
 </window>
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/downloads.js
@@ -0,0 +1,298 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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 Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function dump(a) {
+  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
+                                  "resource://gre/modules/Notifications.jsm");
+
+const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
+const URI_PAUSE_ICON = "drawable://pause";
+const URI_CANCEL_ICON = "drawable://close";
+const URI_RESUME_ICON = "drawable://play";
+
+
+XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+var Downloads = {
+  _initialized: false,
+  _dlmgr: null,
+  _progressAlert: null,
+  _privateDownloads: [],
+  _showingPrompt: false,
+  _downloadsIdMap: {},
+
+  _getLocalFile: function dl__getLocalFile(aFileURI) {
+    // if this is a URL, get the file from that
+    // XXX it's possible that using a null char-set here is bad
+    const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
+    return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
+  },
+
+  init: function dl_init() {
+    if (this._initialized)
+      return;
+    this._initialized = true;
+
+    // Monitor downloads and display alerts
+    this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+    this._progressAlert = new AlertDownloadProgressListener();
+    this._dlmgr.addPrivacyAwareListener(this._progressAlert);
+    Services.obs.addObserver(this, "last-pb-context-exited", true);
+  },
+
+  openDownload: function dl_openDownload(aDownload) {
+    let fileUri = aDownload.target.spec;
+    let guid = aDownload.guid;
+    let f = this._getLocalFile(fileUri);
+    try {
+      f.launch();
+    } catch (ex) {
+      // in case we are not able to open the file (i.e. there is no app able to handle it)
+      // we just open the browser tab showing it 
+      BrowserApp.addTab("about:downloads?id=" + guid);
+    }
+  },
+
+  cancelDownload: function dl_cancelDownload(aDownload) {
+    aDownload.cancel();
+    let fileURI = aDownload.target.spec;
+    let f = this._getLocalFile(fileURI);
+
+    OS.File.remove(f.path);
+  },
+
+  showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) {
+    if (this._showingPrompt)
+      return;
+    this._showingPrompt = true;
+    // Open a prompt that offers a choice to cancel the download
+    let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle");
+    let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage");
+    let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES +
+                Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO;
+    let choice = Services.prompt.confirmEx(null, title, message, flags,
+                                           null, null, null, null, {});
+    if (choice == 0)
+      this.cancelDownload(aDownload);
+    this._showingPrompt = false;
+  },
+
+  handleClickEvent: function dl_handleClickEvent(aDownload) {
+    // Only open the downloaded file if the download is complete
+    if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED)
+      this.openDownload(aDownload);
+    else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING ||
+                aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED)
+      this.showCancelConfirmPrompt(aDownload);
+  },
+
+  clickCallback: function dl_clickCallback(aDownloadId) {
+    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+          if (Components.isSuccessCode(status))
+            this.handleClickEvent(download);
+        }).bind(this));
+  },
+
+  pauseClickCallback: function dl_buttonPauseCallback(aDownloadId) {
+    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+          if (Components.isSuccessCode(status))
+            download.pause();
+        }).bind(this));
+  },
+
+  resumeClickCallback: function dl_buttonPauseCallback(aDownloadId) {
+    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+          if (Components.isSuccessCode(status))
+            download.resume();
+        }).bind(this));
+  },
+
+  cancelClickCallback: function dl_buttonPauseCallback(aDownloadId) {
+    this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) {
+          if (Components.isSuccessCode(status))
+            this.cancelDownload(download);
+        }).bind(this));
+  },
+
+  notificationCanceledCallback: function dl_notifCancelCallback(aId, aDownloadId) {
+    let notificationId = this._downloadsIdMap[aDownloadId];
+    if (notificationId && notificationId == aId)
+      delete this._downloadsIdMap[aDownloadId];
+  },
+
+  createNotification: function dl_createNotif(aDownload, aOptions) {
+    let notificationId = Notifications.create(aOptions);
+    this._downloadsIdMap[aDownload.guid] = notificationId;
+  },
+
+  updateNotification: function dl_updateNotif(aDownload, aOptions) {
+    let notificationId = this._downloadsIdMap[aDownload.guid];
+    if (notificationId)
+      Notifications.update(notificationId, aOptions);
+  },
+
+  cancelNotification: function dl_cleanNotif(aDownload) {
+    Notifications.cancel(this._downloadsIdMap[aDownload.guid]);
+    delete this._downloadsIdMap[aDownload.guid];
+  },
+
+  // observer for last-pb-context-exited
+  observe: function dl_observe(aSubject, aTopic, aData) {
+    let download;
+    while ((download = this._privateDownloads.pop())) {
+      try {
+        let notificationId = aDownload.guid;
+        Notifications.clear(notificationId);
+        Downloads.removeNotification(download);
+      } catch (e) {
+        dump("Error removing private download: " + e);
+      }
+    }
+  },
+
+  QueryInterface: function (aIID) {
+    if (!aIID.equals(Ci.nsISupports) &&
+        !aIID.equals(Ci.nsIObserver) &&
+        !aIID.equals(Ci.nsISupportsWeakReference))
+      throw Components.results.NS_ERROR_NO_INTERFACE;
+    return this;
+  }
+};
+
+const PAUSE_BUTTON = {
+  buttonId: "pause",
+  title : Strings.browser.GetStringFromName("alertDownloadsPause"),
+  icon : URI_PAUSE_ICON,
+  onClicked: function (aId, aCookie) {
+    Downloads.pauseClickCallback(aCookie);
+  }
+};
+
+const CANCEL_BUTTON = {
+  buttonId: "cancel",
+  title : Strings.browser.GetStringFromName("alertDownloadsCancel"),
+  icon : URI_CANCEL_ICON,
+  onClicked: function (aId, aCookie) {
+    Downloads.cancelClickCallback(aCookie);
+  }
+};
+
+const RESUME_BUTTON = {
+  buttonId: "resume",
+  title : Strings.browser.GetStringFromName("alertDownloadsResume"),
+  icon: URI_RESUME_ICON,
+  onClicked: function (aId, aCookie) {
+    Downloads.resumeClickCallback(aCookie);
+  }
+};
+
+function DownloadNotifOptions (aDownload, aTitle, aMessage) {
+  this.icon = URI_GENERIC_ICON_DOWNLOAD;
+  this.onCancel = function (aId, aCookie) {
+    Downloads.notificationCanceledCallback(aId, aCookie);
+  }
+  this.onClick = function (aId, aCookie) {
+    Downloads.clickCallback(aCookie);
+  }
+  this.title = aTitle;
+  this.message = aMessage;
+  this.buttons = null;
+  this.cookie = aDownload.guid;
+  this.persistent = true;
+}
+
+function DownloadProgressNotifOptions (aDownload, aButtons) {
+  DownloadNotifOptions.apply(this, [aDownload, aDownload.displayName, aDownload.percentComplete + "%"]);
+  this.ongoing = true;
+  this.progress = aDownload.percentComplete;
+  this.buttons = aButtons;
+}
+
+// AlertDownloadProgressListener is used to display progress in the alert notifications.
+function AlertDownloadProgressListener() { }
+
+AlertDownloadProgressListener.prototype = {
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIDownloadProgressListener
+  onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) {
+    let strings = Strings.browser;
+    let availableSpace = -1;
+    try {
+      // diskSpaceAvailable is not implemented on all systems
+      let availableSpace = aDownload.targetFile.diskSpaceAvailable;
+    } catch(ex) { }
+    let contentLength = aDownload.size;
+    if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
+      Downloads.updateNotification(aDownload, new DownloadNotifOptions(aDownload,
+                                                                        strings.GetStringFromName("alertDownloadsNoSpace"),
+                                                                        strings.GetStringFromName("alertDownloadsSize")));
+      aDownload.cancel();
+    }
+
+    if (aDownload.percentComplete == -1) {
+      // Undetermined progress is not supported yet
+      return;
+    }
+
+    Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [PAUSE_BUTTON, CANCEL_BUTTON]));
+  },
+
+  onDownloadStateChange: function(aState, aDownload) {
+    let state = aDownload.state;
+    switch (state) {
+      case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: {
+        NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long");
+        Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
+                                                                         Strings.browser.GetStringFromName("alertDownloadsStart2"),
+                                                                         aDownload.displayName));
+        break;
+      }
+      case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: {
+        Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [RESUME_BUTTON, CANCEL_BUTTON]));
+        break;
+      }
+      case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
+      case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+      case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+      case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+      case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: {
+        Downloads.cancelNotification(aDownload);
+        if (aDownload.isPrivate) {
+          let index = Downloads._privateDownloads.indexOf(aDownload);
+          if (index != -1) {
+            Downloads._privateDownloads.splice(index, 1);
+          }
+        }
+
+        if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
+          Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload,
+                                                                Strings.browser.GetStringFromName("alertDownloadsDone2"),
+                                                                aDownload.displayName));
+        }
+        break;
+      }
+    }
+  },
+
+  onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
+  onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+  QueryInterface: function (aIID) {
+    if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
+        !aIID.equals(Ci.nsISupports))
+      throw Components.results.NS_ERROR_NO_INTERFACE;
+    return this;
+  }
+};
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -29,16 +29,17 @@ chrome.jar:
 * 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/downloads.js                 (content/downloads.js)
   content/netError.xhtml               (content/netError.xhtml)
   content/SelectHelper.js              (content/SelectHelper.js)
   content/SelectionHandler.js          (content/SelectionHandler.js)
   content/dbg-browser-actors.js        (content/dbg-browser-actors.js)
 * content/WebappRT.js                  (content/WebappRT.js)
   content/InputWidgetHelper.js         (content/InputWidgetHelper.js)
   content/WebrtcUI.js                  (content/WebrtcUI.js)
   content/MemoryObserver.js            (content/MemoryObserver.js)
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -437,19 +437,16 @@
 @BINPATH@/components/dom_webspeechsynth.xpt
 #endif
 
 #ifdef MOZ_DEBUG
 @BINPATH@/components/TestInterfaceJS.js
 @BINPATH@/components/TestInterfaceJS.manifest
 #endif
 
-@BINPATH@/components/Downloads.manifest
-@BINPATH@/components/DownloadLegacy.js
-
 ; Modules
 @BINPATH@/modules/*
 
 #ifdef MOZ_SAFE_BROWSING
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
deleted file mode 100644
--- a/mobile/android/modules/DownloadNotifications.jsm
+++ /dev/null
@@ -1,232 +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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["DownloadNotifications"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "strings",
-                            () => Services.strings.createBundle("chrome://browser/locale/browser.properties"));
-Object.defineProperty(this, "nativeWindow",
-                      { get: () => Services.wm.getMostRecentWindow("navigator:browser").NativeWindow });
-
-const kButtons = {
-  PAUSE: new DownloadNotificationButton("pause",
-                                        "drawable://pause",
-                                        "alertDownloadsPause",
-                                        notification => notification.pauseDownload()),
-  RESUME: new DownloadNotificationButton("resume",
-                                         "drawable://play",
-                                         "alertDownloadsResume",
-                                         notification => notification.resumeDownload()),
-  CANCEL: new DownloadNotificationButton("cancel",
-                                         "drawable://close",
-                                         "alertDownloadsCancel",
-                                         notification => notification.cancelDownload())
-};
-
-let notifications = new Map();
-
-var DownloadNotifications = {
-  init: function () {
-    if (!this._viewAdded) {
-      Downloads.getList(Downloads.ALL)
-               .then(list => list.addView(this))
-               .then(null, Cu.reportError);
-
-      this._viewAdded = true;
-    }
-  },
-
-  uninit: function () {
-    if (this._viewAdded) {
-      Downloads.getList(Downloads.ALL)
-               .then(list => list.removeView(this))
-               .then(null, Cu.reportError);
-
-      for (let notification of notifications.values()) {
-        notification.hide();
-      }
-
-      this._viewAdded = false;
-    }
-  },
-
-  onDownloadAdded: function (download) {
-    let notification = new DownloadNotification(download);
-    notifications.set(download, notification);
-
-    notification.showOrUpdate();
-    if (download.currentBytes == 0) {
-      nativeWindow.toast.show(strings.GetStringFromName("alertDownloadsToast"), "long");
-    }
-  },
-
-  onDownloadChanged: function (download) {
-    let notification = notifications.get(download);
-    if (!notification) {
-      Cu.reportError("Download doesn't have a notification.");
-      return;
-    }
-
-    notification.showOrUpdate();
-  },
-
-  onDownloadRemoved: function (download) {
-    let notification = notifications.get(download);
-    if (!notification) {
-      Cu.reportError("Download doesn't have a notification.");
-      return;
-    }
-
-    notification.hide();
-    notifications.delete(download);
-  }
-};
-
-function DownloadNotification(download) {
-  this.download = download;
-  this._fileName = OS.Path.basename(download.target.path);
-
-  this.id = null;
-}
-
-DownloadNotification.prototype = {
-  _updateFromDownload: function () {
-    this._downloading = !this.download.stopped;
-    this._paused = this.download.canceled && this.download.hasPartialData;
-    this._succeeded = this.download.succeeded;
-
-    this._show = this._downloading || this._paused || this._succeeded;
-  },
-
-  get options() {
-    if (!this._show) {
-      return null;
-    }
-
-    let options = {
-      icon : "drawable://alert_download",
-      onClick : (id, cookie) => this.onClick(),
-      onCancel : (id, cookie) => this._notificationId = null,
-      cookie : this.download
-    };
-
-    if (this._downloading) {
-      if (this.download.currentBytes == 0) {
-        this._updateOptionsForStatic(options, "alertDownloadsStart2");
-      } else {
-        this._updateOptionsForOngoing(options, [kButtons.PAUSE, kButtons.CANCEL]);
-      }
-    } else if (this._paused) {
-      this._updateOptionsForOngoing(options, [kButtons.RESUME, kButtons.CANCEL]);
-    } else if (this._succeeded) {
-      options.persistent = false;
-      this._updateOptionsForStatic(options, "alertDownloadsDone2");
-    }
-
-    return options;
-  },
-
-  _updateOptionsForStatic : function (options, titleName) {
-    options.title = strings.GetStringFromName(titleName);
-    options.message = this._fileName;
-  },
-
-  _updateOptionsForOngoing: function (options, buttons) {
-    options.title = this._fileName;
-    options.message = this.download.progress + "%";
-    options.buttons = buttons;
-    options.ongoing = true;
-    options.progress = this.download.progress;
-    options.persistent = true;
-  },
-
-  showOrUpdate: function () {
-    this._updateFromDownload();
-
-    if (this._show) {
-      if (!this.id) {
-        this.id = Notifications.create(this.options);
-      } else {
-        Notifications.update(this.id, this.options);
-      }
-    } else {
-      this.hide();
-    }
-  },
-
-  hide: function () {
-    if (this.id) {
-      Notifications.cancel(this.id);
-      this.id = null;
-    }
-  },
-
-  onClick: function () {
-    if (this.download.succeeded) {
-      this.download.launch().then(null, Cu.reportError);
-    } else {
-      ConfirmCancelPrompt.show(this);
-    }
-  },
-
-  pauseDownload: function () {
-    this.download.cancel().then(null, Cu.reportError);
-  },
-
-  resumeDownload: function () {
-    this.download.start().then(null, Cu.reportError);
-  },
-
-  cancelDownload: function () {
-    this.hide();
-
-    this.download.cancel().then(null, Cu.reportError);
-    this.download.removePartialData().then(null, Cu.reportError);
-  }
-};
-
-var ConfirmCancelPrompt = {
-  showing: false,
-  show: function (downloadNotification) {
-    if (this.showing) {
-      return;
-    }
-
-    this.showing = true;
-    // Open a prompt that offers a choice to cancel the download
-    let title = strings.GetStringFromName("downloadCancelPromptTitle");
-    let message = strings.GetStringFromName("downloadCancelPromptMessage");
-
-    if (Services.prompt.confirm(null, title, message)) {
-      downloadNotification.cancelDownload();
-    }
-    this.showing = false;
-  }
-};
-
-function DownloadNotificationButton(buttonId, iconUrl, titleStringName, onClicked) {
-  this.buttonId = buttonId;
-  this.title = strings.GetStringFromName(titleStringName);
-  this.icon = iconUrl;
-  this.onClicked = (id, download) => {
-    let notification = notifications.get(download);
-    if (!notification) {
-      Cu.reportError("No DownloadNotification for button");
-      return;
-    }
-
-    onClicked(notification);
-  }
-}
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -3,17 +3,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/.
 
 EXTRA_JS_MODULES += [
     'Accounts.jsm',
     'AndroidLog.jsm',
     'ContactService.jsm',
-    'DownloadNotifications.jsm',
     'HelperApps.jsm',
     'Home.jsm',
     'HomeProvider.jsm',
     'JNI.jsm',
     'LightweightThemeConsumer.jsm',
     'Messaging.jsm',
     'Notifications.jsm',
     'OrderedBroadcast.jsm',
--- a/mobile/android/themes/core/aboutDownloads.css
+++ b/mobile/android/themes/core/aboutDownloads.css
@@ -46,27 +46,27 @@ li:active div.details,
   color: gray;
 }
 
 .domain,
 .size {
   display: inline;
 }
 
-.state {
+.displayState {
   color: gray;
   margin-bottom: -3px; /* Prevent overflow that hides bottom border */
 }
 
 .size:after {
   content: " - ";
   white-space: pre;
 }
 
 #no-downloads-indicator {
   display: none;
 }
 
-#private-downloads-list:empty + #public-downloads-list:empty + #no-downloads-indicator {
+#private-downloads-list:empty + #normal-downloads-list:empty + #no-downloads-indicator {
   display: block;
   text-align: center;
   padding-top: 3.9em;
 }
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -600,18 +600,19 @@ VerifySignature(AppTrustedRoot trustedRo
 
   // Verify certificate.
   AppTrustDomain trustDomain(nullptr); // TODO: null pinArg
   if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) {
     return MapSECStatus(SECFailure);
   }
   if (BuildCertChain(trustDomain, signerCert, PR_Now(),
                      EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                     SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                     SEC_OID_X509_ANY_POLICY, nullptr, builtChain)
+                     KeyPurposeId::id_kp_codeSigning,
+                     CertPolicyId::anyPolicy,
+                     nullptr, builtChain)
         != SECSuccess) {
     return MapSECStatus(SECFailure);
   }
 
   // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
   SECOidData* contentTypeOidData =
     SECOID_FindOID(&signedData->contentInfo.contentType);
   if (!contentTypeOidData) {
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -98,25 +98,25 @@ AppTrustDomain::FindPotentialIssuers(con
 
   results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
                                        encodedIssuerName, time, true);
   return SECSuccess;
 }
 
 SECStatus
 AppTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
-                             SECOidTag policy,
+                             const CertPolicyId& policy,
                              const CERTCertificate* candidateCert,
                      /*out*/ TrustLevel* trustLevel)
 {
-  MOZ_ASSERT(policy == SEC_OID_X509_ANY_POLICY);
+  MOZ_ASSERT(policy.IsAnyPolicy());
   MOZ_ASSERT(candidateCert);
   MOZ_ASSERT(trustLevel);
   MOZ_ASSERT(mTrustedRoot);
-  if (!candidateCert || !trustLevel || policy != SEC_OID_X509_ANY_POLICY) {
+  if (!candidateCert || !trustLevel || !policy.IsAnyPolicy()) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
   if (!mTrustedRoot) {
     PR_SetError(PR_INVALID_STATE_ERROR, 0);
     return SECFailure;
   }
 
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -16,17 +16,17 @@ namespace mozilla { namespace psm {
 class AppTrustDomain MOZ_FINAL : public mozilla::pkix::TrustDomain
 {
 public:
   AppTrustDomain(void* pinArg);
 
   SECStatus SetTrustedRoot(AppTrustedRoot trustedRoot);
 
   SECStatus GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
-                         SECOidTag policy,
+                         const mozilla::pkix::CertPolicyId& policy,
                          const CERTCertificate* candidateCert,
                  /*out*/ mozilla::pkix::TrustLevel* trustLevel) MOZ_OVERRIDE;
   SECStatus FindPotentialIssuers(const SECItem* encodedIssuerName,
                                  PRTime time,
                          /*out*/ mozilla::pkix::ScopedCERTCertList& results)
                                  MOZ_OVERRIDE;
   SECStatus VerifySignedData(const CERTSignedData* signedData,
                              const CERTCertificate* cert) MOZ_OVERRIDE;
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -295,18 +295,18 @@ destroyCertListThatShouldNotExist(CERTCe
     *certChain = nullptr;
   }
 }
 #endif
 
 static SECStatus
 BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
                              PRTime time, KeyUsages ku1, KeyUsages ku2,
-                             KeyUsages ku3, SECOidTag eku,
-                             SECOidTag requiredPolicy,
+                             KeyUsages ku3, KeyPurposeId eku,
+                             const CertPolicyId& requiredPolicy,
                              const SECItem* stapledOCSPResponse,
                              ScopedCERTCertList& builtChain)
 {
   PR_ASSERT(ku1);
   PR_ASSERT(ku2);
 
   SECStatus rv = BuildCertChain(trustDomain, cert, time,
                                 EndEntityOrCA::MustBeEndEntity, ku1,
@@ -386,48 +386,49 @@ CertVerifier::MozillaPKIXVerifyCert(
   switch (usage) {
     case certificateUsageSSLClient: {
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time,
                           EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                          SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
-                          SEC_OID_X509_ANY_POLICY,
-                          stapledOCSPResponse, builtChain);
+                          KeyPurposeId::id_kp_clientAuth,
+                          CertPolicyId::anyPolicy, stapledOCSPResponse,
+                          builtChain);
       break;
     }
 
     case certificateUsageSSLServer: {
       // TODO: When verifying a certificate in an SSL handshake, we should
       // restrict the acceptable key usage based on the key exchange method
       // chosen by the server.
 
 #ifndef MOZ_NO_EV_CERTS
       // Try to validate for EV first.
-      SECOidTag evPolicy = SEC_OID_UNKNOWN;
-      rv = GetFirstEVPolicy(cert, evPolicy);
-      if (rv == SECSuccess && evPolicy != SEC_OID_UNKNOWN) {
+      CertPolicyId evPolicy;
+      SECOidTag evPolicyOidTag;
+      rv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag);
+      if (rv == SECSuccess) {
         NSSCertDBTrustDomain
           trustDomain(trustSSL,
                       ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
                         ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
                         : NSSCertDBTrustDomain::FetchOCSPForEV,
                       mOCSPCache, pinArg, &callbackContainer);
         rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                           KU_DIGITAL_SIGNATURE, // ECDHE/DHE
                                           KU_KEY_ENCIPHERMENT, // RSA
                                           KU_KEY_AGREEMENT, // ECDH/DH
-                                          SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
+                                          KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           builtChain);
         if (rv == SECSuccess) {
           if (evOidPolicy) {
-            *evOidPolicy = evPolicy;
+            *evOidPolicy = evPolicyOidTag;
           }
           break;
         }
         builtChain = nullptr; // clear built chain, just in case.
       }
 #endif
 
       if (flags & FLAG_MUST_BE_EV) {
@@ -438,107 +439,106 @@ CertVerifier::MozillaPKIXVerifyCert(
 
       // Now try non-EV.
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
                                        pinArg, &callbackContainer);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_DIGITAL_SIGNATURE, // ECDHE/DHE
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
-                                        SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
-                                        SEC_OID_X509_ANY_POLICY,
+                                        KeyPurposeId::id_kp_serverAuth,
+                                        CertPolicyId::anyPolicy,
                                         stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time, EndEntityOrCA::MustBeCA,
-                          KU_KEY_CERT_SIGN,
-                          SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
-                          SEC_OID_X509_ANY_POLICY,
+                          KU_KEY_CERT_SIGN, KeyPurposeId::id_kp_serverAuth,
+                          CertPolicyId::anyPolicy,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time,
                           EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                          SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
-                          SEC_OID_X509_ANY_POLICY,
+                          KeyPurposeId::id_kp_emailProtection,
+                          CertPolicyId::anyPolicy,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailRecipient: {
       // TODO: The higher level S/MIME processing should pass in which key
       // usage it is trying to verify for, and base its algorithm choices
       // based on the result of the verification(s).
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
                                         0,
-                                        SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
-                                        SEC_OID_X509_ANY_POLICY,
+                                        KeyPurposeId::id_kp_emailProtection,
+                                        CertPolicyId::anyPolicy,
                                         stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
                                        mOCSPCache, pinArg);
       rv = BuildCertChain(trustDomain, cert, time,
                           EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                          SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                          SEC_OID_X509_ANY_POLICY,
+                          KeyPurposeId::id_kp_codeSigning,
+                          CertPolicyId::anyPolicy,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageVerifyCA:
     case certificateUsageStatusResponder: {
       // XXX This is a pretty useless way to verify a certificate. It is used
       // by the implementation of window.crypto.importCertificates and in the
       // certificate viewer UI. Because we don't know what trust bit is
       // interesting, we just try them all.
       mozilla::pkix::EndEntityOrCA endEntityOrCA;
       mozilla::pkix::KeyUsages keyUsage;
-      SECOidTag eku;
+      KeyPurposeId eku;
       if (usage == certificateUsageVerifyCA) {
         endEntityOrCA = EndEntityOrCA::MustBeCA;
         keyUsage = KU_KEY_CERT_SIGN;
-        eku = SEC_OID_UNKNOWN;
+        eku = KeyPurposeId::anyExtendedKeyUsage;
       } else {
         endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
         keyUsage = KU_DIGITAL_SIGNATURE;
-        eku = SEC_OID_OCSP_RESPONDER;
+        eku = KeyPurposeId::id_kp_OCSPSigning;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache,
                                     pinArg);
       rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
-                          keyUsage, eku, SEC_OID_X509_ANY_POLICY,
+                          keyUsage, eku, CertPolicyId::anyPolicy,
                           stapledOCSPResponse, builtChain);
       if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache,
                                         pinArg);
         rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
-                            eku, SEC_OID_X509_ANY_POLICY,
+                            eku, CertPolicyId::anyPolicy,
                             stapledOCSPResponse, builtChain);
         if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   ocspFetching, mOCSPCache,
                                                   pinArg);
           rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
-                              keyUsage, eku, SEC_OID_X509_ANY_POLICY,
+                              keyUsage, eku, CertPolicyId::anyPolicy,
                               stapledOCSPResponse, builtChain);
         }
       }
 
       break;
     }
 
     default:
@@ -611,17 +611,18 @@ CertVerifier::VerifyCert(CERTCertificate
 
 #ifndef NSS_NO_LIBPKIX
   ScopedCERTCertList trustAnchors;
   SECStatus rv;
   SECOidTag evPolicy = SEC_OID_UNKNOWN;
 
   // Do EV checking only for sslserver usage
   if (usage == certificateUsageSSLServer) {
-    SECStatus srv = GetFirstEVPolicy(cert, evPolicy);
+    CertPolicyId unusedPolicyId;
+    SECStatus srv = GetFirstEVPolicy(cert, unusedPolicyId, evPolicy);
     if (srv == SECSuccess) {
       if (evPolicy != SEC_OID_UNKNOWN) {
         trustAnchors = GetRootsForOid(evPolicy);
       }
       if (!trustAnchors) {
         return SECFailure;
       }
       // pkix ignores an empty trustanchors list and
--- a/security/certverifier/ExtendedValidation.cpp
+++ b/security/certverifier/ExtendedValidation.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ExtendedValidation.h"
 
 #include "cert.h"
 #include "certdb.h"
 #include "base64.h"
 #include "pkix/nullptr.h"
+#include "pkix/pkixtypes.h"
 #include "pk11pub.h"
 #include "secerr.h"
 #include "prerror.h"
 #include "prinit.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gPIPNSSLog;
 #endif
@@ -875,29 +876,31 @@ GetRootsForOid(SECOidTag oid_tag)
   }
 
   return certList;
 }
 #endif
 
 bool
 CertIsAuthoritativeForEVPolicy(const CERTCertificate* cert,
-                               SECOidTag policyOidTag)
+                               const mozilla::pkix::CertPolicyId& policy)
 {
   PR_ASSERT(cert);
-  PR_ASSERT(policyOidTag != SEC_OID_UNKNOWN);
-  if (!cert || !policyOidTag) {
+  if (!cert) {
     return false;
   }
 
   for (size_t iEV = 0; iEV < PR_ARRAY_SIZE(myTrustedEVInfos); ++iEV) {
     nsMyTrustedEVInfo& entry = myTrustedEVInfos[iEV];
-    if (entry.oid_tag == policyOidTag && entry.cert &&
-        CERT_CompareCerts(cert, entry.cert)) {
-      return true;
+    if (entry.cert && CERT_CompareCerts(cert, entry.cert)) {
+      const SECOidData* oidData = SECOID_FindOIDByTag(entry.oid_tag);
+      if (oidData && oidData->oid.len == policy.numBytes &&
+          !memcmp(oidData->oid.data, policy.bytes, policy.numBytes)) {
+        return true;
+      }
     }
   }
 
   return false;
 }
 
 static PRStatus
 IdentityInfoInit()
@@ -940,17 +943,18 @@ IdentityInfoInit()
       }
 #endif
       PR_NOT_REACHED("Could not find EV root in NSS storage");
       continue;
     }
 
     unsigned char certFingerprint[20];
     rv = PK11_HashBuf(SEC_OID_SHA1, certFingerprint,
-                      entry.cert->derCert.data, entry.cert->derCert.len);
+                      entry.cert->derCert.data,
+                      static_cast<int32_t>(entry.cert->derCert.len));
     PR_ASSERT(rv == SECSuccess);
     if (rv == SECSuccess) {
       bool same = !memcmp(certFingerprint, entry.ev_root_sha1_fingerprint, 20);
       PR_ASSERT(same);
       if (same) {
 
         SECItem ev_oid_item;
         ev_oid_item.data = nullptr;
@@ -1000,20 +1004,24 @@ CleanupIdentityInfo()
     }
   }
 
   memset(&sIdentityInfoCallOnce, 0, sizeof(PRCallOnceType));
 }
 
 // Find the first policy OID that is known to be an EV policy OID.
 SECStatus
-GetFirstEVPolicy(CERTCertificate* cert, SECOidTag& outOidTag)
+GetFirstEVPolicy(CERTCertificate* cert,
+                 /*out*/ mozilla::pkix::CertPolicyId& policy,
+                 /*out*/ SECOidTag& policyOidTag)
 {
-  if (!cert)
+  if (!cert) {
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
+  }
 
   if (cert->extensions) {
     for (int i=0; cert->extensions[i]; i++) {
       const SECItem* oid = &cert->extensions[i]->id;
 
       SECOidTag oidTag = SECOID_FindOIDTag(oid);
       if (oidTag != SEC_OID_X509_CERTIFICATE_POLICIES)
         continue;
@@ -1030,24 +1038,35 @@ GetFirstEVPolicy(CERTCertificate* cert, 
       policyInfos = policies->policyInfos;
 
       bool found = false;
       while (*policyInfos) {
         const CERTPolicyInfo* policyInfo = *policyInfos++;
 
         SECOidTag oid_tag = policyInfo->oid;
         if (oid_tag != SEC_OID_UNKNOWN && isEVPolicy(oid_tag)) {
-          // in our list of OIDs accepted for EV
-          outOidTag = oid_tag;
-          found = true;
+          const SECOidData* oidData = SECOID_FindOIDByTag(oid_tag);
+          PR_ASSERT(oidData);
+          PR_ASSERT(oidData->oid.data);
+          PR_ASSERT(oidData->oid.len > 0);
+          PR_ASSERT(oidData->oid.len <= mozilla::pkix::CertPolicyId::MAX_BYTES);
+          if (oidData && oidData->oid.data && oidData->oid.len > 0 &&
+              oidData->oid.len <= mozilla::pkix::CertPolicyId::MAX_BYTES) {
+            policy.numBytes = static_cast<uint16_t>(oidData->oid.len);
+            memcpy(policy.bytes, oidData->oid.data, policy.numBytes);
+            policyOidTag = oid_tag;
+            found = true;
+          }
           break;
         }
       }
       CERT_DestroyCertificatePoliciesExtension(policies);
-      if (found)
+      if (found) {
         return SECSuccess;
+      }
     }
   }
 
+  PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
   return SECFailure;
 }
 
 } } // namespace mozilla::psm
--- a/security/certverifier/ExtendedValidation.h
+++ b/security/certverifier/ExtendedValidation.h
@@ -4,27 +4,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_psm_ExtendedValidation_h
 #define mozilla_psm_ExtendedValidation_h
 
 #include "certt.h"
 #include "prtypes.h"
 
+namespace mozilla { namespace pkix { struct CertPolicyId; } }
+
 namespace mozilla { namespace psm {
 
 #ifndef MOZ_NO_EV_CERTS
 void EnsureIdentityInfoLoaded();
 void CleanupIdentityInfo();
-SECStatus GetFirstEVPolicy(CERTCertificate* cert, SECOidTag& outOidTag);
+SECStatus GetFirstEVPolicy(CERTCertificate* cert,
+                           /*out*/ mozilla::pkix::CertPolicyId& policy,
+                           /*out*/ SECOidTag& policyOidTag);
 
 // CertIsAuthoritativeForEVPolicy does NOT evaluate whether the cert is trusted
 // or distrusted.
 bool CertIsAuthoritativeForEVPolicy(const CERTCertificate* cert,
-                                    SECOidTag policyOidTag);
+                                    const mozilla::pkix::CertPolicyId& policy);
 #endif
 
 #ifndef NSS_NO_LIBPKIX
 CERTCertList* GetRootsForOid(SECOidTag oid_tag);
 #endif
 
 } } // namespace mozilla::psm
 
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -63,30 +63,30 @@ NSSCertDBTrustDomain::FindPotentialIssue
   // "there was an error trying to retrieve the potential issuers."
   results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
                                        encodedIssuerName, time, true);
   return SECSuccess;
 }
 
 SECStatus
 NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
-                                   SECOidTag policy,
+                                   const CertPolicyId& policy,
                                    const CERTCertificate* candidateCert,
                                    /*out*/ TrustLevel* trustLevel)
 {
   PR_ASSERT(candidateCert);
   PR_ASSERT(trustLevel);
 
   if (!candidateCert || !trustLevel) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
 
 #ifdef MOZ_NO_EV_CERTS
-  if (policy != SEC_OID_X509_ANY_POLICY) {
+  if (!policy.IsAnyPolicy()) {
     PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
     return SECFailure;
   }
 #endif
 
   // XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where
   // SECSuccess means that there is a trust record and SECFailure means there
   // is not a trust record. I looked at NSS's internal uses of
@@ -109,17 +109,17 @@ NSSCertDBTrustDomain::GetCertTrust(EndEn
       *trustLevel = TrustLevel::ActivelyDistrusted;
       return SECSuccess;
     }
 
     // For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't
     // needed to consider end-entity certs to be their own trust anchors since
     // Gecko implemented nsICertOverrideService.
     if (flags & CERTDB_TRUSTED_CA) {
-      if (policy == SEC_OID_X509_ANY_POLICY) {
+      if (policy.IsAnyPolicy()) {
         *trustLevel = TrustLevel::TrustAnchor;
         return SECSuccess;
       }
 #ifndef MOZ_NO_EV_CERTS
       if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) {
         *trustLevel = TrustLevel::TrustAnchor;
         return SECSuccess;
       }
@@ -463,28 +463,28 @@ NSSCertDBTrustDomain::IsChainValid(const
 }
 
 namespace {
 
 static char*
 nss_addEscape(const char* string, char quote)
 {
   char* newString = 0;
-  int escapes = 0, size = 0;
+  size_t escapes = 0, size = 0;
   const char* src;
   char* dest;
 
   for (src = string; *src; src++) {
   if ((*src == quote) || (*src == '\\')) {
     escapes++;
   }
   size++;
   }
 
-  newString = (char*) PORT_ZAlloc(escapes + size + 1);
+  newString = (char*) PORT_ZAlloc(escapes + size + 1u);
   if (!newString) {
     return nullptr;
   }
 
   for (src = string, dest = newString; *src; src++, dest++) {
     if ((*src == quote) || (*src == '\\')) {
       *dest++ = '\\';
     }
@@ -594,19 +594,19 @@ SetClassicOCSPBehavior(CertVerifier::ocs
 
   SEC_OcspFailureMode failureMode = strict == CertVerifier::ocsp_strict
                                   ? ocspMode_FailureIsVerificationFailure
                                   : ocspMode_FailureIsNotAVerificationFailure;
   (void) CERT_SetOCSPFailureMode(failureMode);
 
   CERT_ForcePostMethodForOCSP(get != CertVerifier::ocsp_get_enabled);
 
-  int OCSPTimeoutSeconds = 3;
+  uint32_t OCSPTimeoutSeconds = 3u;
   if (strict == CertVerifier::ocsp_strict) {
-    OCSPTimeoutSeconds = 10;
+    OCSPTimeoutSeconds = 10u;
   }
   CERT_SetOCSPTimeout(OCSPTimeoutSeconds);
 }
 
 char*
 DefaultServerNicknameForCert(CERTCertificate* cert)
 {
   char* nickname = nullptr;
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -61,17 +61,17 @@ public:
                        CERTChainVerifyCallback* checkChainCallback = nullptr);
 
   virtual SECStatus FindPotentialIssuers(
                         const SECItem* encodedIssuerName,
                         PRTime time,
                 /*out*/ mozilla::pkix::ScopedCERTCertList& results);
 
   virtual SECStatus GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
-                                 SECOidTag policy,
+                                 const mozilla::pkix::CertPolicyId& policy,
                                  const CERTCertificate* candidateCert,
                          /*out*/ mozilla::pkix::TrustLevel* trustLevel);
 
   virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
                                      const CERTCertificate* cert);
 
   virtual SECStatus CheckRevocation(mozilla::pkix::EndEntityOrCA endEntityOrCA,
                                     const CERTCertificate* cert,
--- a/security/certverifier/OCSPCache.cpp
+++ b/security/certverifier/OCSPCache.cpp
@@ -19,16 +19,18 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "OCSPCache.h"
 
+#include <limits>
+
 #include "NSSCertDBTrustDomain.h"
 #include "pk11pub.h"
 #include "secerr.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gCertVerifierLog;
 #endif
 
@@ -108,40 +110,44 @@ OCSPCache::OCSPCache()
 {
 }
 
 OCSPCache::~OCSPCache()
 {
   Clear();
 }
 
-// Returns -1 if no entry is found for the given (cert, issuer) pair.
-int32_t
+// Returns false with index in an undefined state if no matching entry was
+// found.
+bool
 OCSPCache::FindInternal(const CERTCertificate* aCert,
                         const CERTCertificate* aIssuerCert,
+                        /*out*/ size_t& index,
                         const MutexAutoLock& /* aProofOfLock */)
 {
   if (mEntries.length() == 0) {
-    return -1;
+    return false;
   }
 
   SHA384Buffer idHash;
   SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert);
   if (rv != SECSuccess) {
-    return -1;
+    return false;
   }
 
   // mEntries is sorted with the most-recently-used entry at the end.
   // Thus, searching from the end will often be fastest.
-  for (int32_t i = mEntries.length() - 1; i >= 0; i--) {
-    if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) {
-      return i;
+  index = mEntries.length();
+  while (index > 0) {
+    --index;
+    if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
+      return true;
     }
   }
-  return -1;
+  return false;
 }
 
 void
 OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
                         const CERTCertificate* aIssuerCert)
 {
 #ifdef PR_LOGGING
   if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) {
@@ -171,18 +177,18 @@ OCSPCache::Get(const CERTCertificate* aC
                PRErrorCode& aErrorCode,
                PRTime& aValidThrough)
 {
   PR_ASSERT(aCert);
   PR_ASSERT(aIssuerCert);
 
   MutexAutoLock lock(mMutex);
 
-  int32_t index = FindInternal(aCert, aIssuerCert, lock);
-  if (index < 0) {
+  size_t index;
+  if (!FindInternal(aCert, aIssuerCert, index, lock)) {
     LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert);
     return false;
   }
   LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert);
   aErrorCode = mEntries[index]->mErrorCode;
   aValidThrough = mEntries[index]->mValidThrough;
   MakeMostRecentlyUsed(index, lock);
   return true;
@@ -195,19 +201,18 @@ OCSPCache::Put(const CERTCertificate* aC
                PRTime aThisUpdate,
                PRTime aValidThrough)
 {
   PR_ASSERT(aCert);
   PR_ASSERT(aIssuerCert);
 
   MutexAutoLock lock(mMutex);
 
-  int32_t index = FindInternal(aCert, aIssuerCert, lock);
-
-  if (index >= 0) {
+  size_t index;
+  if (FindInternal(aCert, aIssuerCert, index, lock)) {
     // Never replace an entry indicating a revoked certificate.
     if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
       LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - "
                    "not replacing", aCert, aIssuerCert);
       MakeMostRecentlyUsed(index, lock);
       return SECSuccess;
     }
 
--- a/security/certverifier/OCSPCache.h
+++ b/security/certverifier/OCSPCache.h
@@ -87,19 +87,20 @@ private:
     PRTime mThisUpdate;
     PRTime mValidThrough;
     // The SHA-384 hash of the concatenation of the DER encodings of the
     // issuer name and issuer key, followed by the serial number.
     // See the documentation for CertIDHash in OCSPCache.cpp.
     SHA384Buffer mIDHash;
   };
 
-  int32_t FindInternal(const CERTCertificate* aCert,
-                       const CERTCertificate* aIssuerCert,
-                       const MutexAutoLock& aProofOfLock);
+  bool FindInternal(const CERTCertificate* aCert,
+                    const CERTCertificate* aIssuerCert,
+                    /*out*/ size_t& index,
+                    const MutexAutoLock& aProofOfLock);
   void MakeMostRecentlyUsed(size_t aIndex, const MutexAutoLock& aProofOfLock);
   void LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
                     const CERTCertificate* aIssuerCert);
 
   Mutex mMutex;
   static const size_t MaxEntries = 1024;
   // Sorted with the most-recently-used entry at the end.
   // Using 256 here reserves as much possible inline storage as the vector
--- a/security/pkix/include/pkix/bind.h
+++ b/security/pkix/include/pkix/bind.h
@@ -19,36 +19,45 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 // Work around missing std::bind, std::ref, std::cref in older compilers. This
 // implementation isn't intended to be complete; rather, it is the minimal
-// implementation needed to make our use of std::bind work.
+// implementation needed to make our use of std::bind work for compilers that
+// lack both C++11 and TR1 support for these features. We cannot even assume
+// that rvalue references work, which means we don't get perfect forwarding
+// and thus we basically have to define a new overload for every distinct call
+// signature.
+//
+// A positive side-effect of this code is improved debugging usability; it is
+// much more convenient to step through code that uses this polyfill than it is
+// to step through the many nested layers of a real std::bind implementation.
+//
+// Build with MOZILLA_PKIX_USE_REAL_FUNCTIONAL defined in order to use the
+// compiler's definitions of these functions. This is helpful in order to
+// ensure that the calling code is actually compatible with the real std::bind
+// and friends.
 
 #ifndef mozilla_pkix__bind_h
 #define mozilla_pkix__bind_h
 
-#ifdef _MSC_VER
-#pragma warning(disable:4275) //Suppress spurious MSVC warning
-#endif
+#ifdef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
 #include <functional>
-#ifdef _MSC_VER
-#pragma warning(default:4275)
 #endif
 
 namespace mozilla { namespace pkix {
 
-#ifdef _MSC_VER
+#ifdef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
 
 using std::bind;
+using std::cref;
 using std::ref;
-using std::cref;
 using std::placeholders::_1;
 
 #else
 
 class Placeholder1 { };
 extern Placeholder1 _1;
 
 template <typename V>       V&  ref(V& v)       { return v; }
@@ -61,44 +70,95 @@ class Bind1
 {
 public:
   typedef R (*F)(P1 &, B1 &);
   Bind1(F f, B1 & b1) : f(f), b1(b1) { }
   R operator()(P1 & p1) const { return f(p1, b1); }
 private:
   const F f;
   B1& b1;
+  void operator=(const Bind1&) /*= delete*/;
 };
 
 template <typename R, typename P1, typename B1, typename B2>
 class Bind2
 {
 public:
   typedef R (*F)(P1&, B1&, B2&);
   Bind2(F f, B1& b1, B2& b2) : f(f), b1(b1), b2(b2) { }
   R operator()(P1& p1) const { return f(p1, b1, b2); }
 private:
   const F f;
   B1& b1;
   B2& b2;
+  void operator=(const Bind2&) /*= delete*/;
+};
+
+template <typename R, typename P1, typename B1, typename B2, typename B3>
+class Bind3
+{
+public:
+  typedef R (*F)(P1&, B1, B2&, B3&);
+  Bind3(F f, B1& b1, B2& b2, B3& b3) : f(f), b1(b1), b2(b2), b3(b3) { }
+  R operator()(P1& p1) const { return f(p1, b1, b2, b3); }
+private:
+  const F f;
+  B1& b1;
+  B2& b2;
+  B3& b3;
+  void operator=(const Bind3&) /*= delete*/;
+};
+
+template <typename R, typename P1, typename B1, typename B2, typename B3,
+          typename B4>
+class Bind4
+{
+public:
+  typedef R (*F)(P1&, B1, B2, B3&, B4&);
+  Bind4(F f, B1& b1, B2& b2, B3& b3, B4& b4)
+    : f(f), b1(b1), b2(b2), b3(b3), b4(b4) { }
+  R operator()(P1& p1) const { return f(p1, b1, b2, b3, b4); }
+private:
+  const F f;
+  B1& b1;
+  B2& b2;
+  B3& b3;
+  B4& b4;
+  void operator=(const Bind4&) /*= delete*/;
 };
 
 } // namespace internal
 
 template <typename R, typename P1, typename B1>
 inline internal::Bind1<R, P1, B1>
 bind(R (*f)(P1&, B1&), Placeholder1&, B1& b1)
 {
   return internal::Bind1<R, P1, B1>(f, b1);
 }
 
 template <typename R, typename P1, typename B1, typename B2>
 inline internal::Bind2<R, P1, B1, B2>
-bind(R (*f)(P1&, B1&, B2&), Placeholder1 &, B1 & b1, B2 & b2)
+bind(R (*f)(P1&, B1&, B2&), Placeholder1&, B1& b1, B2& b2)
 {
   return internal::Bind2<R, P1, B1, B2>(f, b1, b2);
 }
 
-#endif // _MSC_VER
+template <typename R, typename P1, typename B1, typename B2, typename B3>
+inline internal::Bind3<R, P1, const B1, const B2, B3>
+bind(R (*f)(P1&, B1, const B2&, B3&), Placeholder1&, B1& b1, const B2& b2, B3& b3)
+{
+  return internal::Bind3<R, P1, const B1, const B2, B3>(f, b1, b2, b3);
+}
+
+template <typename R, typename P1, typename B1, typename B2, typename B3,
+          typename B4>
+inline internal::Bind4<R, P1, const B1, const B2, B3, B4>
+bind(R (*f)(P1&, B1, B2, B3&, B4&), Placeholder1&, const B1& b1, const B2& b2,
+     B3& b3, B4& b4)
+{
+  return internal::Bind4<R, P1, const B1, const B2, B3, B4>(f, b1, b2, b3, b4);
+}
+
+#endif
 
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix__bind_h
--- a/security/pkix/include/pkix/pkix.h
+++ b/security/pkix/include/pkix/pkix.h
@@ -89,18 +89,18 @@ namespace mozilla { namespace pkix {
 //                            distrust.
 // TODO(bug 968451): Document more of these.
 
 SECStatus BuildCertChain(TrustDomain& trustDomain,
                          CERTCertificate* cert,
                          PRTime time,
                          EndEntityOrCA endEntityOrCA,
             /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
-            /*optional*/ SECOidTag requiredEKUIfPresent,
-            /*optional*/ SECOidTag requiredPolicy,
+                         KeyPurposeId requiredEKUIfPresent,
+                         const CertPolicyId& requiredPolicy,
             /*optional*/ const SECItem* stapledOCSPResponse,
                  /*out*/ ScopedCERTCertList& results);
 
 // Verify the given signed data using the public key of the given certificate.
 // (EC)DSA parameter inheritance is not supported.
 SECStatus VerifySignedData(const CERTSignedData* sd,
                            const CERTCertificate* cert,
                            void* pkcs11PinArg);
--- a/security/pkix/include/pkix/pkixtypes.h
+++ b/security/pkix/include/pkix/pkixtypes.h
@@ -25,30 +25,50 @@
 #ifndef mozilla_pkix__pkixtypes_h
 #define mozilla_pkix__pkixtypes_h
 
 #include "pkix/enumclass.h"
 #include "pkix/ScopedPtr.h"
 #include "plarena.h"
 #include "cert.h"
 #include "keyhi.h"
+#include "stdint.h"
 
 namespace mozilla { namespace pkix {
 
 typedef ScopedPtr<PLArenaPool, PL_FreeArenaPool> ScopedPLArenaPool;
 
 typedef ScopedPtr<CERTCertificate, CERT_DestroyCertificate>
         ScopedCERTCertificate;
 typedef ScopedPtr<CERTCertList, CERT_DestroyCertList> ScopedCERTCertList;
 typedef ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey>
         ScopedSECKEYPublicKey;
 
+MOZILLA_PKIX_ENUM_CLASS EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
+
 typedef unsigned int KeyUsages;
 
-MOZILLA_PKIX_ENUM_CLASS EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
+MOZILLA_PKIX_ENUM_CLASS KeyPurposeId {
+  anyExtendedKeyUsage = 0,
+  id_kp_serverAuth = 1,           // id-kp-serverAuth
+  id_kp_clientAuth = 2,           // id-kp-clientAuth
+  id_kp_codeSigning = 3,          // id-kp-codeSigning
+  id_kp_emailProtection = 4,      // id-kp-emailProtection
+  id_kp_OCSPSigning = 9,          // id-kp-OCSPSigning
+};
+
+struct CertPolicyId {
+  uint16_t numBytes;
+  static const uint16_t MAX_BYTES = 24;
+  uint8_t bytes[MAX_BYTES];
+
+  bool IsAnyPolicy() const;
+
+  static const CertPolicyId anyPolicy;
+};
 
 MOZILLA_PKIX_ENUM_CLASS TrustLevel {
   TrustAnchor = 1,        // certificate is a trusted root CA certificate or
                           // equivalent *for the given policy*.
   ActivelyDistrusted = 2, // certificate is known to be bad
   InheritsTrust = 3       // certificate must chain to a trust anchor
 };
 
@@ -60,26 +80,26 @@ class TrustDomain
 {
 public:
   virtual ~TrustDomain() { }
 
   // Determine the level of trust in the given certificate for the given role.
   // This will be called for every certificate encountered during path
   // building.
   //
-  // When policy == SEC_OID_X509_ANY_POLICY, then no policy-related checking
-  // should be done. When policy != SEC_OID_X509_ANY_POLICY, then GetCertTrust
-  // MUST NOT return with *trustLevel == TrustAnchor unless the given cert is
-  // considered a trust anchor *for that policy*. In particular, if the user
-  // has marked an intermediate certificate as trusted, but that intermediate
-  // isn't in the list of EV roots, then GetCertTrust must result in
+  // When policy.IsAnyPolicy(), then no policy-related checking should be done.
+  // When !policy.IsAnyPolicy(), then GetCertTrust MUST NOT return with
+  // *trustLevel == TrustAnchor unless the given cert is considered a trust
+  // anchor *for that policy*. In particular, if the user has marked an
+  // intermediate certificate as trusted, but that intermediate isn't in the
+  // list of EV roots, then GetCertTrust must result in
   // *trustLevel == InheritsTrust instead of *trustLevel == TrustAnchor
   // (assuming the candidate cert is not actively distrusted).
   virtual SECStatus GetCertTrust(EndEntityOrCA endEntityOrCA,
-                                 SECOidTag policy,
+                                 const CertPolicyId& policy,
                                  const CERTCertificate* candidateCert,
                          /*out*/ TrustLevel* trustLevel) = 0;
 
   // Find all certificates (intermediate and/or root) in the certificate
   // database that have a subject name matching |encodedIssuerName| at
   // the given time. Certificates where the given time is not within the
   // certificate's validity period may be excluded. On input, |results|
   // will be null on input. If no potential issuers are found, then this
--- a/security/pkix/lib/pkixbind.cpp
+++ b/security/pkix/lib/pkixbind.cpp
@@ -17,19 +17,19 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#ifndef _MSC_VER
+#ifndef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
 
 #include "pkix/bind.h"
 
 namespace mozilla { namespace pkix {
 
 Placeholder1 _1;
 
 } } // namespace mozilla::pkix
 
-#endif // _MSC_VER
+#endif
--- a/security/pkix/lib/pkixbuild.cpp
+++ b/security/pkix/lib/pkixbuild.cpp
@@ -109,30 +109,30 @@ BackCert::Init()
   return Success;
 }
 
 static Result BuildForward(TrustDomain& trustDomain,
                            BackCert& subject,
                            PRTime time,
                            EndEntityOrCA endEntityOrCA,
                            KeyUsages requiredKeyUsagesIfPresent,
-                           SECOidTag requiredEKUIfPresent,
-                           SECOidTag requiredPolicy,
+                           KeyPurposeId requiredEKUIfPresent,
+                           const CertPolicyId& requiredPolicy,
                            /*optional*/ const SECItem* stapledOCSPResponse,
                            unsigned int subCACount,
                            /*out*/ ScopedCERTCertList& results);
 
 // The code that executes in the inner loop of BuildForward
 static Result
 BuildForwardInner(TrustDomain& trustDomain,
                   BackCert& subject,
                   PRTime time,
                   EndEntityOrCA endEntityOrCA,
-                  SECOidTag requiredEKUIfPresent,
-                  SECOidTag requiredPolicy,
+                  KeyPurposeId requiredEKUIfPresent,
+                  const CertPolicyId& requiredPolicy,
                   CERTCertificate* potentialIssuerCertToDup,
                   unsigned int subCACount,
                   ScopedCERTCertList& results)
 {
   PORT_Assert(potentialIssuerCertToDup);
 
   BackCert potentialIssuer(potentialIssuerCertToDup, &subject,
                            BackCert::IncludeCN::No);
@@ -191,18 +191,18 @@ BuildForwardInner(TrustDomain& trustDoma
 // chain has multiple problems. See the error ranking documentation in
 // pkix/pkix.h.
 static Result
 BuildForward(TrustDomain& trustDomain,
              BackCert& subject,
              PRTime time,
              EndEntityOrCA endEntityOrCA,
              KeyUsages requiredKeyUsagesIfPresent,
-             SECOidTag requiredEKUIfPresent,
-             SECOidTag requiredPolicy,
+             KeyPurposeId requiredEKUIfPresent,
+             const CertPolicyId& requiredPolicy,
              /*optional*/ const SECItem* stapledOCSPResponse,
              unsigned int subCACount,
              /*out*/ ScopedCERTCertList& results)
 {
   // Avoid stack overflows and poor performance by limiting cert length.
   // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests
   static const size_t MAX_DEPTH = 8;
   if (subCACount >= MAX_DEPTH - 1) {
@@ -331,18 +331,18 @@ BuildForward(TrustDomain& trustDomain,
 }
 
 SECStatus
 BuildCertChain(TrustDomain& trustDomain,
                CERTCertificate* certToDup,
                PRTime time,
                EndEntityOrCA endEntityOrCA,
                /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
-               /*optional*/ SECOidTag requiredEKUIfPresent,
-               /*optional*/ SECOidTag requiredPolicy,
+               /*optional*/ KeyPurposeId requiredEKUIfPresent,
+               const CertPolicyId& requiredPolicy,
                /*optional*/ const SECItem* stapledOCSPResponse,
                /*out*/ ScopedCERTCertList& results)
 {
   PORT_Assert(certToDup);
 
   if (!certToDup) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
@@ -350,17 +350,17 @@ BuildCertChain(TrustDomain& trustDomain,
 
   // The only non-const operation on the cert we are allowed to do is
   // CERT_DupCertificate.
 
   // XXX: Support the legacy use of the subject CN field for indicating the
   // domain name the certificate is valid for.
   BackCert::IncludeCN includeCN
     = endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
-      requiredEKUIfPresent == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH
+      requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth
     ? BackCert::IncludeCN::Yes
     : BackCert::IncludeCN::No;
 
   BackCert cert(certToDup, nullptr, includeCN);
   Result rv = cert.Init();
   if (rv != Success) {
     return SECFailure;
   }
--- a/security/pkix/lib/pkixcheck.cpp
+++ b/security/pkix/lib/pkixcheck.cpp
@@ -19,21 +19,21 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include <limits>
 
+#include "pkix/bind.h"
 #include "pkix/pkix.h"
 #include "pkixcheck.h"
 #include "pkixder.h"
 #include "pkixutil.h"
-#include "secder.h"
 
 namespace mozilla { namespace pkix {
 
 Result
 CheckTimes(const CERTCertificate* cert, PRTime time)
 {
   PR_ASSERT(cert);
 
@@ -102,180 +102,202 @@ CheckKeyUsage(EndEntityOrCA endEntityOrC
     //  return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     //}
   }
 
   return Success;
 }
 
 // RFC5820 4.2.1.4. Certificate Policies
-//
+
 // "The user-initial-policy-set contains the special value any-policy if the
 // user is not concerned about certificate policy."
-Result
-CheckCertificatePolicies(BackCert& cert, EndEntityOrCA endEntityOrCA,
-                         bool isTrustAnchor, SECOidTag requiredPolicy)
+//
+// id-ce OBJECT IDENTIFIER  ::=  {joint-iso-ccitt(2) ds(5) 29}
+// id-ce-certificatePolicies OBJECT IDENTIFIER ::=  { id-ce 32 }
+// anyPolicy OBJECT IDENTIFIER ::= { id-ce-certificatePolicies 0 }
+
+/*static*/ const CertPolicyId CertPolicyId::anyPolicy = {
+  4, { (40*2)+5, 29, 32, 0 }
+};
+
+bool CertPolicyId::IsAnyPolicy() const
 {
-  if (requiredPolicy == SEC_OID_X509_ANY_POLICY) {
-    return Success;
+  return this == &anyPolicy ||
+         (numBytes == anyPolicy.numBytes &&
+          !memcmp(bytes, anyPolicy.bytes, anyPolicy.numBytes));
+}
+
+// PolicyInformation ::= SEQUENCE {
+//         policyIdentifier   CertPolicyId,
+//         policyQualifiers   SEQUENCE SIZE (1..MAX) OF
+//                                 PolicyQualifierInfo OPTIONAL }
+inline der::Result
+CheckPolicyInformation(der::Input& input, EndEntityOrCA endEntityOrCA,
+                       const CertPolicyId& requiredPolicy,
+                       /*in/out*/ bool& found)
+{
+  if (input.MatchTLV(der::OIDTag, requiredPolicy.numBytes,
+                     requiredPolicy.bytes)) {
+    found = true;
+  } else if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+             input.MatchTLV(der::OIDTag, CertPolicyId::anyPolicy.numBytes,
+                            CertPolicyId::anyPolicy.bytes)) {
+    found = true;
   }
 
-  // It is likely some callers will pass SEC_OID_UNKNOWN when they don't care,
-  // instead of passing SEC_OID_X509_ANY_POLICY. Help them out by failing hard.
-  if (requiredPolicy == SEC_OID_UNKNOWN) {
-    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
-    return FatalError;
+  // RFC 5280 Section 4.2.1.4 says "Optional qualifiers, which MAY be present,
+  // are not expected to change the definition of the policy." Also, it seems
+  // that Section 6, which defines validation, does not require any matching of
+  // qualifiers. Thus, doing anything with the policy qualifiers would be a
+  // waste of time and a source of potential incompatibilities, so we just
+  // ignore them.
+
+  // Skip unmatched OID and/or policyQualifiers
+  input.SkipToEnd();
+
+  return der::Success;
+}
+
+// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
+Result
+CheckCertificatePolicies(EndEntityOrCA endEntityOrCA,
+                         const SECItem* encodedCertificatePolicies,
+                         const SECItem* encodedInhibitAnyPolicy,
+                         TrustLevel trustLevel,
+                         const CertPolicyId& requiredPolicy)
+{
+  if (requiredPolicy.numBytes == 0 ||
+      requiredPolicy.numBytes > sizeof requiredPolicy.bytes) {
+    return Fail(FatalError, SEC_ERROR_INVALID_ARGS);
+  }
+
+  // Ignore all policy information if the caller indicates any policy is
+  // acceptable. See TrustDomain::GetCertTrust and the policy part of
+  // BuildCertChain's documentation.
+  if (requiredPolicy.IsAnyPolicy()) {
+    return Success;
   }
 
   // Bug 989051. Until we handle inhibitAnyPolicy we will fail close when
   // inhibitAnyPolicy extension is present and we need to evaluate certificate
   // policies.
-  if (cert.encodedInhibitAnyPolicy) {
+  if (encodedInhibitAnyPolicy) {
     return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
   }
 
   // The root CA certificate may omit the policies that it has been
   // trusted for, so we cannot require the policies to be present in those
   // certificates. Instead, the determination of which roots are trusted for
   // which policies is made by the TrustDomain's GetCertTrust method.
-  if (isTrustAnchor && endEntityOrCA == EndEntityOrCA::MustBeCA) {
+  if (trustLevel == TrustLevel::TrustAnchor &&
+      endEntityOrCA == EndEntityOrCA::MustBeCA) {
     return Success;
   }
 
-  if (!cert.encodedCertificatePolicies) {
+  if (!encodedCertificatePolicies) {
     return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
   }
 
-  ScopedPtr<CERTCertificatePolicies, CERT_DestroyCertificatePoliciesExtension>
-    policies(CERT_DecodeCertificatePoliciesExtension(
-                cert.encodedCertificatePolicies));
-  if (!policies) {
-    return MapSECStatus(SECFailure);
+  bool found = false;
+
+  der::Input input;
+  if (input.Init(encodedCertificatePolicies->data,
+                 encodedCertificatePolicies->len) != der::Success) {
+    return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
+  }
+  if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE, der::EmptyAllowed::No,
+                    bind(CheckPolicyInformation, _1, endEntityOrCA,
+                         requiredPolicy, ref(found))) != der::Success) {
+    return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
+  }
+  if (der::End(input) != der::Success) {
+    return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
+  }
+  if (!found) {
+    return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
   }
 
-  for (const CERTPolicyInfo* const* policyInfos = policies->policyInfos;
-       *policyInfos; ++policyInfos) {
-    if ((*policyInfos)->oid == requiredPolicy) {
-      return Success;
-    }
-    // Intermediate certs are allowed to have the anyPolicy OID
-    if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
-        (*policyInfos)->oid == SEC_OID_X509_ANY_POLICY) {
-      return Success;
-    }
-  }
+  return Success;
+}
 
-  return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
-}
+static const long UNLIMITED_PATH_LEN = -1; // must be less than zero
 
 //  BasicConstraints ::= SEQUENCE {
 //          cA                      BOOLEAN DEFAULT FALSE,
 //          pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
-der::Result
-DecodeBasicConstraints(const SECItem* encodedBasicConstraints,
-                       CERTBasicConstraints& basicConstraints)
+static der::Result
+DecodeBasicConstraints(der::Input& input, /*out*/ bool& isCA,
+                       /*out*/ long& pathLenConstraint)
 {
-  PR_ASSERT(encodedBasicConstraints);
-  if (!encodedBasicConstraints) {
-    return der::Fail(SEC_ERROR_INVALID_ARGS);
-  }
-
-  basicConstraints.isCA = false;
-  basicConstraints.pathLenConstraint = 0;
-
-  der::Input input;
-  if (input.Init(encodedBasicConstraints->data, encodedBasicConstraints->len)
-        != der::Success) {
-    return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
-  }
-
-  if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) {
-    return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
-  }
-
-  bool isCA = false;
   // TODO(bug 989518): cA is by default false. According to DER, default
   // values must not be explicitly encoded in a SEQUENCE. So, if this
   // value is present and false, it is an encoding error. However, Go Daddy
   // has issued many certificates with this improper encoding, so we can't
   // enforce this yet (hence passing true for allowInvalidExplicitEncoding
   // to der::OptionalBoolean).
   if (der::OptionalBoolean(input, true, isCA) != der::Success) {
     return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
   }
-  basicConstraints.isCA = isCA;
 
-  if (input.Peek(der::INTEGER)) {
-    SECItem pathLenConstraintEncoded;
-    if (der::Integer(input, pathLenConstraintEncoded) != der::Success) {
-      return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
-    }
-    long pathLenConstraint = DER_GetInteger(&pathLenConstraintEncoded);
-    if (pathLenConstraint >= std::numeric_limits<int>::max() ||
-        pathLenConstraint < 0) {
-      return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
-    }
-    basicConstraints.pathLenConstraint = static_cast<int>(pathLenConstraint);
-    // TODO(bug 985025): If isCA is false, pathLenConstraint MUST NOT
-    // be included (as per RFC 5280 section 4.2.1.9), but for compatibility
-    // reasons, we don't check this for now.
-  } else if (basicConstraints.isCA) {
-    // If this is a CA but the path length is omitted, it is unlimited.
-    basicConstraints.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT;
+  // TODO(bug 985025): If isCA is false, pathLenConstraint MUST NOT
+  // be included (as per RFC 5280 section 4.2.1.9), but for compatibility
+  // reasons, we don't check this for now.
+  if (OptionalInteger(input, UNLIMITED_PATH_LEN, pathLenConstraint)
+        != der::Success) {
+    return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
   }
 
-  if (der::End(input) != der::Success) {
-    return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
-  }
   return der::Success;
 }
 
 // RFC5280 4.2.1.9. Basic Constraints (id-ce-basicConstraints)
 Result
-CheckBasicConstraints(const BackCert& cert,
-                      EndEntityOrCA endEntityOrCA,
-                      bool isTrustAnchor,
+CheckBasicConstraints(EndEntityOrCA endEntityOrCA,
+                      const SECItem* encodedBasicConstraints,
+                      const der::Version version, TrustLevel trustLevel,
                       unsigned int subCACount)
 {
-  CERTBasicConstraints basicConstraints;
-  if (cert.encodedBasicConstraints) {
-    if (DecodeBasicConstraints(cert.encodedBasicConstraints,
-                               basicConstraints) != der::Success) {
-      return RecoverableError;
+  bool isCA = false;
+  long pathLenConstraint = UNLIMITED_PATH_LEN;
+
+  if (encodedBasicConstraints) {
+    der::Input input;
+    if (input.Init(encodedBasicConstraints->data,
+                   encodedBasicConstraints->len) != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
+    }
+    if (der::Nested(input, der::SEQUENCE,
+                    bind(DecodeBasicConstraints, _1, ref(isCA),
+                         ref(pathLenConstraint))) != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
+    }
+    if (der::End(input) != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
     }
   } else {
-    // Synthesize a non-CA basic constraints by default
-    basicConstraints.isCA = false;
-    basicConstraints.pathLenConstraint = 0;
-
     // "If the basic constraints extension is not present in a version 3
     //  certificate, or the extension is present but the cA boolean is not
     //  asserted, then the certified public key MUST NOT be used to verify
     //  certificate signatures."
     //
     // For compatibility, we must accept v1 trust anchors without basic
     // constraints as CAs.
     //
     // TODO: add check for self-signedness?
-    if (endEntityOrCA == EndEntityOrCA::MustBeCA && isTrustAnchor) {
-      const CERTCertificate* nssCert = cert.GetNSSCert();
-      // We only allow trust anchor CA certs to omit the
-      // basicConstraints extension if they are v1. v1 is encoded
-      // implicitly.
-      if (!nssCert->version.data && !nssCert->version.len) {
-        basicConstraints.isCA = true;
-        basicConstraints.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT;
-      }
+    if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+        trustLevel == TrustLevel::TrustAnchor && version == der::Version::v1) {
+      isCA = true;
     }
   }
 
   if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
     // CA certificates are not trusted as EE certs.
 
-    if (basicConstraints.isCA) {
+    if (isCA) {
       // XXX: We use SEC_ERROR_CA_CERT_INVALID here so we can distinguish
       // this error from other errors, given that NSS does not have a "CA cert
       // used as end-entity" error code since it doesn't have such a
       // prohibition. We should add such an error code and stop abusing
       // SEC_ERROR_CA_CERT_INVALID this way.
       //
       // Note, in particular, that this check prevents a delegated OCSP
       // response signing certificate with the CA bit from successfully
@@ -285,25 +307,23 @@ CheckBasicConstraints(const BackCert& ce
     }
 
     return Success;
   }
 
   PORT_Assert(endEntityOrCA == EndEntityOrCA::MustBeCA);
 
   // End-entity certificates are not allowed to act as CA certs.
-  if (!basicConstraints.isCA) {
+  if (!isCA) {
     return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID);
   }
 
-  if (basicConstraints.pathLenConstraint >= 0) {
-    if (subCACount >
-           static_cast<unsigned int>(basicConstraints.pathLenConstraint)) {
-      return Fail(RecoverableError, SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
-    }
+  if (pathLenConstraint >= 0 &&
+      static_cast<long>(subCACount) > pathLenConstraint) {
+    return Fail(RecoverableError, SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
   }
 
   return Success;
 }
 
 Result
 BackCert::GetConstrainedNames(/*out*/ const CERTGeneralName** result)
 {
@@ -363,61 +383,130 @@ CheckNameConstraints(BackCert& cert)
       currentName = CERT_GetNextGeneralName(currentName);
     } while (currentName != names);
   }
 
   return Success;
 }
 
 // 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
-// 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
-Result
-CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedEKUs,
-                      SECOidTag requiredEKU)
+
+static der::Result
+MatchEKU(der::Input& value, KeyPurposeId requiredEKU,
+         EndEntityOrCA endEntityOrCA, /*in/out*/ bool& found,
+         /*in/out*/ bool& foundOCSPSigning)
 {
-  // TODO: Either do not allow anyExtendedKeyUsage to be passed as requiredEKU,
-  // or require that callers pass anyExtendedKeyUsage instead of
-  // SEC_OID_UNKNWON and disallow SEC_OID_UNKNWON.
+  // See Section 5.9 of "A Layman's Guide to a Subset of ASN.1, BER, and DER"
+  // for a description of ASN.1 DER encoding of OIDs.
+
+  // id-pkix  OBJECT IDENTIFIER  ::=
+  //            { iso(1) identified-organization(3) dod(6) internet(1)
+  //                    security(5) mechanisms(5) pkix(7) }
+  // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
+  // id-kp-serverAuth      OBJECT IDENTIFIER ::= { id-kp 1 }
+  // id-kp-clientAuth      OBJECT IDENTIFIER ::= { id-kp 2 }
+  // id-kp-codeSigning     OBJECT IDENTIFIER ::= { id-kp 3 }
+  // id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
+  // id-kp-OCSPSigning     OBJECT IDENTIFIER ::= { id-kp 9 }
+  static const uint8_t server[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 1 };
+  static const uint8_t client[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 2 };
+  static const uint8_t code  [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 3 };
+  static const uint8_t email [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 4 };
+  static const uint8_t ocsp  [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 9 };
+
+  // id-Netscape        OBJECT IDENTIFIER ::= { 2 16 840 1 113730 }
+  // id-Netscape-policy OBJECT IDENTIFIER ::= { id-Netscape 4 }
+  // id-Netscape-stepUp OBJECT IDENTIFIER ::= { id-Netscape-policy 1 }
+  static const uint8_t serverStepUp[] =
+    { (40*2)+16, 128+6,72, 1, 128+6,128+120,66, 4, 1 };
+
+  bool match = false;
+
+  if (!found) {
+    switch (requiredEKU) {
+      case KeyPurposeId::id_kp_serverAuth:
+        // Treat CA certs with step-up OID as also having SSL server type.
+        // Comodo has issued certificates that require this behavior that don't
+        // expire until June 2020! TODO(bug 982932): Limit this exception to
+        // old certificates.
+        match = value.MatchBytes(server) ||
+                (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+                 value.MatchBytes(serverStepUp));
+        break;
+
+      case KeyPurposeId::id_kp_clientAuth:
+        match = value.MatchBytes(client);
+        break;
 
+      case KeyPurposeId::id_kp_codeSigning:
+        match = value.MatchBytes(code);
+        break;
+
+      case KeyPurposeId::id_kp_emailProtection:
+        match = value.MatchBytes(email);
+        break;
+
+      case KeyPurposeId::id_kp_OCSPSigning:
+        match = value.MatchBytes(ocsp);
+        break;
+
+      case KeyPurposeId::anyExtendedKeyUsage:
+        PR_NOT_REACHED("anyExtendedKeyUsage should start with found==true");
+        return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
+
+      default:
+        PR_NOT_REACHED("unrecognized EKU");
+        return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
+    }
+  }
+
+  if (match) {
+    if (value.AtEnd()) {
+      found = true;
+      if (requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+        foundOCSPSigning = true;
+      }
+    }
+  } else if (value.MatchBytes(ocsp) && value.AtEnd()) {
+    foundOCSPSigning = true;
+  }
+
+  value.SkipToEnd(); // ignore unmatched OIDs.
+
+  return der::Success;
+}
+
+Result
+CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
+                      const SECItem* encodedExtendedKeyUsage,
+                      KeyPurposeId requiredEKU)
+{
   // XXX: We're using SEC_ERROR_INADEQUATE_CERT_TYPE here so that callers can
   // distinguish EKU mismatch from KU mismatch from basic constraints mismatch.
   // We should probably add a new error code that is more clear for this type
   // of problem.
 
   bool foundOCSPSigning = false;
 
-  if (encodedEKUs) {
-    ScopedPtr<CERTOidSequence, CERT_DestroyOidSequence>
-      seq(CERT_DecodeOidSequence(encodedEKUs));
-    if (!seq) {
-      PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
-      return RecoverableError;
-    }
-
-    bool found = false;
+  if (encodedExtendedKeyUsage) {
+    bool found = requiredEKU == KeyPurposeId::anyExtendedKeyUsage;
 
-    // XXX: We allow duplicate entries.
-    for (const SECItem* const* oids = seq->oids; oids && *oids; ++oids) {
-      SECOidTag oidTag = SECOID_FindOIDTag(*oids);
-      if (requiredEKU != SEC_OID_UNKNOWN && oidTag == requiredEKU) {
-        found = true;
-      } else {
-        // Treat CA certs with step-up OID as also having SSL server type.
-        // COMODO has issued certificates that require this behavior
-        // that don't expire until June 2020!
-        // TODO 982932: Limit this expection to old certificates
-        if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
-            requiredEKU == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH &&
-            oidTag == SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) {
-          found = true;
-        }
-      }
-      if (oidTag == SEC_OID_OCSP_RESPONDER) {
-        foundOCSPSigning = true;
-      }
+    der::Input input;
+    if (input.Init(encodedExtendedKeyUsage->data,
+                   encodedExtendedKeyUsage->len) != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
+    }
+    if (der::NestedOf(input, der::SEQUENCE, der::OIDTag, der::EmptyAllowed::No,
+                      bind(MatchEKU, _1, requiredEKU, endEntityOrCA,
+                           ref(found), ref(foundOCSPSigning)))
+          != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
+    }
+    if (der::End(input) != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
 
     // If the EKU extension was included, then the required EKU must be in the
     // list.
     if (!found) {
       return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
   }
@@ -432,46 +521,44 @@ CheckExtendedKeyUsage(EndEntityOrCA endE
     // if not for this check.
     // That said, we accept CA certificates with id-kp-OCSPSigning because
     // some CAs in Mozilla's CA program have issued such intermediate
     // certificates, and because some CAs have reported some Microsoft server
     // software wrongly requires CA certificates to have id-kp-OCSPSigning.
     // Allowing this exception does not cause any security issues because we
     // require delegated OCSP response signing certificates to be end-entity
     // certificates.
-    if (foundOCSPSigning && requiredEKU != SEC_OID_OCSP_RESPONDER) {
-      PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
-      return RecoverableError;
+    if (foundOCSPSigning && requiredEKU != KeyPurposeId::id_kp_OCSPSigning) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
     // http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
     // "OCSP signing delegation SHALL be designated by the inclusion of
     // id-kp-OCSPSigning in an extended key usage certificate extension
     // included in the OCSP response signer's certificate."
     //
     // id-kp-OCSPSigning is the only EKU that isn't implicitly assumed when the
     // EKU extension is missing from an end-entity certificate. However, any CA
     // certificate can issue a delegated OCSP response signing certificate, so
     // we can't require the EKU be explicitly included for CA certificates.
-    if (!foundOCSPSigning && requiredEKU == SEC_OID_OCSP_RESPONDER) {
-      PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
-      return RecoverableError;
+    if (!foundOCSPSigning && requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
   }
 
   return Success;
 }
 
 Result
 CheckIssuerIndependentProperties(TrustDomain& trustDomain,
                                  BackCert& cert,
                                  PRTime time,
                                  EndEntityOrCA endEntityOrCA,
                                  KeyUsages requiredKeyUsagesIfPresent,
-                                 SECOidTag requiredEKUIfPresent,
-                                 SECOidTag requiredPolicy,
+                                 KeyPurposeId requiredEKUIfPresent,
+                                 const CertPolicyId& requiredPolicy,
                                  unsigned int subCACount,
                 /*optional out*/ TrustLevel* trustLevelOut)
 {
   Result rv;
 
   TrustLevel trustLevel;
   rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
                                              requiredPolicy,
@@ -488,18 +575,23 @@ CheckIssuerIndependentProperties(TrustDo
     // The TrustDomain returned a trust level that we weren't expecting.
     PORT_SetError(PR_INVALID_STATE_ERROR);
     return FatalError;
   }
   if (trustLevelOut) {
     *trustLevelOut = trustLevel;
   }
 
-  bool isTrustAnchor = endEntityOrCA == EndEntityOrCA::MustBeCA &&
-                       trustLevel == TrustLevel::TrustAnchor;
+  // XXX: Good enough for now. There could be an illegal explicit version
+  // number or one we don't support, but we can safely treat those all as v3
+  // for now since processing of v3 certificates is strictly more strict than
+  // processing of v1 certificates.
+  der::Version version = (!cert.GetNSSCert()->version.data &&
+                          !cert.GetNSSCert()->version.len) ? der::Version::v1
+                                                           : der::Version::v3;
 
   PLArenaPool* arena = cert.GetArena();
   if (!arena) {
     return FatalError;
   }
 
   // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136).
 
@@ -508,17 +600,18 @@ CheckIssuerIndependentProperties(TrustDo
   // 4.2.1.3. Key Usage
   rv = CheckKeyUsage(endEntityOrCA, cert.encodedKeyUsage,
                      requiredKeyUsagesIfPresent, arena);
   if (rv != Success) {
     return rv;
   }
 
   // 4.2.1.4. Certificate Policies
-  rv = CheckCertificatePolicies(cert, endEntityOrCA, isTrustAnchor,
+  rv = CheckCertificatePolicies(endEntityOrCA, cert.encodedCertificatePolicies,
+                                cert.encodedInhibitAnyPolicy, trustLevel,
                                 requiredPolicy);
   if (rv != Success) {
     return rv;
   }
 
   // 4.2.1.5. Policy Mappings are not supported; see the documentation about
   //          policy enforcement in pkix.h.
 
@@ -526,17 +619,18 @@ CheckIssuerIndependentProperties(TrustDo
   //          checking and during name verification (CERT_VerifyCertName).
 
   // 4.2.1.7. Issuer Alternative Name is not something that needs checking.
 
   // 4.2.1.8. Subject Directory Attributes is not something that needs
   //          checking.
 
   // 4.2.1.9. Basic Constraints.
-  rv = CheckBasicConstraints(cert, endEntityOrCA, isTrustAnchor, subCACount);
+  rv = CheckBasicConstraints(endEntityOrCA, cert.encodedBasicConstraints,
+                             version, trustLevel, subCACount);
   if (rv != Success) {
     return rv;
   }
 
   // 4.2.1.10. Name Constraints is dealt with in during path building.
 
   // 4.2.1.11. Policy Constraints are implicitly supported; see the
   //           documentation about policy enforcement in pkix.h.
--- a/security/pkix/lib/pkixcheck.h
+++ b/security/pkix/lib/pkixcheck.h
@@ -20,29 +20,30 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef mozilla_pkix__pkixcheck_h
 #define mozilla_pkix__pkixcheck_h
 
+#include "pkix/pkixtypes.h"
 #include "pkixutil.h"
 #include "certt.h"
 
 namespace mozilla { namespace pkix {
 
 Result CheckIssuerIndependentProperties(
           TrustDomain& trustDomain,
           BackCert& cert,
           PRTime time,
           EndEntityOrCA endEntityOrCA,
           KeyUsages requiredKeyUsagesIfPresent,
-          SECOidTag requiredEKUIfPresent,
-          SECOidTag requiredPolicy,
+          KeyPurposeId requiredEKUIfPresent,
+          const CertPolicyId& requiredPolicy,
           unsigned int subCACount,
           /*optional out*/ TrustLevel* trustLevel = nullptr);
 
 Result CheckNameConstraints(BackCert& cert);
 
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix__pkixcheck_h
--- a/security/pkix/lib/pkixder.cpp
+++ b/security/pkix/lib/pkixder.cpp
@@ -29,16 +29,18 @@ namespace mozilla { namespace pkix { nam
 // not inline
 Result
 Fail(PRErrorCode errorCode)
 {
   PR_SetError(errorCode, 0);
   return Failure;
 }
 
+namespace internal {
+
 // Too complicated to be inline
 Result
 ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length)
 {
   PR_ASSERT((expectedTag & 0x1F) != 0x1F); // high tag number form not allowed
 
   uint8_t tag;
   if (input.Read(tag) != Success) {
@@ -81,9 +83,11 @@ ExpectTagAndGetLength(Input& input, uint
     // We don't support lengths larger than 2^16 - 1.
     return Fail(SEC_ERROR_BAD_DER);
   }
 
   // Ensure the input is long enough for the length it says it has.
   return input.EnsureLength(length);
 }
 
+} // namespace internal
+
 } } } // namespace mozilla::pkix::der
--- a/security/pkix/lib/pkixder.h
+++ b/security/pkix/lib/pkixder.h
@@ -20,16 +20,28 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef mozilla_pkix__pkixder_h
 #define mozilla_pkix__pkixder_h
 
+// Expect* functions advance the input mark and return Success if the input
+// matches the given criteria; they return Failure with the input mark in an
+// undefined state if the input does not match the criteria.
+//
+// Match* functions advance the input mark and return true if the input matches
+// the given criteria; they return false without changing the input mark if the
+// input does not match the criteria.
+//
+// Skip* functions unconditionally advance the input mark and return Success if
+// they are able to do so; otherwise they return Failure with the input mark in
+// an undefined state.
+
 #include "pkix/enumclass.h"
 #include "pkix/nullptr.h"
 
 #include "prerror.h"
 #include "prlog.h"
 #include "secder.h"
 #include "secerr.h"
 #include "secoidt.h"
@@ -133,16 +145,54 @@ public:
       return Fail(SEC_ERROR_BAD_DER);
     }
     out = *input++;
     out <<= 8u;
     out |= *input++;
     return Success;
   }
 
+  template <uint16_t N>
+  bool MatchBytes(const uint8_t (&toMatch)[N])
+  {
+    if (EnsureLength(N) != Success) {
+      return false;
+    }
+    if (memcmp(input, toMatch, N)) {
+      return false;
+    }
+    input += N;
+    return true;
+  }
+
+  template <uint16_t N>
+  bool MatchTLV(uint8_t tag, uint16_t len, const uint8_t (&value)[N])
+  {
+    static_assert(N <= 127, "buffer larger than largest length supported");
+    if (len > N) {
+      PR_NOT_REACHED("overflow prevented dynamically instead of statically");
+      return false;
+    }
+    uint16_t totalLen = 2u + len;
+    if (EnsureLength(totalLen) != Success) {
+      return false;
+    }
+    if (*input != tag) {
+      return false;
+    }
+    if (*(input + 1) != len) {
+      return false;
+    }
+    if (memcmp(input + 2, value, len)) {
+      return false;
+    }
+    input += totalLen;
+    return true;
+  }
+
   Result Skip(uint16_t len)
   {
     if (EnsureLength(len) != Success) {
       return Fail(SEC_ERROR_BAD_DER);
     }
     input += len;
     return Success;
   }
@@ -234,76 +284,98 @@ ExpectTagAndLength(Input& input, uint8_t
 
   if (tagAndLength != expectedTagAndLength) {
     return Fail(SEC_ERROR_BAD_DER);
   }
 
   return Success;
 }
 
+namespace internal {
+
 Result
 ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length);
 
+} // namespace internal
+
 inline Result
-ExpectTagAndIgnoreLength(Input& input, uint8_t expectedTag)
+ExpectTagAndSkipLength(Input& input, uint8_t expectedTag)
 {
   uint16_t ignored;
-  return ExpectTagAndGetLength(input, expectedTag, ignored);
+  return internal::ExpectTagAndGetLength(input, expectedTag, ignored);
+}
+
+inline Result
+ExpectTagAndSkipValue(Input& input, uint8_t tag)
+{
+  uint16_t length;
+  if (internal::ExpectTagAndGetLength(input, tag, length) != Success) {
+    return Failure;
+  }
+  return input.Skip(length);
+}
+
+inline Result
+ExpectTagAndGetValue(Input& input, uint8_t tag, /*out*/ SECItem& value)
+{
+  uint16_t length;
+  if (internal::ExpectTagAndGetLength(input, tag, length) != Success) {
+    return Failure;
+  }
+  return input.Skip(length, value);
+}
+
+inline Result
+ExpectTagAndGetValue(Input& input, uint8_t tag, /*out*/ Input& value)
+{
+  uint16_t length;
+  if (internal::ExpectTagAndGetLength(input, tag, length) != Success) {
+    return Failure;
+  }
+  return input.Skip(length, value);
 }
 
 inline Result
 End(Input& input)
 {
   if (!input.AtEnd()) {
     return Fail(SEC_ERROR_BAD_DER);
   }
 
   return Success;
 }
 
 template <typename Decoder>
 inline Result
 Nested(Input& input, uint8_t tag, Decoder decoder)
 {
-  uint16_t length;
-  if (ExpectTagAndGetLength(input, tag, length) != Success) {
+  Input nested;
+  if (ExpectTagAndGetValue(input, tag, nested) != Success) {
     return Failure;
   }
-
-  Input nested;
-  if (input.Skip(length, nested) != Success) {
-    return Failure;
-  }
-
   if (decoder(nested) != Success) {
     return Failure;
   }
-
   return End(nested);
 }
 
 template <typename Decoder>
 inline Result
 Nested(Input& input, uint8_t outerTag, uint8_t innerTag, Decoder decoder)
 {
   // XXX: This doesn't work (in VS2010):
   // return Nested(input, outerTag, bind(Nested, _1, innerTag, decoder));
 
-  uint16_t length;
-  if (ExpectTagAndGetLength(input, outerTag, length) != Success) {
-    return Failure;
-  }
   Input nestedInput;
-  if (input.Skip(length, nestedInput) != Success) {
+  if (ExpectTagAndGetValue(input, outerTag, nestedInput) != Success) {
     return Failure;
   }
   if (Nested(nestedInput, innerTag, decoder) != Success) {
     return Failure;
   }
-
   return End(nestedInput);
 }
 
 // This can be used to decode constructs like this:
 //
 //     ...
 //     foos SEQUENCE OF Foo,
 //     ...
@@ -319,23 +391,18 @@ Nested(Input& input, uint8_t outerTag, u
 //
 // In this example, Foo will get called once for each element of foos.
 //
 template <typename Decoder>
 inline Result
 NestedOf(Input& input, uint8_t outerTag, uint8_t innerTag,
          EmptyAllowed mayBeEmpty, Decoder decoder)
 {
-  uint16_t responsesLength;
-  if (ExpectTagAndGetLength(input, outerTag, responsesLength) != Success) {
-    return Failure;
-  }
-
   Input inner;
-  if (input.Skip(responsesLength, inner) != Success) {
+  if (ExpectTagAndGetValue(input, outerTag, inner) != Success) {
     return Failure;
   }
 
   if (inner.AtEnd()) {
     if (mayBeEmpty != EmptyAllowed::Yes) {
       return Fail(SEC_ERROR_BAD_DER);
     }
     return Success;
@@ -345,37 +412,43 @@ NestedOf(Input& input, uint8_t outerTag,
     if (Nested(inner, innerTag, decoder) != Success) {
       return Failure;
     }
   } while (!inner.AtEnd());
 
   return Success;
 }
 
-inline Result
-Skip(Input& input, uint8_t tag)
+// Universal types
+
+namespace internal {
+
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
+template <typename T> inline Result
+IntegralValue(Input& input, uint8_t tag, T& value)
 {
-  uint16_t length;
-  if (ExpectTagAndGetLength(input, tag, length) != Success) {
+  // Conveniently, all the Integers that we actually have to be able to parse
+  // are positive and very small. Consequently, this parser is *much* simpler
+  // than a general Integer parser would need to be.
+  if (ExpectTagAndLength(input, tag, 1) != Success) {
     return Failure;
   }
-  return input.Skip(length);
+  uint8_t valueByte;
+  if (input.Read(valueByte) != Success) {
+    return Failure;
+  }
+  if (valueByte & 0x80) { // negative
+    return Fail(SEC_ERROR_BAD_DER);
+  }
+  value = valueByte;
+  return Success;
 }
 
-inline Result
-Skip(Input& input, uint8_t tag, /*out*/ SECItem& value)
-{
-  uint16_t length;
-  if (ExpectTagAndGetLength(input, tag, length) != Success) {
-    return Failure;
-  }
-  return input.Skip(length, value);
-}
-
-// Universal types
+} // namespace internal
 
 inline Result
 Boolean(Input& input, /*out*/ bool& value)
 {
   if (ExpectTagAndLength(input, BOOLEAN, 1) != Success) {
     return Failure;
   }
 
@@ -406,71 +479,71 @@ OptionalBoolean(Input& input, bool allow
     }
     if (!allowInvalidExplicitEncoding && !value) {
       return Fail(SEC_ERROR_BAD_DER);
     }
   }
   return Success;
 }
 
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
 inline Result
 Enumerated(Input& input, uint8_t& value)
 {
-  if (ExpectTagAndLength(input, ENUMERATED | 0, 1) != Success) {
-    return Failure;
-  }
-  return input.Read(value);
+  return internal::IntegralValue(input, ENUMERATED | 0, value);
 }
 
 inline Result
 GeneralizedTime(Input& input, PRTime& time)
 {
-  uint16_t length;
   SECItem encoded;
-  if (ExpectTagAndGetLength(input, GENERALIZED_TIME, length) != Success) {
-    return Failure;
-  }
-  if (input.Skip(length, encoded) != Success) {
+  if (ExpectTagAndGetValue(input, GENERALIZED_TIME, encoded) != Success) {
     return Failure;
   }
   if (DER_GeneralizedTimeToTime(&time, &encoded) != SECSuccess) {
     return Failure;
   }
+  return Success;
+}
 
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
+inline Result
+Integer(Input& input, /*out*/ uint8_t& value)
+{
+  if (internal::IntegralValue(input, INTEGER, value) != Success) {
+    return Failure;
+  }
   return Success;
 }
 
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed. The default value must be
+// -1; defaultValue is only a parameter to make it clear in the calling code
+// what the default value is.
 inline Result
-Integer(Input& input, /*out*/ SECItem& value)
+OptionalInteger(Input& input, long defaultValue, /*out*/ long& value)
 {
-  uint16_t length;
-  if (ExpectTagAndGetLength(input, INTEGER, length) != Success) {
-    return Failure;
-  }
-
-  if (input.Skip(length, value) != Success) {
-    return Failure;
+  // If we need to support a different default value in the future, we need to
+  // test that parsedValue != defaultValue.
+  if (defaultValue != -1) {
+    return Fail(SEC_ERROR_INVALID_ARGS);
   }
 
-  if (value.len == 0) {
-    return Fail(SEC_ERROR_BAD_DER);
+  if (!input.Peek(INTEGER)) {
+    value = defaultValue;
+    return Success;
   }
 
-  // Check for overly-long encodings. If the first byte is 0x00 then the high
-  // bit on the second byte must be 1; otherwise the same *positive* value
-  // could be encoded without the leading 0x00 byte. If the first byte is 0xFF
-  // then the second byte must NOT have its high bit set; otherwise the same
-  // *negative* value could be encoded without the leading 0xFF byte.
-  if (value.len > 1) {
-    if ((value.data[0] == 0x00 && (value.data[1] & 0x80) == 0) ||
-        (value.data[0] == 0xff && (value.data[1] & 0x80) != 0)) {
-      return Fail(SEC_ERROR_BAD_DER);
-    }
+  uint8_t parsedValue;
+  if (Integer(input, parsedValue) != Success) {
+    return Failure;
   }
-
+  value = parsedValue;
   return Success;
 }
 
 inline Result
 Null(Input& input)
 {
   return ExpectTagAndLength(input, NULLTag, 0);
 }
@@ -489,42 +562,62 @@ OID(Input& input, const uint8_t (&expect
 // PKI-specific types
 
 // AlgorithmIdentifier  ::=  SEQUENCE  {
 //         algorithm               OBJECT IDENTIFIER,
 //         parameters              ANY DEFINED BY algorithm OPTIONAL  }
 inline Result
 AlgorithmIdentifier(Input& input, SECAlgorithmID& algorithmID)
 {
-  if (Skip(input, OIDTag, algorithmID.algorithm) != Success) {
+  if (ExpectTagAndGetValue(input, OIDTag, algorithmID.algorithm) != Success) {
     return Failure;
   }
   algorithmID.parameters.data = nullptr;
   algorithmID.parameters.len = 0;
   if (input.AtEnd()) {
     return Success;
   }
   return Null(input);
 }
 
 inline Result
-CertificateSerialNumber(Input& input, /*out*/ SECItem& serialNumber)
+CertificateSerialNumber(Input& input, /*out*/ SECItem& value)
 {
   // http://tools.ietf.org/html/rfc5280#section-4.1.2.2:
   //
   // * "The serial number MUST be a positive integer assigned by the CA to
   //   each certificate."
   // * "Certificate users MUST be able to handle serialNumber values up to 20
   //   octets. Conforming CAs MUST NOT use serialNumber values longer than 20
   //   octets."
   // * "Note: Non-conforming CAs may issue certificates with serial numbers
   //   that are negative or zero.  Certificate users SHOULD be prepared to
   //   gracefully handle such certificates."
 
-  return Integer(input, serialNumber);
+  if (ExpectTagAndGetValue(input, INTEGER, value) != Success) {
+    return Failure;
+  }
+
+  if (value.len == 0) {
+    return Fail(SEC_ERROR_BAD_DER);
+  }
+
+  // Check for overly-long encodings. If the first byte is 0x00 then the high
+  // bit on the second byte must be 1; otherwise the same *positive* value
+  // could be encoded without the leading 0x00 byte. If the first byte is 0xFF
+  // then the second byte must NOT have its high bit set; otherwise the same
+  // *negative* value could be encoded without the leading 0xFF byte.
+  if (value.len > 1) {
+    if ((value.data[0] == 0x00 && (value.data[1] & 0x80) == 0) ||
+        (value.data[0] == 0xff && (value.data[1] & 0x80) != 0)) {
+      return Fail(SEC_ERROR_BAD_DER);
+    }
+  }
+
+  return Success;
 }
 
 // x.509 and OCSP both use this same version numbering scheme, though OCSP
 // only supports v1.
 enum Version { v1 = 0, v2 = 1, v3 = 2 };
 
 // X.509 Certificate and OCSP ResponseData both use this
 // "[0] EXPLICIT Version DEFAULT <defaultVersion>" construct, but with
--- a/security/pkix/lib/pkixocsp.cpp
+++ b/security/pkix/lib/pkixocsp.cpp
@@ -141,18 +141,18 @@ CheckOCSPResponseSignerCert(TrustDomain&