merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 07 Mar 2014 13:06:05 +0100
changeset 189664 0dc1be930880824493f072a00a47ef06ca79f0a1
parent 189636 ff96e428da76e07a74cbd67808141621f1387fa7 (current diff)
parent 189663 f6a9862a0d2b065ca467f4cb30ffd2378e41a920 (diff)
child 189665 36c081b72fd8801fc6c697aee1684362ccde551d
child 189701 10f476147e93fe055d4778e4b3e6691ae777a32b
child 189732 af1a435e117a7035d5e9f1464663ab5f49ea464f
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1115,16 +1115,24 @@ pref("devtools.appmanager.manifestEditor
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.selectedTool", "webconsole");
 pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
 pref("devtools.toolbox.sideEnabled", true);
 pref("devtools.toolbox.zoomValue", "1");
 
+// Toolbox Button preferences
+pref("devtools.command-button-pick.enabled", true);
+pref("devtools.command-button-splitconsole.enabled", true);
+pref("devtools.command-button-paintflashing.enabled", false);
+pref("devtools.command-button-tilt.enabled", false);
+pref("devtools.command-button-scratchpad.enabled", false);
+pref("devtools.command-button-responsive.enabled", true);
+
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 // What was the last active sidebar in the inspector
 pref("devtools.inspector.activeSidebar", "ruleview");
 // Enable the markup preview
 pref("devtools.inspector.markupPreview", false);
 pref("devtools.inspector.remote", false);
--- a/browser/base/content/tab-shape.inc.svg
+++ b/browser/base/content/tab-shape.inc.svg
@@ -4,8 +4,12 @@
 
 <svg:clipPath id="tab-curve-clip-path-start" clipPathUnits="objectBoundingBox">
   <svg:path d="m 1,0.0625 0.05,0 0,0.938 -1,0 0,-0.028 C 0.32082458,0.95840561 0.4353096,0.81970962 0.48499998,0.5625 0.51819998,0.3905 0.535,0.0659 1,0.0625 z"/>
 </svg:clipPath>
 
 <svg:clipPath id="tab-curve-clip-path-end" clipPathUnits="objectBoundingBox">
   <svg:path d="m 0,0.0625 -0.05,0 0,0.938 1,0 0,-0.028 C 0.67917542,0.95840561 0.56569036,0.81970962 0.51599998,0.5625 0.48279998,0.3905 0.465,0.0659 0,0.0625 z"/>
 </svg:clipPath>
+
+<svg:clipPath id="tab-hover-clip-path" clipPathUnits="objectBoundingBox">
+  <svg:path d="M 0,0.2 0,1 1,1, 1,0.2 z"/>
+</svg:clipPath>
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -232,17 +232,18 @@
   </vbox>
 </panel>
 
 <panel id="customization-tipPanel"
        type="arrow"
        flip="none"
        side="left"
        position="leftcenter topright"
-       noautohide="true">
+       noautohide="true"
+       hidden="true">
   <hbox class="customization-tipPanel-wrapper">
     <vbox class="customization-tipPanel-infoBox"/>
     <vbox class="customization-tipPanel-content" flex="1">
       <description class="customization-tipPanel-contentMessage"/>
       <image class="customization-tipPanel-contentImage"/>
     </vbox>
     <vbox pack="start" align="end" class="customization-tipPanel-closeBox">
       <toolbarbutton oncommand="gCustomizeMode.hideTip()" class="close-icon"/>
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -564,16 +564,17 @@ CustomizeMode.prototype = {
       messageNode.querySelector("." + kLabelClass).addEventListener("click", () => {
         let url = Services.urlFormatter.formatURLPref("browser.customizemode.tip0.learnMoreUrl");
         let browser = this.browser;
         browser.selectedTab = browser.addTab(url);
         this.hideTip();
       });
     }
 
+    this.tipPanel.hidden = false;
     this.tipPanel.openPopup(anchorNode);
     Services.prefs.setBoolPref(kShownPref, true);
   },
 
   hideTip: function() {
     this.tipPanel.hidePopup();
   },
 
--- a/browser/devtools/framework/options-panel.css
+++ b/browser/devtools/framework/options-panel.css
@@ -42,17 +42,12 @@
 
 .options-citation-label {
   font-size: 1rem !important;
   /* !important is required otherwise font-size will still be 1.4rem */
   font-style: italic;
   padding: 4px 0 0; /* To align it with the checkbox */
 }
 
-.options-citation-label + label {
-  padding: 3px 0 0 !important; /* To align it with the checkbox */
-  font-style: italic;
-}
-
 .hidden-labels-box:not(.visible) > label,
 .hidden-labels-box.visible ~ .hidden-labels-box > label:last-child {
   display: none;
 }
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -10,16 +10,17 @@ support-files =
 [browser_keybindings.js]
 [browser_new_activation_workflow.js]
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_highlight.js]
 [browser_toolbox_hosts.js]
 [browser_toolbox_options.js]
+[browser_toolbox_options_disable_buttons.js]
 [browser_toolbox_options_disable_cache.js]
 [browser_toolbox_options_disable_js.js]
 # [browser_toolbox_raise.js] # Bug 962258
 # skip-if = os == "win"
 [browser_toolbox_ready.js]
 [browser_toolbox_select_event.js]
 [browser_toolbox_sidebar.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_options_disable_buttons.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
+    gDevTools.showToolbox(target)
+      .then(testSelectTool)
+      .then(testToggleToolboxButtons)
+      .then(testPrefsAreRespectedWhenReopeningToolbox)
+      .then(cleanup, errorHandler);
+  }, true);
+
+  content.location = "data:text/html;charset=utf8,test for dynamically registering and unregistering tools";
+}
+
+function testPrefsAreRespectedWhenReopeningToolbox() {
+  let deferred = promise.defer();
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  info ("Closing toolbox to test after reopening");
+  gDevTools.closeToolbox(target).then(() => {
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.showToolbox(target)
+      .then(testSelectTool)
+      .then(() => {
+        info ("Toolbox has been reopened.  Checking UI state.");
+        testPreferenceAndUIStateIsConsistent();
+        deferred.resolve();
+      });
+  });
+
+  return deferred.promise;
+}
+
+function testSelectTool(aToolbox) {
+  let deferred = promise.defer();
+  info ("Selecting the options panel");
+
+  toolbox = aToolbox;
+  doc = toolbox.doc;
+  toolbox.once("options-selected", (event, tool) => {
+    ok(true, "Options panel selected via selectTool method");
+    panelWin = tool.panelWin;
+    deferred.resolve();
+  });
+  toolbox.selectTool("options");
+
+  return deferred.promise;
+}
+
+function testPreferenceAndUIStateIsConsistent() {
+  let checkNodes = [...panelWin.document.querySelectorAll("#enabled-toolbox-buttons-box > checkbox")];
+  let toolboxButtonNodes = [...doc.querySelectorAll("#toolbox-buttons > toolbarbutton")];
+  let toggleableTools = toolbox.toolboxButtons;
+
+  for (let tool of toggleableTools) {
+    let isVisible = getBoolPref(tool.visibilityswitch);
+
+    let button = toolboxButtonNodes.filter(button=>button.id === tool.id)[0];
+    is (!button.hasAttribute("hidden"), isVisible, "Button visibility matches pref for " + tool.id);
+
+    let check = checkNodes.filter(node=>node.id === tool.id)[0];
+    is (check.checked, isVisible, "Checkbox should be selected based on current pref for " + tool.id);
+  }
+}
+
+function testToggleToolboxButtons() {
+  let checkNodes = [...panelWin.document.querySelectorAll("#enabled-toolbox-buttons-box > checkbox")];
+  let toolboxButtonNodes = [...doc.querySelectorAll("#toolbox-buttons > toolbarbutton")];
+  let visibleButtons = toolboxButtonNodes.filter(button=>!button.hasAttribute("hidden"));
+  let toggleableTools = toolbox.toolboxButtons;
+
+  is (checkNodes.length, toggleableTools.length, "All of the buttons are toggleable." );
+  is (checkNodes.length, toolboxButtonNodes.length, "All of the DOM buttons are toggleable." );
+
+  for (let tool of toggleableTools) {
+    let id = tool.id;
+    let matchedCheckboxes = checkNodes.filter(node=>node.id === id);
+    let matchedButtons = toolboxButtonNodes.filter(button=>button.id === id);
+    ok (matchedCheckboxes.length === 1,
+      "There should be a single toggle checkbox for: " + id);
+    ok (matchedButtons.length === 1,
+      "There should be a DOM button for: " + id);
+    is (matchedButtons[0], tool.button,
+      "DOM buttons should match for: " + id);
+
+    is (matchedCheckboxes[0].getAttribute("label"), tool.label,
+      "The label for checkbox matches the tool definition.")
+    is (matchedButtons[0].getAttribute("tooltiptext"), tool.label,
+      "The tooltip for button matches the tool definition.")
+  }
+
+  // Store modified pref names so that they can be cleared on error.
+  for (let tool of toggleableTools) {
+    let pref = tool.visibilityswitch;
+    modifiedPrefs.push(pref);
+  }
+
+  // Try checking each checkbox, making sure that it changes the preference
+  for (let node of checkNodes) {
+    let tool = toggleableTools.filter(tool=>tool.id === node.id)[0];
+    let isVisible = getBoolPref(tool.visibilityswitch);
+
+    testPreferenceAndUIStateIsConsistent();
+    toggleButton(node);
+    testPreferenceAndUIStateIsConsistent();
+
+    let isVisibleAfterClick = getBoolPref(tool.visibilityswitch);
+
+    is (isVisible, !isVisibleAfterClick,
+      "Clicking on the node should have toggled visibility preference for " + tool.visibilityswitch);
+  }
+
+  return promise.resolve();
+}
+
+function getBoolPref(key) {
+  return Services.prefs.getBoolPref(key);
+}
+
+function toggleButton(node) {
+  node.scrollIntoView();
+  EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
+}
+
+function cleanup() {
+  toolbox.destroy().then(function() {
+    gBrowser.removeCurrentTab();
+    for (let pref of modifiedPrefs) {
+      Services.prefs.clearUserPref(pref);
+    }
+    toolbox = doc = panelWin = modifiedPrefs = null;
+    finish();
+  });
+}
+
+function errorHandler(error) {
+  ok(false, "Unexpected error: " + error);
+  cleanup();
+}
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -56,16 +56,17 @@ OptionsPanel.prototype = {
     if (!this.target.isRemote) {
       targetPromise = this.target.makeRemote();
     } else {
       targetPromise = promise.resolve(this.target);
     }
 
     return targetPromise.then(() => {
       this.setupToolsList();
+      this.setupToolbarButtonsList();
       this.populatePreferences();
 
       this._disableJSClicked = this._disableJSClicked.bind(this);
       this._disableCacheClicked = this._disableCacheClicked.bind(this);
 
       let disableJSNode = this.panelDoc.getElementById("devtools-disable-javascript");
       disableJSNode.addEventListener("click", this._disableJSClicked, false);
 
@@ -76,34 +77,62 @@ OptionsPanel.prototype = {
       this.emit("ready");
       return this;
     }).then(null, function onError(aReason) {
       Cu.reportError("OptionsPanel open failed. " +
                      aReason.error + ": " + aReason.message);
     });
   },
 
+  setupToolbarButtonsList: function() {
+    let enabledToolbarButtonsBox = this.panelDoc.getElementById("enabled-toolbox-buttons-box");
+    enabledToolbarButtonsBox.textContent = "";
+
+    let toggleableButtons = this.toolbox.toolboxButtons;
+    let setToolboxButtonsVisibility =
+      this.toolbox.setToolboxButtonsVisibility.bind(this.toolbox);
+
+    let onCheckboxClick = (checkbox) => {
+      let toolDefinition = toggleableButtons.filter(tool => tool.id === checkbox.id)[0];
+      Services.prefs.setBoolPref(toolDefinition.visibilityswitch, checkbox.checked);
+      setToolboxButtonsVisibility();
+    };
+
+    let createCommandCheckbox = tool => {
+      let checkbox = this.panelDoc.createElement("checkbox");
+      checkbox.setAttribute("id", tool.id);
+      checkbox.setAttribute("label", tool.label);
+      checkbox.setAttribute("checked", this.getBoolPref(tool.visibilityswitch));
+      checkbox.addEventListener("command", onCheckboxClick.bind(this, checkbox));
+      return checkbox;
+    };
+
+    for (let tool of toggleableButtons) {
+      enabledToolbarButtonsBox.appendChild(createCommandCheckbox(tool));
+    }
+  },
+
+  getBoolPref: function(key) {
+    try {
+      return Services.prefs.getBoolPref(key);
+    }
+    catch (ex) {
+      return true;
+    }
+  },
+
   setupToolsList: function() {
     let defaultToolsBox = this.panelDoc.getElementById("default-tools-box");
     let additionalToolsBox = this.panelDoc.getElementById("additional-tools-box");
     let toolsNotSupportedLabel = this.panelDoc.getElementById("tools-not-supported-label");
     let atleastOneToolNotSupported = false;
 
     defaultToolsBox.textContent = "";
     additionalToolsBox.textContent = "";
 
-    let pref = function(key) {
-      try {
-        return Services.prefs.getBoolPref(key);
-      }
-      catch (ex) {
-        return true;
-      }
-    };
-
     let onCheckboxClick = function(id) {
       let toolDefinition = gDevTools._tools.get(id);
       // Set the kill switch pref boolean to true
       Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked);
       if (this.checked) {
         gDevTools.emit("tool-registered", id);
       }
       else {
@@ -119,17 +148,17 @@ OptionsPanel.prototype = {
         checkbox.setAttribute("label", tool.label);
       }
       else {
         atleastOneToolNotSupported = true;
         checkbox.setAttribute("label",
                               l10n("options.toolNotSupportedMarker", tool.label));
         checkbox.setAttribute("unsupported", "");
       }
-      checkbox.setAttribute("checked", pref(tool.visibilityswitch));
+      checkbox.setAttribute("checked", this.getBoolPref(tool.visibilityswitch));
       checkbox.addEventListener("command", onCheckboxClick.bind(checkbox, tool.id));
       return checkbox;
     };
 
     // Populating the default tools lists
     let toggleableTools = gDevTools.getDefaultTools().filter(tool => {
       return tool.visibilityswitch
     });
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -15,19 +15,22 @@
           src="chrome://browser/content/devtools/theme-switching.js"/>
   <hbox id="options-panel-container" flex="1">
     <hbox id="options-panel" class="theme-body" flex="1">
       <vbox id="tools-box" class="options-vertical-pane" flex="1">
         <label value="&options.selectDefaultTools.label;"/>
         <vbox id="default-tools-box" class="options-groupbox" tabindex="0"/>
         <label value="&options.selectAdditionalTools.label;"/>
         <vbox id="additional-tools-box" class="options-groupbox"/>
+        <label value="&options.selectEnabledToolboxButtons.label;"/>
+        <vbox id="enabled-toolbox-buttons-box" class="options-groupbox"/>
         <label id="tools-not-supported-label"
                class="options-citation-label theme-comment"
                value="&options.toolNotSupported.label;"/>
+
       </vbox>
       <vbox class="options-vertical-pane" flex="1">
         <label value="&options.selectDevToolsTheme.label;"/>
         <radiogroup id="devtools-theme-box"
                     class="options-groupbox"
                     data-pref="devtools.theme"
                     orient="horizontal">
           <radio value="light" label="&options.lightTheme.label;"/>
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -538,16 +538,17 @@ Toolbox.prototype = {
 
     let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
     let environment = CommandUtils.createEnvironment(this, '_target');
     this._requisition = new Requisition({ environment: environment });
     let buttons = CommandUtils.createButtons(spec, this._target,
                                              this.doc, this._requisition);
     let container = this.doc.getElementById("toolbox-buttons");
     buttons.forEach(container.appendChild.bind(container));
+    this.setToolboxButtonsVisibility();
   },
 
   /**
    * Adding the element picker button is done here unlike the other buttons
    * since we want it to work for remote targets too
    */
   _buildPickerButton: function() {
     this._pickerButton = this.doc.createElement("toolbarbutton");
@@ -558,16 +559,67 @@ Toolbox.prototype = {
     let container = this.doc.querySelector("#toolbox-buttons");
     container.appendChild(this._pickerButton);
 
     this._togglePicker = this.highlighterUtils.togglePicker.bind(this.highlighterUtils);
     this._pickerButton.addEventListener("command", this._togglePicker, false);
   },
 
   /**
+   * Return all toolbox buttons (command buttons, plus any others that were
+   * added manually).
+   */
+  get toolboxButtons() {
+    // White-list buttons that can be toggled to prevent adding prefs for
+    // addons that have manually inserted toolbarbuttons into DOM.
+    return [
+      "command-button-pick",
+      "command-button-splitconsole",
+      "command-button-responsive",
+      "command-button-paintflashing",
+      "command-button-tilt",
+      "command-button-scratchpad"
+    ].map(id => {
+      let button = this.doc.getElementById(id);
+      // Some buttons may not exist inside of Browser Toolbox
+      if (!button) {
+        return false;
+      }
+      return {
+        id: id,
+        button: button,
+        label: button.getAttribute("tooltiptext"),
+        visibilityswitch: "devtools." + id + ".enabled"
+      }
+    }).filter(button=>button);
+  },
+
+  /**
+   * Ensure the visibility of each toolbox button matches the
+   * preference value.  Simply hide buttons that are preffed off.
+   */
+  setToolboxButtonsVisibility: function() {
+    this.toolboxButtons.forEach(buttonSpec => {
+      let {visibilityswitch, id, button}=buttonSpec;
+      let on = true;
+      try {
+        on = Services.prefs.getBoolPref(visibilityswitch);
+      } catch (ex) { }
+
+      if (button) {
+        if (on) {
+          button.removeAttribute("hidden");
+        } else {
+          button.setAttribute("hidden", "true");
+        }
+      }
+    });
+  },
+
+  /**
    * Build a tab for one tool definition and add to the toolbox
    *
    * @param {string} toolDefinition
    *        Tool definition of the tool to build a tab for.
    */
   _buildTabForTool: function(toolDefinition) {
     if (!toolDefinition.isTargetSupported(this._target)) {
       return;
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -87,16 +87,21 @@
 <!ENTITY options.selectDefaultTools.label     "Default Firefox Developer Tools">
 
 <!-- LOCALIZATION NOTE (options.selectAdditionalTools.label): This is the label for
   -  the heading of group of checkboxes corresponding to the developer tools
   -  added by add-ons. This heading is hidden when there is no developer tool
   -  installed by add-ons. -->
 <!ENTITY options.selectAdditionalTools.label  "Developer Tools installed by add-ons">
 
+<!-- LOCALIZATION NOTE (options.selectEnabledToolboxButtons.label): This is the label for
+  -  the heading of group of checkboxes corresponding to the default developer
+  -  tool buttons. -->
+<!ENTITY options.selectEnabledToolboxButtons.label     "Available Toolbox Buttons">
+
 <!-- LOCALIZATION NOTE (options.toolNotSupported.label): This is the label for
   -  the explanation of the * marker on a tool which is currently not supported
   -  for the target of the toolbox. -->
 <!ENTITY options.toolNotSupported.label  "* Not supported for current toolbox target">
 
 <!-- LOCALIZATION NOTE (options.selectDevToolsTheme.label): This is the label for
   -  the heading of the radiobox corresponding to the theme of the developer
   -  tools. -->
--- a/browser/metro/base/tests/mochiperf/browser_apzc.js
+++ b/browser/metro/base/tests/mochiperf/browser_apzc.js
@@ -22,16 +22,21 @@ function setup() {
   BookmarksTestHelper.setup();
   HistoryTestHelper.setup();
 }
 
 function tearDown() {
   PanelUI.hide();
   BookmarksTestHelper.restore();
   HistoryTestHelper.restore();
+  Browser.selectedTab
+         .browser
+         .contentWindow
+         .QueryInterface(Ci.nsIInterfaceRequestor)
+         .getInterface(Ci.nsIDOMWindowUtils).clearNativeTouchSequence();
 }
 
 /*
  * short up/down touch scroll. This test isn't affected by
  * skate or stationary apzc prefs provided the display port
  * is twice the height of the screen. Measures apzc/composition
  * perf since the display port (should) only render once.
  */
@@ -80,17 +85,17 @@ gTests.push({
     yield hideContextUI();
     yield hideNavBar();
 
     let stopwatch = new StopWatch();
     let win = Browser.selectedTab.browser.contentWindow;
     let domUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     var touchdrag = new TouchDragAndHold();
     touchdrag.useNativeEvents = true;
-    touchdrag.stepTimeout = 20;
+    touchdrag.stepTimeout = 5;
     touchdrag.numSteps = 20;
 
     stopwatch.start();
     let recordingHandle = domUtils.startFrameTimeRecording();
     for (let count = 0; count < 5; count++) {
       yield touchdrag.start(win, 100, (win.innerHeight - 50), 100, 50);
       touchdrag.end();
     }
@@ -117,17 +122,17 @@ gTests.push({
     yield hideContextUI();
     yield hideNavBar();
 
     let stopwatch = new StopWatch();
     let win = Browser.selectedTab.browser.contentWindow;
     let domUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     var touchdrag = new TouchDragAndHold();
     touchdrag.useNativeEvents = true;
-    touchdrag.stepTimeout = 20;
+    touchdrag.stepTimeout = 5;
     touchdrag.numSteps = 10;
 
     let iterations = 3;
     let sets = [];
     for (let count = 0; count < iterations; count++) {
       let obsPromise = waitForObserver("apzc-transform-end", 30000);
       stopwatch.start();
       let recordingHandle = domUtils.startFrameTimeRecording();
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -196,23 +196,20 @@ menulist {
 .menu-popup richlistitem {
   width: 100%;
   min-height: @touch_button_small@;
   min-width: @touch_action_minwidth@; /* keep the button from being too narrow */
   border: 0 none;
   -moz-box-align: center;
   font-weight: 600;
 }
-
 .menu-popup > richlistbox > richlistitem {
-  padding-right: 50px;
+  -moz-padding-end: 50px;
 }
-
 /* Additional styles applied to popups for form <select> elements. */
-
 #select-container {
   padding: 0;
   position: absolute;
   background-color: transparent;
 }
 
 #select-commands {
   -moz-appearance: none;
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -122,27 +122,27 @@ toolbarbutton.bookmark-item[open="true"]
 toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
 #personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
 }
 
 /* ----- BOOKMARK STAR ANIMATION ----- */
 
 @keyframes animation-bookmarkAdded {
-  from { transform: rotate(0deg) translateX(-20px) rotate(0deg) scale(1); opacity: 0; }
-  60%  { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(2.2); opacity: 1; }
+  from { transform: rotate(0deg) translateX(-16px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(2.2); opacity: 1; }
   80%  { opacity: 1; }
-  to   { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(1); opacity: 0; }
+  to   { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(1); opacity: 0; }
 }
 
 @keyframes animation-bookmarkAddedToBookmarksBar {
-  from { transform: rotate(0deg) translateX(-12px) rotate(0deg) scale(1); opacity: 0; }
-  60%  { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(2.2); opacity: 1; }
+  from { transform: rotate(0deg) translateX(-10px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-10px) rotate(-180deg) scale(2.2); opacity: 1; }
   80%  { opacity: 1; }
-  to   { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(1); opacity: 0; }
+  to   { transform: rotate(180deg) translateX(-10px) rotate(-180deg) scale(1); opacity: 0; }
 }
 
 @keyframes animation-bookmarkPulse {
   from { transform: scale(1); }
   50%  { transform: scale(1.3); }
   to   { transform: scale(1); }
 }
 
@@ -169,16 +169,20 @@ toolbarpaletteitem[place="palette"] > #p
   animation: animation-bookmarkAdded 800ms;
   animation-timing-function: ease, ease, ease;
 }
 
 #bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
   animation: animation-bookmarkAddedToBookmarksBar 800ms;
 }
 
+#bookmarks-menu-button[notification="finish"] {
+  pointer-events: none;
+}
+
 #bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   animation: animation-bookmarkPulse 300ms;
   animation-delay: 600ms;
   animation-timing-function: ease-out;
 }
 
 /* Bookmark menus */
 menu.bookmark-item,
@@ -568,17 +572,17 @@ menuitem:not([type]):not(.menuitem-toolt
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon {
   -moz-margin-end: 0;
   padding: 2px 6px;
   border: 1px solid transparent;
   border-radius: 2px;
   transition-property: background-color, border-color;
-  transition-duration: 250ms;
+  transition-duration: 150ms;
 }
 
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
   padding: 3px 7px;
 }
 
 /* Help SDK icons fit: */
@@ -591,18 +595,18 @@ toolbarbutton[sdk-button="true"][cui-are
   width: 32px;
 }
 
 #nav-bar #PanelUI-menu-button {
   -moz-padding-start: 7px;
   -moz-padding-end: 5px;
 }
 
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):hover > .toolbarbutton-icon,
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([buttonover]):not([open]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-icon {
   background-color: hsla(0,0%,100%,.3);
   background-image: linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.2));
   border: 1px solid rgb(154,154,154);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
               0 0 0 1px hsla(0,0%,100%,.2) inset,
               0 1px 0 hsla(0,0%,0%,.03);
 }
@@ -643,16 +647,20 @@ toolbarbutton[sdk-button="true"][cui-are
   background-image: linear-gradient(hsla(210,54%,20%,.2) 0, hsla(210,54%,20%,.2) 18px);
   background-clip: padding-box;
   background-position: center;
   background-repeat: no-repeat;
   background-size: 1px 18px;
   box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
 }
 
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+  -moz-margin-start: -4px;
+}
+
 #forward-button[disabled] {
   transform: scale(0);
   opacity: 0;
   pointer-events: none;
 }
 
 @conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
   transition: @forwardTransitionLength@ ease-out;
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -68,16 +68,17 @@ browser.jar:
   skin/classic/browser/webRTC-shareDevice-16.png
   skin/classic/browser/webRTC-shareDevice-64.png
   skin/classic/browser/webRTC-sharingDevice-16.png
   skin/classic/browser/webRTC-shareMicrophone-16.png
   skin/classic/browser/webRTC-shareMicrophone-64.png
   skin/classic/browser/webRTC-sharingMicrophone-16.png
   skin/classic/browser/customizableui/background-noise-toolbar.png  (customizableui/background-noise-toolbar.png)
   skin/classic/browser/customizableui/customize-illustration.png  (../shared/customizableui/customize-illustration.png)
+  skin/classic/browser/customizableui/customize-illustration-rtl.png  (../shared/customizableui/customize-illustration-rtl.png)
   skin/classic/browser/customizableui/customizeMode-gridTexture.png  (customizableui/customizeMode-gridTexture.png)
   skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
   skin/classic/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
   skin/classic/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
   skin/classic/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
   skin/classic/browser/customizableui/menuPanel-customizeFinish.png  (../shared/customizableui/menuPanel-customizeFinish.png)
   skin/classic/browser/customizableui/panelarrow-customizeTip.png  (../shared/customizableui/panelarrow-customizeTip.png)
 * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css)
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -409,16 +409,20 @@ toolbarpaletteitem[place="palette"] > #p
 }
 
 @media (min-resolution: 2dppx) {
   #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
     background-image: url("chrome://browser/skin/places/bookmarks-notification-finish@2x.png");
   }
 }
 
+#bookmarks-menu-button[notification="finish"] {
+  pointer-events: none;
+}
+
 #bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   animation: animation-bookmarkPulse 300ms;
   animation-delay: 600ms;
   animation-timing-function: ease-out;
 }
 
 /* ----- BOOKMARK MENUS ----- */
 
@@ -4256,16 +4260,20 @@ window > chatbox {
     background-image: url(chrome://browser/skin/customizableui/info-icon-customizeTip@2x.png);
     background-size: 25px 25px;
   }
 
   .customization-tipPanel-contentImage {
     list-style-image: url(chrome://browser/skin/customizableui/customize-illustration@2x.png);
   }
 
+  .customization-tipPanel-contentImage:-moz-locale-dir(rtl) {
+    list-style-image: url(chrome://browser/skin/customizableui/customize-illustration-rtl@2x.png);
+  }
+
   #customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="left"],
   #customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="right"] {
     list-style-image: url("chrome://browser/skin/customizableui/panelarrow-customizeTip@2x.png");
   }
 }
 
 /* End customization mode */
 
--- a/browser/themes/osx/downloads/indicator.css
+++ b/browser/themes/osx/downloads/indicator.css
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*** Status and progress indicator ***/
 
 #downloads-indicator-anchor {
-  min-width: 20px;
-  min-height: 20px;
+  min-width: 18px;
+  min-height: 18px;
 }
 
 #downloads-animation-container {
   min-height: 1px;
   min-width: 1px;
   height: 1px;
   margin-bottom: -1px;
   /* Makes the outermost animation container element positioned, so that its
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -119,16 +119,18 @@ browser.jar:
   skin/classic/browser/webRTC-shareMicrophone-64@2x.png
   skin/classic/browser/webRTC-sharingMicrophone-16.png
   skin/classic/browser/webRTC-sharingMicrophone-16@2x.png
   skin/classic/browser/customizableui/background-noise-toolbar.png  (customizableui/background-noise-toolbar.png)
   skin/classic/browser/customizableui/customize-titleBar-toggle.png  (customizableui/customize-titleBar-toggle.png)
   skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png  (customizableui/customize-titleBar-toggle@2x.png)
   skin/classic/browser/customizableui/customize-illustration.png  (../shared/customizableui/customize-illustration.png)
   skin/classic/browser/customizableui/customize-illustration@2x.png  (../shared/customizableui/customize-illustration@2x.png)
+  skin/classic/browser/customizableui/customize-illustration-rtl.png  (../shared/customizableui/customize-illustration-rtl.png)
+  skin/classic/browser/customizableui/customize-illustration-rtl@2x.png  (../shared/customizableui/customize-illustration-rtl@2x.png)
   skin/classic/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
   skin/classic/browser/customizableui/customizeMode-gridTexture.png  (customizableui/customizeMode-gridTexture.png)
   skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
   skin/classic/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
   skin/classic/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
   skin/classic/browser/customizableui/info-icon-customizeTip@2x.png  (../shared/customizableui/info-icon-customizeTip@2x.png)
   skin/classic/browser/customizableui/menuPanel-customizeFinish.png  (../shared/customizableui/menuPanel-customizeFinish.png)
   skin/classic/browser/customizableui/menuPanel-customizeFinish@2x.png  (../shared/customizableui/menuPanel-customizeFinish@2x.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12005095cbce00d80371de31ca215e3a32969822
GIT binary patch
literal 7592
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
Y`htgD2xL4eFwmEln!aj{vTgYP0LZF)8vp<R
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c1af12990c4d7ad7784447d36eeba7e8bd7a04a3
GIT binary patch
literal 16521
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
Ml2@0jkbwmKf5PXFWB>pF
index dd4f3de118293e1174fa2692c3a56e6b065e4d2e..9131951b03a25f05300fe3abc3e498447741f0d0
GIT binary patch
literal 7609
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
Y0y2I0dt_r~10L?uRMSzdhS<FMAMGBHegFUf
index 3606eb26831c492f024e7445f9f8f48ccc5fe7c8..50c0d19490bf12aa027ee7262fe04eaab57e284f
GIT binary patch
literal 16465
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
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -69,17 +69,17 @@
   outline-color: #bbb;
 }
 
 #PanelUI-contents > .panel-customization-placeholder {
   cursor: auto;
   outline-offset: -5px;
 }
 
-#main-window[customize-entered] .customization-target:not(#PanelUI-contents) {
+#main-window[customizing] .customization-target:not(#PanelUI-contents) {
   min-width: 100px;
   padding-left: 10px;
   padding-right: 10px;
 }
 
 #customization-container {
   background-color: rgb(247,247,247);
 }
--- a/browser/themes/shared/customizableui/customizeTip.inc.css
+++ b/browser/themes/shared/customizableui/customizeTip.inc.css
@@ -41,16 +41,20 @@
   list-style-image: url(chrome://browser/skin/customizableui/customize-illustration.png);
   min-width: 300px;
   max-width: 300px;
   min-height: 190px;
   max-height: 190px;
   display: -moz-box;
 }
 
+.customization-tipPanel-contentImage:-moz-locale-dir(rtl) {
+  list-style-image: url(chrome://browser/skin/customizableui/customize-illustration-rtl.png);
+}
+
 .customization-tipPanel-link {
   -moz-appearance: none;
   background: transparent;
   border: none;
   box-shadow: none;
   color: rgb(25,82,171);
   margin: 0;
   cursor: pointer;
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -514,16 +514,21 @@ toolbarpaletteitem[place="palette"] > to
   background-color: #ad3434;
   outline-color: #992e2e;
 }
 
 #customization-panelHolder #PanelUI-customize {
   color: white;
   background-color: rgb(116,191,67);
   text-shadow: none;
+  margin-top: -1px;
+}
+
+#customization-panelHolder #PanelUI-customize + toolbarseparator {
+  display: none;
 }
 
 #customization-panelHolder #PanelUI-customize:hover,
 #customization-panelHolder #PanelUI-customize:hover:active {
   background-color: rgb(105,173,61);
 }
 
 #customization-palette .toolbarbutton-multiline-text,
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -222,16 +222,30 @@
 .tab-background-start[selected=true]:-moz-lwtheme::before,
 .tab-background-end[selected=true]:-moz-lwtheme::before,
 .tab-background-middle[selected=true]:-moz-lwtheme {
   background-color: transparent;
 }
 
 /* End selected tab */
 
+/* Background tabs */
+
+/* Decrease the height of the hoverable region of background tabs whenever the tabs are at the top
+   of the window (e.g. no menubar, tabs in titlebar, etc.) to make it easier to drag the window by
+   the titlebar. We don't need this in fullscreen since window dragging is not an issue there. */
+%ifdef XP_MACOSX
+#main-window[tabsintitlebar][sizemode="maximized"] .tab-background-middle:not([selected=true]),
+%endif
+#main-window[tabsintitlebar]:not([sizemode="maximized"]):not([inFullscreen]) #toolbar-menubar:-moz-any([autohide="true"][inactive], :not([autohide])) + #TabsToolbar .tab-background-middle:not([selected=true]) {
+  clip-path: url(chrome://browser/content/browser.xul#tab-hover-clip-path);
+}
+
+/* End background tabs */
+
 /* new tab button border and gradient on hover */
 .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
 .tabs-newtab-button:hover {
   background-image: url(chrome://browser/skin/tabbrowser/tab-background-start.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-end.png);
   background-position: left bottom, @tabCurveWidth@ bottom, right bottom;
   background-repeat: no-repeat;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -359,16 +359,20 @@ toolbarpaletteitem[place="palette"] > #p
 }
 
 #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
   background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
   animation: animation-bookmarkAdded 800ms;
   animation-timing-function: ease, ease, ease;
 }
 
+#bookmarks-menu-button[notification="finish"] {
+  pointer-events: none;
+}
+
 #bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   animation: animation-bookmarkPulse 300ms;
   animation-delay: 600ms;
   animation-timing-function: ease-out;
 }
 
 /* ::::: bookmark menus ::::: */
 
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -87,16 +87,17 @@ browser.jar:
         skin/classic/browser/webRTC-shareDevice-64.png
         skin/classic/browser/webRTC-sharingDevice-16.png
         skin/classic/browser/webRTC-shareMicrophone-16.png
         skin/classic/browser/webRTC-shareMicrophone-64.png
         skin/classic/browser/webRTC-sharingMicrophone-16.png
         skin/classic/browser/customizableui/background-noise-toolbar.png  (customizableui/background-noise-toolbar.png)
         skin/classic/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
         skin/classic/browser/customizableui/customize-illustration.png  (../shared/customizableui/customize-illustration.png)
+        skin/classic/browser/customizableui/customize-illustration-rtl.png  (../shared/customizableui/customize-illustration-rtl.png)
         skin/classic/browser/customizableui/customize-titleBar-toggle.png  (customizableui/customize-titleBar-toggle.png)
         skin/classic/browser/customizableui/customizeMode-gridTexture.png  (customizableui/customizeMode-gridTexture.png)
         skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
         skin/classic/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
         skin/classic/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
         skin/classic/browser/customizableui/menuPanel-customizeFinish.png  (../shared/customizableui/menuPanel-customizeFinish.png)
         skin/classic/browser/customizableui/panelarrow-customizeTip.png  (../shared/customizableui/panelarrow-customizeTip.png)
 *       skin/classic/browser/customizableui/panelUIOverlay.css       (customizableui/panelUIOverlay.css)
@@ -412,16 +413,17 @@ browser.jar:
         skin/classic/aero/browser/webRTC-shareDevice-16.png
         skin/classic/aero/browser/webRTC-shareDevice-64.png
         skin/classic/aero/browser/webRTC-sharingDevice-16.png
         skin/classic/aero/browser/webRTC-shareMicrophone-16.png
         skin/classic/aero/browser/webRTC-shareMicrophone-64.png
         skin/classic/aero/browser/webRTC-sharingMicrophone-16.png
         skin/classic/aero/browser/customizableui/background-noise-toolbar.png  (customizableui/background-noise-toolbar.png)
         skin/classic/aero/browser/customizableui/customize-illustration.png  (../shared/customizableui/customize-illustration.png)
+        skin/classic/aero/browser/customizableui/customize-illustration-rtl.png  (../shared/customizableui/customize-illustration-rtl.png)
         skin/classic/aero/browser/customizableui/customize-titleBar-toggle.png  (customizableui/customize-titleBar-toggle.png)
         skin/classic/aero/browser/customizableui/customizeFavicon.ico  (../shared/customizableui/customizeFavicon.ico)
         skin/classic/aero/browser/customizableui/customizeMode-gridTexture.png  (customizableui/customizeMode-gridTexture.png)
         skin/classic/aero/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
         skin/classic/aero/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
         skin/classic/aero/browser/customizableui/info-icon-customizeTip.png  (../shared/customizableui/info-icon-customizeTip.png)
         skin/classic/aero/browser/customizableui/menuPanel-customizeFinish.png  (../shared/customizableui/menuPanel-customizeFinish.png)
         skin/classic/aero/browser/customizableui/panelarrow-customizeTip.png  (../shared/customizableui/panelarrow-customizeTip.png)
--- a/mobile/android/base/FilePicker.java
+++ b/mobile/android/base/FilePicker.java
@@ -28,60 +28,63 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
 public class FilePicker implements GeckoEventListener {
     private static final String LOGTAG = "GeckoFilePicker";
     private static FilePicker sFilePicker;
-    private final Context mContext;
+    private final Context context;
 
     public interface ResultHandler {
         public void gotFile(String filename);
     }
 
     public static void init(Context context) {
         if (sFilePicker == null) {
             sFilePicker = new FilePicker(context.getApplicationContext());
         }
     }
 
     protected FilePicker(Context context) {
-        mContext = context;
+        this.context = context;
         GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this);
     }
 
     @Override
     public void handleMessage(String event, final JSONObject message) {
         if (event.equals("FilePicker:Show")) {
             String mimeType = "*/*";
-            String mode = message.optString("mode");
+            final String mode = message.optString("mode");
+            final int tabId = message.optInt("tabId", -1);
 
             if ("mimeType".equals(mode))
                 mimeType = message.optString("mimeType");
             else if ("extension".equals(mode))
                 mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions"));
 
             showFilePickerAsync(mimeType, new ResultHandler() {
                 public void gotFile(String filename) {
                     try {
                         message.put("file", filename);
                     } catch (JSONException ex) {
                         Log.i(LOGTAG, "Can't add filename to message " + filename);
                     }
+
+
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
                         "FilePicker:Result", message.toString()));
                 }
-            });
+            }, tabId);
         }
     }
 
     private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
-        PackageManager pm = mContext.getPackageManager();
+        PackageManager pm = context.getPackageManager();
         List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0);
         for (ResolveInfo ri : lri) {
             ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name);
             if (filters != null && !filters.containsKey(cn.toString())) {
                 Intent rintent = new Intent(intent);
                 rintent.setComponent(cn);
                 intents.put(cn.toString(), rintent);
             }
@@ -150,23 +153,23 @@ public class FilePicker implements Gecko
 
         ArrayList<Intent> vals = new ArrayList<Intent>(intents.values());
         vals.add(0, baseIntent);
         return vals;
     }
 
     private String getFilePickerTitle(String mimeType) {
         if (mimeType.equals("audio/*")) {
-            return mContext.getString(R.string.filepicker_audio_title);
+            return context.getString(R.string.filepicker_audio_title);
         } else if (mimeType.equals("image/*")) {
-            return mContext.getString(R.string.filepicker_image_title);
+            return context.getString(R.string.filepicker_image_title);
         } else if (mimeType.equals("video/*")) {
-            return mContext.getString(R.string.filepicker_video_title);
+            return context.getString(R.string.filepicker_video_title);
         } else {
-            return mContext.getString(R.string.filepicker_title);
+            return context.getString(R.string.filepicker_title);
         }
     }
 
     private interface IntentHandler {
         public void gotIntent(Intent intent);
     }
 
     /* Gets an intent that can open a particular mimetype. Will show a prompt with a list
@@ -196,18 +199,18 @@ public class FilePicker implements Gecko
         chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{}));
         handler.gotIntent(chooser);
     }
 
     /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
      * sends the file returned to the passed in handler. If a null handler is passed in, will still
      * pick and launch the file picker, but will throw away the result.
      */
-    protected void showFilePickerAsync(String mimeType, final ResultHandler handler) {
-        final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler);
+    protected void showFilePickerAsync(String mimeType, final ResultHandler handler, final int tabId) {
+        final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId);
         getFilePickerIntentAsync(mimeType, fileHandler, new IntentHandler() {
             @Override
             public void gotIntent(Intent intent) {
                 if (handler == null) {
                     return;
                 }
 
                 if (intent == null) {
--- a/mobile/android/base/FilePickerResultHandler.java
+++ b/mobile/android/base/FilePickerResultHandler.java
@@ -1,67 +1,67 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.util.ActivityResultHandler;
+import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
 import android.provider.MediaStore;
 import android.provider.OpenableColumns;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.CursorLoader;
 import android.support.v4.content.Loader;
+import android.text.TextUtils;
 import android.text.format.Time;
 import android.util.Log;
+import android.os.Process;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
 import java.util.Queue;
 
 class FilePickerResultHandler implements ActivityResultHandler {
     private static final String LOGTAG = "GeckoFilePickerResultHandler";
+    private static final String UPLOADS_DIR = "uploads";
 
-    protected final Queue<String> mFilePickerResult;
-    protected final FilePicker.ResultHandler mHandler;
+    protected final FilePicker.ResultHandler handler;
+    private final int tabId;
+    private final File cacheDir;
 
     // this code is really hacky and doesn't belong anywhere so I'm putting it here for now
     // until I can come up with a better solution.
     private String mImageName = "";
 
-    public FilePickerResultHandler(Queue<String> resultQueue) {
-        mFilePickerResult = resultQueue;
-        mHandler = null;
-    }
-
     /* Use this constructor to asynchronously listen for results */
-    public FilePickerResultHandler(FilePicker.ResultHandler handler) {
-        mFilePickerResult = null;
-        mHandler = handler;
+    public FilePickerResultHandler(final FilePicker.ResultHandler handler, final Context context, final int tabId) {
+        this.tabId = tabId;
+        cacheDir = new File(context.getCacheDir(), UPLOADS_DIR);
+        this.handler = handler;
     }
 
     private void sendResult(String res) {
-        if (mFilePickerResult != null)
-            mFilePickerResult.offer(res);
-
-        if (mHandler != null)
-            mHandler.gotFile(res);
+        if (handler != null) {
+            handler.gotFile(res);
+        }
     }
 
     @Override
     public void onActivityResult(int resultCode, Intent intent) {
         if (resultCode != Activity.RESULT_OK) {
             sendResult("");
             return;
         }
@@ -119,26 +119,26 @@ class FilePickerResultHandler implements
     public String generateImageName() {
         Time now = new Time();
         now.setToNow();
         mImageName = now.format("%Y-%m-%d %H.%M.%S") + ".jpg";
         return mImageName;
     }
 
     private class VideoLoaderCallbacks implements LoaderCallbacks<Cursor> {
-        final private Uri mUri;
+        final private Uri uri;
         public VideoLoaderCallbacks(Uri uri) {
-            mUri = uri;
+            this.uri = uri;
         }
 
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
             final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
             return new CursorLoader(fa,
-                                    mUri,
+                                    uri,
                                     new String[] { MediaStore.Video.Media.DATA },
                                     null,  // selection
                                     null,  // selectionArgs
                                     null); // sortOrder
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
@@ -147,75 +147,114 @@ class FilePickerResultHandler implements
                 sendResult(res);
             }
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) { }
     }
 
-    private class FileLoaderCallbacks implements LoaderCallbacks<Cursor> {
-        final private Uri mUri;
+    private class FileLoaderCallbacks implements LoaderCallbacks<Cursor>,
+                                                 Tabs.OnTabsChangedListener {
+        final private Uri uri;
+        private String tempFile;
+
         public FileLoaderCallbacks(Uri uri) {
-            mUri = uri;
+            this.uri = uri;
         }
 
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
             final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
             return new CursorLoader(fa,
-                                    mUri,
+                                    uri,
                                     new String[] { OpenableColumns.DISPLAY_NAME },
                                     null,  // selection
                                     null,  // selectionArgs
                                     null); // sortOrder
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
             if (cursor.moveToFirst()) {
                 String name = cursor.getString(0);
                 // tmp filenames must be at least 3 characters long. Add a prefix to make sure that happens
-                String fileName = "tmp_";
+                String fileName = "tmp_" + Process.myPid() + "-";
                 String fileExt = null;
                 int period;
 
                 final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
                 final ContentResolver cr = fa.getContentResolver();
 
                 // Generate an extension if we don't already have one
                 if (name == null || (period = name.lastIndexOf('.')) == -1) {
-                    String mimeType = cr.getType(mUri);
+                    String mimeType = cr.getType(uri);
                     fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
                 } else {
                     fileExt = name.substring(period);
                     fileName += name.substring(0, period);
                 }
 
                 // Now write the data to the temp file
                 try {
-                    File file = File.createTempFile(fileName, fileExt, GeckoLoader.getGREDir(GeckoAppShell.getContext()));
+                    cacheDir.mkdir();
+
+                    File file = File.createTempFile(fileName, fileExt, cacheDir);
                     FileOutputStream fos = new FileOutputStream(file);
-                    InputStream is = cr.openInputStream(mUri);
+                    InputStream is = cr.openInputStream(uri);
                     byte[] buf = new byte[4096];
                     int len = is.read(buf);
                     while (len != -1) {
                         fos.write(buf, 0, len);
                         len = is.read(buf);
                     }
                     fos.close();
 
-                    String path = file.getAbsolutePath();
-                    sendResult((path == null) ? "" : path);
+                    tempFile = file.getAbsolutePath();
+                    sendResult((tempFile == null) ? "" : tempFile);
+
+                    if (tabId > -1 && !TextUtils.isEmpty(tempFile)) {
+                        Tabs.registerOnTabsChangedListener(this);
+                    }
                 } catch(IOException ex) {
                     Log.i(LOGTAG, "Error writing file", ex);
                 }
             } else {
                 sendResult("");
             }
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) { }
+
+        /*Tabs.OnTabsChangedListener*/
+        // This cleans up our temp file. If it doesn't run, we just hope that Android
+        // will eventually does the cleanup for us.
+        @Override
+        public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
+            if (tab.getId() != tabId) {
+                return;
+            }
+
+            if (msg == Tabs.TabEvents.LOCATION_CHANGE ||
+                msg == Tabs.TabEvents.CLOSED) {
+                ThreadUtils.postToBackgroundThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        File f = new File(tempFile);
+                        f.delete();
+                    }
+                });
+
+                // We're already on the UIThread, but we have to post this back to the uithread to avoid
+                // modifying the listener array while its being iterated through.
+                ThreadUtils.postToUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        Tabs.unregisterOnTabsChangedListener(FileLoaderCallbacks.this);
+                    }
+                });
+            }
+        }
     }
 
 }
 
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -39,21 +39,23 @@ import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PluginLayer;
 import org.mozilla.gecko.health.HealthRecorder;
 import org.mozilla.gecko.health.SessionInformation;
 import org.mozilla.gecko.health.StubbedHealthRecorder;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
 import org.mozilla.gecko.menu.MenuPanel;
+import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityResultHandler;
+import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.webapp.EventListener;
 import org.mozilla.gecko.webapp.UninstallListener;
 import org.mozilla.gecko.widget.ButtonToast;
 
@@ -128,16 +130,17 @@ public abstract class GeckoApp
     GeckoEventListener,
     GeckoMenu.Callback,
     GeckoMenu.MenuPresenter,
     LocationListener,
     SensorEventListener,
     Tabs.OnTabsChangedListener
 {
     private static final String LOGTAG = "GeckoApp";
+    private static final int ONE_DAY_MS = 1000*60*60*24;
 
     private static enum StartupAction {
         NORMAL,     /* normal application start */
         URL,        /* launched with a passed URL */
         PREFETCH    /* launched with a passed URL that we prefetch */
     }
 
     public static final String ACTION_ALERT_CALLBACK       = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
@@ -151,16 +154,17 @@ public abstract class GeckoApp
     public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
 
     public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
     public static final String PREFS_CRASHED               = "crashed";
     public static final String PREFS_NAME                  = "GeckoApp";
     public static final String PREFS_OOM_EXCEPTION         = "OOMException";
     public static final String PREFS_VERSION_CODE          = "versionCode";
     public static final String PREFS_WAS_STOPPED           = "wasStopped";
+    public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
 
     public static final String SAVED_STATE_IN_BACKGROUND   = "inBackground";
     public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
 
     static private final String LOCATION_URL = "https://location.services.mozilla.com/v1/submit";
 
     // Delay before running one-time "cleanup" tasks that may be needed
     // after a version upgrade.
@@ -2048,16 +2052,25 @@ public abstract class GeckoApp
             @Override
             public void run() {
                 SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
                 SharedPreferences.Editor editor = prefs.edit();
                 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
                 if (rec != null) {
                     rec.recordSessionEnd("P", editor);
                 }
+
+                // If we haven't done it before, cleanup any old files in our old temp dir
+                if (prefs.getBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, true)) {
+                    File tempDir = GeckoLoader.getGREDir(GeckoApp.this);
+                    FileUtils.delTree(tempDir, new FileUtils.NameAndAgeFilter(null, ONE_DAY_MS), false);
+
+                    editor.putBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, false);
+                }
+
                 editor.commit();
 
                 // In theory, the first browser session will not run long enough that we need to
                 // prune during it and we'd rather run it when the browser is inactive so we wait
                 // until here to register the prune service.
                 GeckoPreferences.broadcastHealthReportPrune(context);
             }
         });
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -529,16 +529,17 @@ sync_java_files = [
     'background/healthreport/prune/PrunePolicy.java',
     'background/healthreport/prune/PrunePolicyDatabaseStorage.java',
     'background/healthreport/prune/PrunePolicyStorage.java',
     'background/healthreport/upload/AndroidSubmissionClient.java',
     'background/healthreport/upload/HealthReportUploadService.java',
     'background/healthreport/upload/ObsoleteDocumentTracker.java',
     'background/healthreport/upload/SubmissionClient.java',
     'background/healthreport/upload/SubmissionPolicy.java',
+    'background/nativecode/NativeCrypto.java',
     'background/preferences/PreferenceFragment.java',
     'background/preferences/PreferenceManagerCompat.java',
     'browserid/ASNUtils.java',
     'browserid/BrowserIDKeyPair.java',
     'browserid/DSACryptoImplementation.java',
     'browserid/JSONWebTokenUtils.java',
     'browserid/MockMyIDTokenFactory.java',
     'browserid/RSACryptoImplementation.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/nativecode/NativeCrypto.java
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.background.nativecode;
+
+import org.mozilla.gecko.mozglue.RobocopTarget;
+
+import java.security.GeneralSecurityException;
+
+@RobocopTarget
+public class NativeCrypto {
+  static {
+    System.loadLibrary("mozglue");
+  }
+
+  /**
+   * Wrapper to perform PBKDF2-HMAC-SHA-256 in native code.
+   */
+  public native static byte[] pbkdf2SHA256(byte[] password, byte[] salt, int c, int dkLen)
+      throws GeneralSecurityException;
+
+  /**
+   * Wrapper to perform SHA-1 in native code.
+   */
+  public native static byte[] sha1(byte[] str);
+}
--- a/mobile/android/base/home/HomePanelPicker.java
+++ b/mobile/android/base/home/HomePanelPicker.java
@@ -118,16 +118,21 @@ public class HomePanelPicker extends Fra
 
         // Filter out panels that are already displayed.
         for (PanelInfo panelInfo : panelInfos) {
             if (!mCurrentPanelsIds.contains(panelInfo.getId())) {
                 availablePanels.add(panelInfo);
             }
         }
 
+        if (availablePanels.isEmpty()) {
+            setContentView(R.layout.home_panel_picker_empty);
+            return;
+        }
+
         final PickerAdapter adapter = (PickerAdapter) mListView.getAdapter();
         adapter.updateFromPanelInfos(availablePanels);
     }
 
     private void installNewPanelAndQuit(PanelInfo panelInfo) {
         final PanelConfig newPanelConfig = panelInfo.toPanelConfig();
         HomeConfigInvalidator.getInstance().installPanel(newPanelConfig);
 
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -87,16 +87,17 @@
 <!ENTITY pref_developer_remotedebugging "Remote debugging">
 <!ENTITY pref_developer_remotedebugging_docs "Learn more">
 <!ENTITY pref_remember_signons "Remember passwords">
 
 <!ENTITY pref_category_home "Home">
 <!ENTITY pref_category_home_panels "Panels">
 <!ENTITY pref_home_add_panel "Add panel">
 <!ENTITY home_add_panel_title "Add new panel">
+<!ENTITY home_add_panel_empty "Sorry, we couldn\'t find any panels for you to add.">
 
 <!-- Localization note: These are shown in the left sidebar on tablets -->
 <!ENTITY pref_header_customize "Customize">
 <!ENTITY pref_header_display "Display">
 <!ENTITY pref_header_privacy_short "Privacy">
 <!ENTITY pref_header_help "Help">
 <!ENTITY pref_header_vendor "&vendorShortName;">
 <!ENTITY pref_header_devtools "Developer tools">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -43,16 +43,17 @@ wsjar.sources += [ thirdparty_source_dir
 ] ]
 wsjar.javac_flags += ['-Xlint:all,-serial']
 
 gujar = add_java_jar('gecko-util')
 gujar.sources += [
     'util/ActivityResultHandler.java',
     'util/ActivityResultHandlerMap.java',
     'util/Clipboard.java',
+    'util/FileUtils.java',
     'util/FloatUtils.java',
     'util/GamepadUtils.java',
     'util/GeckoBackgroundThread.java',
     'util/GeckoEventListener.java',
     'util/GeckoJarReader.java',
     'util/HardwareUtils.java',
     'util/INIParser.java',
     'util/INISection.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/home_panel_picker_empty.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:padding="15dp"
+          android:textAppearance="?android:attr/textAppearanceSmall"
+          android:text="@string/home_add_panel_empty" />
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -110,16 +110,17 @@
   <string name="pref_category_devtools">&pref_category_devtools;</string>
   <string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
   <string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>
 
   <string name="pref_category_home">&pref_category_home;</string>
   <string name="pref_category_home_panels">&pref_category_home_panels;</string>
   <string name="pref_home_add_panel">&pref_home_add_panel;</string>
   <string name="home_add_panel_title">&home_add_panel_title;</string>
+  <string name="home_add_panel_empty">&home_add_panel_empty;</string>
 
   <string name="pref_header_customize">&pref_header_customize;</string>
   <string name="pref_header_display">&pref_header_display;</string>
   <string name="pref_header_privacy_short">&pref_header_privacy_short;</string>
   <string name="pref_header_vendor">&pref_header_vendor;</string>
   <string name="pref_header_devtools">&pref_header_devtools;</string>
 
   <string name="pref_remember_signons">&pref_remember_signons;</string>
--- a/mobile/android/base/sync/crypto/PBKDF2.java
+++ b/mobile/android/base/sync/crypto/PBKDF2.java
@@ -1,76 +1,67 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.crypto;
 
-import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
+import java.util.Arrays;
 
 import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.ShortBufferException;
 import javax.crypto.spec.SecretKeySpec;
 
 public class PBKDF2 {
-  public static byte[] pbkdf2SHA1(byte[] password, byte[] salt, int c, int dkLen)
-      throws GeneralSecurityException {
-    // Won't work on API level 8, but this is trivial.
-    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
-    PBEKeySpec keySpec;
-    try {
-      keySpec = new PBEKeySpec(new String(password, "UTF-8").toCharArray(), salt, c, dkLen * 8);
-    } catch (UnsupportedEncodingException e) {
-      throw new GeneralSecurityException(e);
-    }
-    SecretKey key = factory.generateSecret(keySpec);
-    return key.getEncoded();
-  }
-
   public static byte[] pbkdf2SHA256(byte[] password, byte[] salt, int c, int dkLen)
       throws GeneralSecurityException {
     final String algorithm = "HmacSHA256";
     SecretKeySpec keyspec = new SecretKeySpec(password, algorithm);
     Mac prf = Mac.getInstance(algorithm);
     prf.init(keyspec);
 
     int hLen = prf.getMacLength();
+
+    byte U_r[] = new byte[hLen];
+    byte U_i[] = new byte[salt.length + 4];
+    byte scratch[] = new byte[hLen];
+
     int l = Math.max(dkLen, hLen);
     int r = dkLen - (l - 1) * hLen;
     byte T[] = new byte[l * hLen];
     int ti_offset = 0;
     for (int i = 1; i <= l; i++) {
-      F(T, ti_offset, prf, salt, c, i);
+      Arrays.fill(U_r, (byte) 0);
+      F(T, ti_offset, prf, salt, c, i, U_r, U_i, scratch);
       ti_offset += hLen;
     }
 
     if (r < hLen) {
       // Incomplete last block.
       byte DK[] = new byte[dkLen];
       System.arraycopy(T, 0, DK, 0, dkLen);
       return DK;
     }
 
     return T;
   }
 
-  private static void F(byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex) {
+  private static void F(byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex, byte U_r[], byte U_i[], byte[] scratch)
+      throws ShortBufferException, IllegalStateException {
     final int hLen = prf.getMacLength();
-    byte U_r[] = new byte[hLen];
 
     // U0 = S || INT (i);
-    byte U_i[] = new byte[S.length + 4];
     System.arraycopy(S, 0, U_i, 0, S.length);
     INT(U_i, S.length, blockIndex);
 
     for (int i = 0; i < c; i++) {
-      U_i = prf.doFinal(U_i);
+      prf.update(U_i);
+      prf.doFinal(scratch, 0);
+      U_i = scratch;
       xor(U_r, U_i);
     }
 
     System.arraycopy(U_r, 0, dest, offset, hLen);
   }
 
   private static void xor(byte[] dest, byte[] src) {
     for (int i = 0; i < dest.length; i++) {
--- a/mobile/android/base/tests/components/AboutHomeComponent.java
+++ b/mobile/android/base/tests/components/AboutHomeComponent.java
@@ -70,56 +70,56 @@ public class AboutHomeComponent extends 
     private View getHomeBannerView() {
         return mSolo.getView(R.id.home_banner);
     }
 
     public AboutHomeComponent assertCurrentPanel(final PanelType expectedPanel) {
         assertVisible();
 
         final int expectedPanelIndex = getPanelIndexForDevice(expectedPanel.ordinal());
-        assertEquals("The current HomePager panel is " + expectedPanel,
+        fAssertEquals("The current HomePager panel is " + expectedPanel,
                      expectedPanelIndex, getHomePagerView().getCurrentItem());
         return this;
     }
 
     public AboutHomeComponent assertNotVisible() {
-        assertTrue("The HomePager is not visible",
+        fAssertTrue("The HomePager is not visible",
                     getHomePagerContainer().getVisibility() != View.VISIBLE ||
                     getHomePagerView().getVisibility() != View.VISIBLE);
         return this;
     }
 
     public AboutHomeComponent assertVisible() {
-        assertTrue("The HomePager is visible",
+        fAssertTrue("The HomePager is visible",
                     getHomePagerContainer().getVisibility() == View.VISIBLE &&
                     getHomePagerView().getVisibility() == View.VISIBLE);
         return this;
     }
 
     public AboutHomeComponent assertBannerNotVisible() {
         View banner = getHomeBannerView();
-        assertTrue("The HomeBanner is not visible",
+        fAssertTrue("The HomeBanner is not visible",
                     getHomePagerContainer().getVisibility() != View.VISIBLE ||
                     banner.getVisibility() != View.VISIBLE ||
                     banner.getTranslationY() == banner.getHeight());
         return this;
     }
 
     public AboutHomeComponent assertBannerVisible() {
-        assertTrue("The HomeBanner is visible",
+        fAssertTrue("The HomeBanner is visible",
                     getHomePagerContainer().getVisibility() == View.VISIBLE &&
                     getHomeBannerView().getVisibility() == View.VISIBLE);
         return this;
     }
 
     public AboutHomeComponent assertBannerText(String text) {
         assertBannerVisible();
 
         final TextView textView = (TextView) getHomeBannerView().findViewById(R.id.text);
-        assertEquals("The correct HomeBanner text is shown",
+        fAssertEquals("The correct HomeBanner text is shown",
                      text, textView.getText().toString());
         return this;
     }
 
     public AboutHomeComponent clickOnBanner() {
         assertBannerVisible();
 
         mTestContext.dumpLog(LOGTAG, "Clicking on HomeBanner.");
@@ -143,17 +143,17 @@ public class AboutHomeComponent extends 
 
     public AboutHomeComponent swipeToPanelOnLeft() {
         mTestContext.dumpLog(LOGTAG, "Swiping to the panel on the left.");
         swipeToPanel(Solo.LEFT);
         return this;
     }
 
     private void swipeToPanel(final int panelDirection) {
-        assertTrue("Swiping in a valid direction",
+        fAssertTrue("Swiping in a valid direction",
                 panelDirection == Solo.LEFT || panelDirection == Solo.RIGHT);
         assertVisible();
 
         final int panelIndex = getHomePagerView().getCurrentItem();
 
         mSolo.scrollViewToSide(getHomePagerView(), panelDirection, SWIPE_PERCENTAGE);
 
         // The panel on the left is a lower index and vice versa.
--- a/mobile/android/base/tests/components/AppMenuComponent.java
+++ b/mobile/android/base/tests/components/AppMenuComponent.java
@@ -44,17 +44,17 @@ public class AppMenuComponent extends Ba
         }
     };
 
     public AppMenuComponent(final UITestContext testContext) {
         super(testContext);
     }
 
     private void assertMenuIsNotOpen() {
-        assertFalse("Menu is not open", isMenuOpen());
+        fAssertFalse("Menu is not open", isMenuOpen());
     }
 
     private View getOverflowMenuButtonView() {
         return mSolo.getView(R.id.menu);
     }
 
     /**
      * Try to find a MenuItemActionBar/MenuItemDefault with the given text set as contentDescription / text.
@@ -85,18 +85,18 @@ public class AppMenuComponent extends Ba
 
     public void pressMenuItem(MenuItem menuItem) {
         openAppMenu();
 
         final String text = menuItem.getString(mSolo);
         final View menuItemView = findAppMenuItemView(text);
 
         if (menuItemView != null) {
-            assertTrue("The menu item is enabled", menuItemView.isEnabled());
-            assertEquals("The menu item is visible", View.VISIBLE, menuItemView.getVisibility());
+            fAssertTrue("The menu item is enabled", menuItemView.isEnabled());
+            fAssertEquals("The menu item is visible", View.VISIBLE, menuItemView.getVisibility());
 
             mSolo.clickOnView(menuItemView);
         } else {
             // We could not find a view representing this menu item: Let's let Robotium try to
             // locate and click it in the legacy Android menu (devices with Android 2.x).
             //
             // Even though we already opened the menu to see if we can locate the menu item,
             // Robotium will also try to open the menu if it doesn't find an open dialog (Does
@@ -115,18 +115,18 @@ public class AppMenuComponent extends Ba
         }
 
         waitForMenuOpen();
     }
 
     private void pressOverflowMenuButton() {
         final View overflowMenuButton = getOverflowMenuButtonView();
 
-        assertTrue("The overflow menu button is enabled", overflowMenuButton.isEnabled());
-        assertEquals("The overflow menu button is visible", View.VISIBLE, overflowMenuButton.getVisibility());
+        fAssertTrue("The overflow menu button is enabled", overflowMenuButton.isEnabled());
+        fAssertEquals("The overflow menu button is visible", View.VISIBLE, overflowMenuButton.getVisibility());
 
         mSolo.clickOnView(overflowMenuButton, true);
     }
 
     private boolean isMenuOpen() {
         // The presence of the "New tab" menu item is our best guess about whether
         // the menu is open or not.
         return mSolo.searchText(MenuItem.NEW_TAB.getString(mSolo));
--- a/mobile/android/base/tests/components/GeckoViewComponent.java
+++ b/mobile/android/base/tests/components/GeckoViewComponent.java
@@ -58,29 +58,29 @@ public class GeckoViewComponent extends 
 
     public class TextInput {
         private TextInput() {
         }
 
         private InputMethodManager getInputMethodManager() {
             final InputMethodManager imm = (InputMethodManager)
                 mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
-            assertNotNull("Must have an InputMethodManager", imm);
+            fAssertNotNull("Must have an InputMethodManager", imm);
             return imm;
         }
 
         /**
          * Returns whether text input is being directed to the GeckoView.
          */
         private boolean isActive() {
             return getInputMethodManager().isActive(getView());
         }
 
         public TextInput assertActive() {
-            assertTrue("Current view should be the active input view", isActive());
+            fAssertTrue("Current view should be the active input view", isActive());
             return this;
         }
 
         public TextInput waitForActive() {
             WaitHelper.waitFor("current view to become the active input view", new Condition() {
                 @Override
                 public boolean isSatisfied() {
                     return isActive();
@@ -96,17 +96,17 @@ public class GeckoViewComponent extends 
          * currently focused inside the GeckoView.
          */
         private boolean hasInputConnection() {
             final InputMethodManager imm = getInputMethodManager();
             return imm.isActive(getView()) && imm.isAcceptingText();
         }
 
         public TextInput assertInputConnection() {
-            assertTrue("Current view should have an active InputConnection", hasInputConnection());
+            fAssertTrue("Current view should have an active InputConnection", hasInputConnection());
             return this;
         }
 
         public TextInput waitForInputConnection() {
             WaitHelper.waitFor("current view to have an active InputConnection", new Condition() {
                 @Override
                 public boolean isSatisfied() {
                     return hasInputConnection();
@@ -122,17 +122,17 @@ public class GeckoViewComponent extends 
          * be temporarily disabled to prevent the system IME from interfering with our
          * tests. We disable the service by override the GeckoView's context with one
          * that returns a null InputMethodManager service.
          *
          * @param test Test to run
          */
         public TextInput testInputConnection(final InputConnectionTest test) {
 
-            assertNotNull("Test must not be null", test);
+            fAssertNotNull("Test must not be null", test);
             assertInputConnection();
 
             // GeckoInputConnection can run on another thread than the main thread,
             // so we need to be testing it on that same thread it's running on
             final View geckoView = getView();
             final Handler inputConnectionHandler = geckoView.getHandler();
             final Context oldGeckoViewContext = FrameworkHelper.getViewContext(geckoView);
 
@@ -160,17 +160,17 @@ public class GeckoViewComponent extends 
                 mTest = test;
             }
 
             public synchronized void runOnHandler(final Handler inputConnectionHandler) {
                 // Below, we are blocking the instrumentation thread to wait on the
                 // InputConnection thread. Therefore, the InputConnection thread must not be
                 // the same as the instrumentation thread to avoid a deadlock. This should
                 // always be the case and we perform a sanity check to make sure.
-                assertNotSame("InputConnection should not be running on instrumentation thread",
+                fAssertNotSame("InputConnection should not be running on instrumentation thread",
                     Looper.myLooper(), inputConnectionHandler.getLooper());
 
                 mDone = false;
                 inputConnectionHandler.post(this);
                 do {
                     try {
                         wait();
                     } catch (InterruptedException e) {
@@ -178,17 +178,17 @@ public class GeckoViewComponent extends 
                     }
                 } while (!mDone);
             }
 
             @Override
             public void run() {
                 final EditorInfo info = new EditorInfo();
                 final InputConnection ic = getView().onCreateInputConnection(info);
-                assertNotNull("Must have an InputConnection", ic);
+                fAssertNotNull("Must have an InputConnection", ic);
                 // Restore the IC to a clean state
                 ic.clearMetaKeyStates(-1);
                 ic.finishComposingText();
                 mTest.test(ic, info);
                 synchronized (this) {
                     // Test finished; return from runOnHandler
                     mDone = true;
                     notify();
--- a/mobile/android/base/tests/components/ToolbarComponent.java
+++ b/mobile/android/base/tests/components/ToolbarComponent.java
@@ -23,33 +23,33 @@ import android.widget.TextView;
  * A class representing any interactions that take place on the Toolbar.
  */
 public class ToolbarComponent extends BaseComponent {
     public ToolbarComponent(final UITestContext testContext) {
         super(testContext);
     }
 
     public ToolbarComponent assertIsEditing() {
-        assertTrue("The toolbar is in the editing state", isEditing());
+        fAssertTrue("The toolbar is in the editing state", isEditing());
         return this;
     }
 
     public ToolbarComponent assertIsNotEditing() {
-        assertFalse("The toolbar is not in the editing state", isEditing());
+        fAssertFalse("The toolbar is not in the editing state", isEditing());
         return this;
     }
 
     public ToolbarComponent assertTitle(final String expected) {
-        assertEquals("The Toolbar title is " + expected, expected, getTitle());
+        fAssertEquals("The Toolbar title is " + expected, expected, getTitle());
         return this;
     }
 
     public ToolbarComponent assertUrl(final String expected) {
         assertIsEditing();
-        assertEquals("The Toolbar url is " + expected, expected, getUrlEditText().getText());
+        fAssertEquals("The Toolbar url is " + expected, expected, getUrlEditText().getText());
         return this;
     }
 
     /**
      * Returns the root View for the browser toolbar.
      */
     private View getToolbarView() {
         return mSolo.getView(R.id.browser_toolbar);
@@ -153,22 +153,22 @@ public class ToolbarComponent extends Ba
         mSolo.goBack();
 
         waitForNotEditing();
 
         return this;
     }
 
     public ToolbarComponent enterUrl(final String url) {
-        assertNotNull("url is not null", url);
+        fAssertNotNull("url is not null", url);
 
         assertIsEditing();
 
         final EditText urlEditText = getUrlEditText();
-        assertTrue("The UrlEditText is the input method target",
+        fAssertTrue("The UrlEditText is the input method target",
                 urlEditText.isInputMethodTarget());
 
         mSolo.clearEditText(urlEditText);
         mSolo.enterText(urlEditText, url);
 
         return this;
     }
 
@@ -178,19 +178,19 @@ public class ToolbarComponent extends Ba
     }
 
     public ToolbarComponent pressForwardButton() {
         final ImageButton forwardButton = getForwardButton();
         return pressButton(forwardButton, "forward");
     }
 
     private ToolbarComponent pressButton(final View view, final String buttonName) {
-        assertNotNull("The " + buttonName + " button View is not null", view);
-        assertTrue("The " + buttonName + " button is enabled", view.isEnabled());
-        assertEquals("The " + buttonName + " button is visible",
+        fAssertNotNull("The " + buttonName + " button View is not null", view);
+        fAssertTrue("The " + buttonName + " button is enabled", view.isEnabled());
+        fAssertEquals("The " + buttonName + " button is visible",
                 View.VISIBLE, view.getVisibility());
         assertIsNotEditing();
 
         WaitHelper.waitForPageLoad(new Runnable() {
             @Override
             public void run() {
                 mSolo.clickOnView(view);
             }
--- a/mobile/android/base/tests/helpers/AssertionHelper.java
+++ b/mobile/android/base/tests/helpers/AssertionHelper.java
@@ -19,94 +19,94 @@ public final class AssertionHelper {
     private static Assert sAsserter;
 
     private AssertionHelper() { /* To disallow instantation. */ }
 
     protected static void init(final UITestContext context) {
         sAsserter = context.getAsserter();
     }
 
-    public static void assertArrayEquals(final String message, final byte[] expecteds, final byte[] actuals) {
+    public static void fAssertArrayEquals(final String message, final byte[] expecteds, final byte[] actuals) {
         sAsserter.ok(Arrays.equals(expecteds, actuals), message, DIAG_STRING);
     }
 
-    public static void assertArrayEquals(final String message, final char[] expecteds, final char[] actuals) {
+    public static void fAssertArrayEquals(final String message, final char[] expecteds, final char[] actuals) {
         sAsserter.ok(Arrays.equals(expecteds, actuals), message, DIAG_STRING);
     }
 
-    public static void assertArrayEquals(final String message, final short[] expecteds, final short[] actuals) {
+    public static void fAssertArrayEquals(final String message, final short[] expecteds, final short[] actuals) {
         sAsserter.ok(Arrays.equals(expecteds, actuals), message, DIAG_STRING);
     }
 
-    public static void assertArrayEquals(final String message, final int[] expecteds, final int[] actuals) {
+    public static void fAssertArrayEquals(final String message, final int[] expecteds, final int[] actuals) {
         sAsserter.ok(Arrays.equals(expecteds, actuals), message, DIAG_STRING);
     }
 
-    public static void assertArrayEquals(final String message, final long[] expecteds, final long[] actuals) {
+    public static void fAssertArrayEquals(final String message, final long[] expecteds, final long[] actuals) {
         sAsserter.ok(Arrays.equals(expecteds, actuals), message, DIAG_STRING);
     }
 
-    public static void assertArrayEquals(final String message, final Object[] expecteds, final Object[] actuals) {
+    public static void fAssertArrayEquals(final String message, final Object[] expecteds, final Object[] actuals) {
         sAsserter.ok(Arrays.equals(expecteds, actuals), message, DIAG_STRING);
     }
 
-    public static void assertEquals(final String message, final double expected, final double actual, final double delta) {
+    public static void fAssertEquals(final String message, final double expected, final double actual, final double delta) {
         if (Double.compare(expected, actual) != 0) {
             sAsserter.ok(Math.abs(expected - actual) <= delta, message, DIAG_STRING);
         }
     }
 
-    public static void assertEquals(final String message, final long expected, final long actual) {
+    public static void fAssertEquals(final String message, final long expected, final long actual) {
         sAsserter.is(actual, expected, message);
     }
 
-    public static void assertEquals(final String message, final Object expected, final Object actual) {
+    public static void fAssertEquals(final String message, final Object expected, final Object actual) {
         sAsserter.is(actual, expected, message);
     }
 
-    public static void assertNotEquals(final String message, final double unexpected, final double actual, final double delta) {
+    public static void fAssertNotEquals(final String message, final double unexpected, final double actual, final double delta) {
         sAsserter.ok(Math.abs(unexpected - actual) > delta, message, DIAG_STRING);
     }
 
-    public static void assertNotEquals(final String message, final long unexpected, final long actual) {
+    public static void fAssertNotEquals(final String message, final long unexpected, final long actual) {
         sAsserter.isnot(actual, unexpected, message);
     }
 
-    public static void assertNotEquals(final String message, final Object unexpected, final Object actual) {
+    public static void fAssertNotEquals(final String message, final Object unexpected, final Object actual) {
         sAsserter.isnot(actual, unexpected, message);
     }
 
-    public static void assertFalse(final String message, final boolean actual) {
+    public static void fAssertFalse(final String message, final boolean actual) {
         sAsserter.ok(!actual, message, DIAG_STRING);
     }
 
-    public static void assertNotNull(final String message, final Object actual) {
+    public static void fAssertNotNull(final String message, final Object actual) {
         sAsserter.isnot(actual, null, message);
     }
 
-    public static void assertNotSame(final String message, final Object unexpected, final Object actual) {
+    public static void fAssertNotSame(final String message, final Object unexpected, final Object actual) {
         sAsserter.ok(unexpected != actual, message, DIAG_STRING);
     }
 
-    public static void assertNull(final String message, final Object actual) {
+    public static void fAssertNull(final String message, final Object actual) {
         sAsserter.is(actual, null, message);
     }
 
-    public static void assertSame(final String message, final Object expected, final Object actual) {
+    public static void fAssertSame(final String message, final Object expected, final Object actual) {
         sAsserter.ok(expected == actual, message, DIAG_STRING);
     }
 
-    public static void assertTrue(final String message, final boolean actual) {
+    public static void fAssertTrue(final String message, final boolean actual) {
         sAsserter.ok(actual, message, DIAG_STRING);
     }
 
-    public static void assertIsPixel(final String message, final int actual, final int r, final int g, final int b) {
+    public static void fAssertIsPixel(final String message, final int actual, final int r, final int g, final int b) {
 	sAsserter.ispixel(actual, r, g, b, message);
     }
 
-    public static void assertIsNotPixel(final String message, final int actual, final int r, final int g, final int b) {
+    public static void fAssertIsNotPixel(final String message, final int actual, final int r, final int g, final int b) {
 	sAsserter.isnotpixel(actual, r, g, b, message);
     }
 
-    public static void fail(final String message) {
+    public static void fFail(final String message) {
         sAsserter.ok(false, message, DIAG_STRING);
     }
 }
--- a/mobile/android/base/tests/helpers/DeviceHelper.java
+++ b/mobile/android/base/tests/helpers/DeviceHelper.java
@@ -39,17 +39,17 @@ public final class DeviceHelper {
     private static AndroidVersion sAndroidVersion;
 
     private static int sScreenHeight;
     private static int sScreenWidth;
 
     private DeviceHelper() { /* To disallow instantiation. */ }
 
     public static void assertIsTablet() {
-        assertTrue("The device is a tablet", isTablet());
+        fAssertTrue("The device is a tablet", isTablet());
     }
 
     protected static void init(final UITestContext context) {
         sActivity = context.getActivity();
         sSolo = context.getSolo();
 
         setAndroidVersion();
         setScreenDimensions();
--- a/mobile/android/base/tests/helpers/FrameworkHelper.java
+++ b/mobile/android/base/tests/helpers/FrameworkHelper.java
@@ -48,39 +48,39 @@ public final class FrameworkHelper {
             final boolean accessible = field.isAccessible();
             field.setAccessible(true);
             final Object ret = field.get(obj);
             field.setAccessible(accessible);
             return ret;
         } catch (final NoSuchFieldException e) {
             // We expect a valid field name; if it's not valid,
             // the caller is doing something wrong and should be fixed.
-            fail("Argument field should be a valid field name: " + e.toString());
+            fFail("Argument field should be a valid field name: " + e.toString());
         } catch (final IllegalAccessException e) {
             // This should not happen. If it does, setAccessible above is not working.
-            fail("Field should be accessible: " + e.toString());
+            fFail("Field should be accessible: " + e.toString());
         }
         throw new IllegalStateException("Should not continue from previous failures");
     }
 
     private static void setField(final Object obj, final String fieldName, final Object value) {
         try {
             final Field field = getClassField(obj.getClass(), fieldName);
             final boolean accessible = field.isAccessible();
             field.setAccessible(true);
             field.set(obj, value);
             field.setAccessible(accessible);
             return;
         } catch (final NoSuchFieldException e) {
             // We expect a valid field name; if it's not valid,
             // the caller is doing something wrong and should be fixed.
-            fail("Argument field should be a valid field name: " + e.toString());
+            fFail("Argument field should be a valid field name: " + e.toString());
         } catch (final IllegalAccessException e) {
             // This should not happen. If it does, setAccessible above is not working.
-            fail("Field should be accessible: " + e.toString());
+            fFail("Field should be accessible: " + e.toString());
         }
         throw new IllegalStateException("Cannot continue from previous failures");
     }
 
     public static Context getViewContext(final View v) {
         return (Context) getField(v, "mContext");
     }
 
--- a/mobile/android/base/tests/helpers/NavigationHelper.java
+++ b/mobile/android/base/tests/helpers/NavigationHelper.java
@@ -30,29 +30,29 @@ final public class NavigationHelper {
         sContext = context;
         sSolo = context.getSolo();
 
         sAppMenu = (AppMenuComponent) context.getComponent(ComponentType.APPMENU);
         sToolbar = (ToolbarComponent) context.getComponent(ComponentType.TOOLBAR);
     }
 
     public static void enterAndLoadUrl(String url) {
-        assertNotNull("url is not null", url);
+        fAssertNotNull("url is not null", url);
 
         url = adjustUrl(url);
         sToolbar.enterEditingMode()
                 .enterUrl(url)
                 .commitEditingMode();
     }
 
     /**
      * Returns a new URL with the docshell HTTP server host prefix.
      */
     private static String adjustUrl(final String url) {
-        assertNotNull("url is not null", url);
+        fAssertNotNull("url is not null", url);
 
         if (url.startsWith("about:") || url.startsWith("chrome:")) {
             return url;
         }
 
         return sContext.getAbsoluteHostnameUrl(url);
     }
 
--- a/mobile/android/base/tests/helpers/TextInputHelper.java
+++ b/mobile/android/base/tests/helpers/TextInputHelper.java
@@ -27,43 +27,43 @@ public final class TextInputHelper {
 
     private static String getText(final InputConnection ic) {
         return getExtractedText(ic).text.toString();
     }
 
     public static void assertText(final String message,
                                   final InputConnection ic,
                                   final String text) {
-        assertEquals(message, text, getText(ic));
+        fAssertEquals(message, text, getText(ic));
     }
 
     public static void assertSelection(final String message,
                                        final InputConnection ic,
                                        final int start,
                                        final int end) {
         ExtractedText extract = getExtractedText(ic);
-        assertEquals(message, start, extract.selectionStart);
-        assertEquals(message, end, extract.selectionEnd);
+        fAssertEquals(message, start, extract.selectionStart);
+        fAssertEquals(message, end, extract.selectionEnd);
     }
 
     public static void assertSelectionAt(final String message,
                                          final InputConnection ic,
                                          final int value) {
         assertSelection(message, ic, value, value);
     }
 
     public static void assertTextAndSelection(final String message,
                                               final InputConnection ic,
                                               final String text,
                                               final int start,
                                               final int end) {
         ExtractedText extract = getExtractedText(ic);
-        assertEquals(message, text, extract.text);
-        assertEquals(message, start, extract.selectionStart);
-        assertEquals(message, end, extract.selectionEnd);
+        fAssertEquals(message, text, extract.text);
+        fAssertEquals(message, start, extract.selectionStart);
+        fAssertEquals(message, end, extract.selectionEnd);
     }
 
     public static void assertTextAndSelectionAt(final String message,
                                                 final InputConnection ic,
                                                 final String text,
                                                 final int selection) {
         assertTextAndSelection(message, ic, text, selection, selection);
     }
--- a/mobile/android/base/tests/helpers/WaitHelper.java
+++ b/mobile/android/base/tests/helpers/WaitHelper.java
@@ -49,34 +49,34 @@ public final class WaitHelper {
     }
 
     /**
      * Waits for the given {@link solo.Condition} using the default wait duration; will throw an
      * AssertionError if the duration is elapsed and the condition is not satisfied.
      */
     public static void waitFor(String message, final Condition condition) {
         message = "Waiting for " + message + ".";
-        assertTrue(message, sSolo.waitForCondition(condition, DEFAULT_MAX_WAIT_MS));
+        fAssertTrue(message, sSolo.waitForCondition(condition, DEFAULT_MAX_WAIT_MS));
     }
 
     /**
      * Waits for the given {@link solo.Condition} using the given wait duration; will throw an
      * AssertionError if the duration is elapsed and the condition is not satisfied.
      */
     public static void waitFor(String message, final Condition condition, final int waitMillis) {
         message = "Waiting for " + message + " with timeout " + waitMillis + ".";
-        assertTrue(message, sSolo.waitForCondition(condition, waitMillis));
+        fAssertTrue(message, sSolo.waitForCondition(condition, waitMillis));
     }
 
     /**
      * Waits for the Gecko event declaring the page has loaded. Takes in and runs a Runnable
      * that will perform the action that will cause the page to load.
      */
     public static void waitForPageLoad(final Runnable initiatingAction) {
-        assertNotNull("initiatingAction is not null", initiatingAction);
+        fAssertNotNull("initiatingAction is not null", initiatingAction);
 
         // Some changes to the UI occur in response to the same event we listen to for when
         // the page has finished loading (e.g. a page title update). As such, we ensure this
         // UI state has changed before returning from this method; here we store the initial
         // state.
         final ChangeVerifier[] pageLoadVerifiers = PAGE_LOAD_VERIFIERS;
         for (final ChangeVerifier verifier : pageLoadVerifiers) {
             verifier.storeState();
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -122,9 +122,10 @@ skip-if = android_version == "10"
 #[testBrowserProviderPerf]
 
 # Using UITest
 #[testAboutHomePageNavigation] # see bug 947550, bug 979038 and bug 977952
 [testAboutHomeVisibility]
 # disabled on Android 2.3; bug 979597
 skip-if = android_version == "10"
 [testInputConnection]
+[testNativeCrypto]
 [testSessionHistory]
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testNativeCrypto.java
@@ -0,0 +1,189 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.tests;
+
+import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.mozilla.gecko.background.nativecode.NativeCrypto;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.tests.helpers.*;
+
+import android.os.SystemClock;
+
+/**
+ * Tests the Java wrapper over native implementations of crypto code. Test vectors from:
+ *   * PBKDF2SHA256:
+ *     - <https://github.com/ircmaxell/PHP-PasswordLib/blob/master/test/Data/Vectors/pbkdf2-draft-josefsson-sha256.test-vectors>
+       - <https://gitorious.org/scrypt/nettle-scrypt/blobs/37c0d5288e991604fe33dba2f1724986a8dddf56/testsuite/pbkdf2-test.c>
+     * SHA-1:
+       - <http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c>
+ */
+public class testNativeCrypto extends UITest {
+  private final static String LOGTAG = "testNativeCrypto";
+
+  /**
+   * Robocop supports only a single test function per test class. Therefore, we
+   * have a single top-level test function that dispatches to sub-tests,
+   * accepting that we might fail part way through the cycle. Proper JUnit 3
+   * testing can't land soon enough!
+   *
+   * @throws Exception
+   */
+  public void test() throws Exception {
+    // This test could complete very quickly. If it completes too soon, the
+    // minidumps directory may not be created before the process is
+    // taken down, causing bug 722166. But we can't run the test and then block
+    // for Gecko:Ready, since it may have arrived before we block. So we wait.
+    // Again, JUnit 3 can't land soon enough!
+    GeckoHelper.blockForReady();
+
+    _testPBKDF2SHA256A();
+    _testPBKDF2SHA256B();
+    _testPBKDF2SHA256C();
+    _testPBKDF2SHA256scryptA();
+    _testPBKDF2SHA256scryptB();
+    _testPBKDF2SHA256InvalidLenArg();
+
+    _testSHA1();
+    _testSHA1AgainstMessageDigest();
+  }
+
+  public void _testPBKDF2SHA256A() throws UnsupportedEncodingException, GeneralSecurityException {
+    final String  p = "password";
+    final String  s = "salt";
+    final int dkLen = 32;
+
+    checkPBKDF2SHA256(p, s, 1, dkLen, "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b");
+    checkPBKDF2SHA256(p, s, 4096, dkLen, "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a");
+  }
+
+  public void _testPBKDF2SHA256B() throws UnsupportedEncodingException, GeneralSecurityException {
+    final String  p = "passwordPASSWORDpassword";
+    final String  s = "saltSALTsaltSALTsaltSALTsaltSALTsalt";
+    final int dkLen = 40;
+
+    checkPBKDF2SHA256(p, s, 4096, dkLen, "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9");
+  }
+
+  public void _testPBKDF2SHA256scryptA() throws UnsupportedEncodingException, GeneralSecurityException {
+    final String  p = "passwd";
+    final String  s = "salt";
+    final int dkLen = 64;
+
+    checkPBKDF2SHA256(p, s, 1, dkLen, "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783");
+  }
+
+  public void _testPBKDF2SHA256scryptB() throws UnsupportedEncodingException, GeneralSecurityException {
+    final String  p = "Password";
+    final String  s = "NaCl";
+    final int dkLen = 64;
+
+    checkPBKDF2SHA256(p, s, 80000, dkLen, "4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d");
+  }
+
+  public void _testPBKDF2SHA256C() throws UnsupportedEncodingException, GeneralSecurityException {
+    final String  p = "pass\0word";
+    final String  s = "sa\0lt";
+    final int dkLen = 16;
+
+    checkPBKDF2SHA256(p, s, 4096, dkLen, "89b69d0516f829893c696226650a8687");
+  }
+
+  public void _testPBKDF2SHA256InvalidLenArg() throws UnsupportedEncodingException, GeneralSecurityException {
+    final String p = "password";
+    final String s = "salt";
+    final int c = 1;
+    final int dkLen = -1; // Should always be positive.
+
+    try {
+      final byte[] key = NativeCrypto.pbkdf2SHA256(p.getBytes("US-ASCII"), s.getBytes("US-ASCII"), c, dkLen);
+      fFail("Expected sha256 to throw with negative dkLen argument.");
+    } catch (IllegalArgumentException e) { } // Expected.
+  }
+
+  private void _testSHA1() throws UnsupportedEncodingException {
+    final String[] inputs = new String[] {
+      "abc",
+      "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+      "" // To be filled in below.
+    };
+    final String baseStr = "01234567";
+    final int repetitions = 80;
+    final StringBuilder builder = new StringBuilder(baseStr.length() * repetitions);
+    for (int i = 0; i < 80; ++i) {
+      builder.append(baseStr);
+    }
+    inputs[2] = builder.toString();
+
+    final String[] expecteds = new String[] {
+      "a9993e364706816aba3e25717850c26c9cd0d89d",
+      "84983e441c3bd26ebaae4aa1f95129e5e54670f1",
+      "dea356a2cddd90c7a7ecedc5ebb563934f460452"
+    };
+
+    for (int i = 0; i < inputs.length; ++i) {
+      final byte[] input = inputs[i].getBytes("US-ASCII");
+      final String expected = expecteds[i];
+
+      final byte[] actual = NativeCrypto.sha1(input);
+      fAssertNotNull("Hashed value is non-null", actual);
+      assertExpectedBytes(expected, actual);
+    }
+  }
+
+  /**
+   * Test to ensure the output of our SHA1 algo is the same as MessageDigest's. This is important
+   * because we intend to replace MessageDigest in FHR with this SHA-1 algo (bug 959652).
+   */
+  private void _testSHA1AgainstMessageDigest() throws UnsupportedEncodingException,
+      NoSuchAlgorithmException {
+    final String[] inputs = {
+      "password",
+      "saranghae",
+      "aoeusnthaoeusnthaoeusnth \0 12345098765432109876_!"
+    };
+
+    final MessageDigest digest = MessageDigest.getInstance("SHA-1");
+    for (final String input : inputs) {
+      final byte[] inputBytes = input.getBytes("US-ASCII");
+
+      final byte[] mdBytes = digest.digest(inputBytes);
+      final byte[] ourBytes = NativeCrypto.sha1(inputBytes);
+      fAssertArrayEquals("MessageDigest hash is the same as NativeCrypto SHA-1 hash", mdBytes, ourBytes);
+    }
+  }
+
+  private void checkPBKDF2SHA256(String p, String s, int c, int dkLen, final String expectedStr)
+      throws GeneralSecurityException, UnsupportedEncodingException {
+    final long start = SystemClock.elapsedRealtime();
+
+    final byte[] key = NativeCrypto.pbkdf2SHA256(p.getBytes("US-ASCII"), s.getBytes("US-ASCII"), c, dkLen);
+    fAssertNotNull("Hash result is non-null", key);
+
+    final long end = SystemClock.elapsedRealtime();
+    dumpLog(LOGTAG, "SHA-256 " + c + " took " + (end - start) + "ms");
+
+    if (expectedStr == null) {
+      return;
+    }
+
+    fAssertEquals("Hash result is the appropriate length", dkLen,
+        Utils.hex2Byte(expectedStr).length);
+    assertExpectedBytes(expectedStr, key);
+  }
+
+  private void assertExpectedBytes(final String expectedStr, byte[] key) {
+    fAssertEquals("Expected string matches hash result", expectedStr, Utils.byte2Hex(key));
+    final byte[] expected = Utils.hex2Byte(expectedStr);
+
+    fAssertEquals("Expected byte array length matches key length", expected.length, key.length);
+    fAssertArrayEquals("Expected byte array matches key byte array", expected, key);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/util/FileUtils.java
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.util;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FilenameFilter;
+
+public class FileUtils {
+    private static final String LOGTAG= "GeckoFileUtils";
+    /*
+    * A basic Filter for checking a filename and age.
+    **/
+    static public class NameAndAgeFilter implements FilenameFilter {
+        final private String mName;
+        final private double mMaxAge;
+
+        public NameAndAgeFilter(String name, double age) {
+            mName = name;
+            mMaxAge = age;
+        }
+
+        @Override
+        public boolean accept(File dir, String filename) {
+            if (mName == null || mName.matches(filename)) {
+                File f = new File(dir, filename);
+
+                if (mMaxAge < 0 || System.currentTimeMillis() - f.lastModified() > mMaxAge) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    public static void delTree(File dir, FilenameFilter filter, boolean recurse) {
+        String[] files = null;
+
+        if (filter != null) {
+          files = dir.list(filter);
+        } else {
+          files = dir.list();
+        }
+
+        if (files == null) {
+          return;
+        }
+
+        for (String file : files) {
+            File f = new File(dir, file);
+            delete(f, recurse);
+        }
+    }
+
+    public static boolean delete(File file) throws IOException {
+        return delete(file, true);
+    }
+
+    public static boolean delete(File file, boolean recurse) {
+        if (file.isDirectory() && recurse) {
+            // If the quick delete failed and this is a dir, recursively delete the contents of the dir
+            String files[] = file.list();
+            for (String temp : files) {
+                File fileDelete = new File(file, temp);
+                try {
+                    delete(fileDelete);
+                } catch(IOException ex) {
+                    Log.i(LOGTAG, "Error deleting " + fileDelete.getPath(), ex);
+                }
+            }
+        }
+
+        // Even if this is a dir, it should now be empty and delete should work
+        return file.delete();
+    }
+}
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -252,17 +252,17 @@ var SelectionHandler = {
    *                          element, or SELECT_AT_POINT to select a word.
    *                   x    - The x-coordinate for SELECT_AT_POINT.
    *                   y    - The y-coordinate for SELECT_AT_POINT.
    */
   startSelection: function sh_startSelection(aElement, aOptions = { mode: SelectionHandler.SELECT_ALL }) {
     // Clear out any existing active selection
     this._closeSelection();
 
-    this._initTargetInfo(aElement);
+    this._initTargetInfo(aElement, this.TYPE_SELECTION);
 
     // Clear any existing selection from the document
     this._contentWindow.getSelection().removeAllRanges();
 
     // Perform the appropriate selection method, if we can't determine method, or it fails, return
     if (!this._performSelection(aOptions)) {
       this._deactivate();
       return false;
@@ -505,37 +505,39 @@ var SelectionHandler = {
   attachCaret: function sh_attachCaret(aElement) {
     // See if its an input element, and it isn't disabled, nor handled by Android native dialog
     if (aElement.disabled ||
         InputWidgetHelper.hasInputWidget(aElement) ||
         !((aElement instanceof HTMLInputElement && aElement.mozIsTextField(false)) ||
           (aElement instanceof HTMLTextAreaElement)))
       return;
 
-    this._initTargetInfo(aElement);
+    this._initTargetInfo(aElement, this.TYPE_CURSOR);
 
     // Caret-specific observer/listeners
     Services.obs.addObserver(this, "TextSelection:UpdateCaretPos", false);
     BrowserApp.deck.addEventListener("keyup", this, false);
     BrowserApp.deck.addEventListener("compositionupdate", this, false);
     BrowserApp.deck.addEventListener("compositionend", this, false);
 
     this._activeType = this.TYPE_CURSOR;
     this._positionHandles();
 
     this._sendMessage("TextSelection:ShowHandles", [this.HANDLE_TYPE_MIDDLE]);
   },
 
   // Target initialization for both TYPE_CURSOR and TYPE_SELECTION
-  _initTargetInfo: function sh_initTargetInfo(aElement) {
+  _initTargetInfo: function sh_initTargetInfo(aElement, aSelectionType) {
     this._targetElement = aElement;
     if (aElement instanceof Ci.nsIDOMNSEditableElement) {
-      // Blur the targetElement to force IME code to undo previous style compositions
-      // (visible underlines / etc generated by autoCorrection, autoSuggestion)
-      aElement.blur();
+      if (aSelectionType === this.TYPE_SELECTION) {
+        // Blur the targetElement to force IME code to undo previous style compositions
+        // (visible underlines / etc generated by autoCorrection, autoSuggestion)
+        aElement.blur();
+      }
       // Ensure targetElement is now focused normally
       aElement.focus();
     }
 
     this._contentWindow = aElement.ownerDocument.defaultView;
     this._isRTL = (this._contentWindow.getComputedStyle(aElement, "").direction == "rtl");
 
     this._addObservers();
--- a/mobile/android/components/FilePicker.js
+++ b/mobile/android/components/FilePicker.js
@@ -192,18 +192,28 @@ FilePicker.prototype = {
   open: function(callback) {
     this._callback = callback;
     this._sendMessage();
   },
 
   _sendMessage: function() {
     let msg = {
       type: "FilePicker:Show",
-      guid: this.guid
+      guid: this.guid,
     };
+
+    // Knowing the window lets us destroy any temp files when the tab is closed
+    // Other consumers of the file picker may have to either wait for Android
+    // to clean up the temp dir (not guaranteed) or clean up after themselves.
+    let win = Services.wm.getMostRecentWindow('navigator:browser');
+    let tab = win.BrowserApp.getTabForWindow(this._domWin.top)
+    if (tab) {
+      msg.tabId = tab.id;
+    }
+
     if (!this._extensionsFilter && !this._mimeTypeFilter) {
       // If neither filters is set show anything we can.
       msg.mode = "mimeType";
       msg.mimeType = "*/*";
     } else if (this._extensionsFilter) {
       msg.mode = "extension";
       msg.extensions = this._extensionsFilter;
     } else {
--- a/mobile/android/tests/background/junit3/android-services-files.mk
+++ b/mobile/android/tests/background/junit3/android-services-files.mk
@@ -38,16 +38,17 @@ BACKGROUND_TESTS_JAVA_FILES := \
   src/healthreport/TestProfileInformationCache.java \
   src/healthreport/upload/TestAndroidSubmissionClient.java \
   src/healthreport/upload/TestHealthReportUploadService.java \
   src/helpers/AndroidSyncTestCase.java \
   src/helpers/BackgroundServiceTestCase.java \
   src/helpers/DBHelpers.java \
   src/helpers/DBProviderTestCase.java \
   src/helpers/FakeProfileTestCase.java \
+  src/nativecode/test/TestNativeCrypto.java \
   src/sync/helpers/BookmarkHelpers.java \
   src/sync/helpers/DefaultBeginDelegate.java \
   src/sync/helpers/DefaultCleanDelegate.java \
   src/sync/helpers/DefaultDelegate.java \
   src/sync/helpers/DefaultFetchDelegate.java \
   src/sync/helpers/DefaultFinishDelegate.java \
   src/sync/helpers/DefaultGuidsSinceDelegate.java \
   src/sync/helpers/DefaultSessionCreationDelegate.java \
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit3/src/nativecode/test/TestNativeCrypto.java
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.gecko.background.nativecode.test;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+
+import junit.framework.TestCase;
+
+import org.mozilla.gecko.background.nativecode.NativeCrypto;
+import org.mozilla.gecko.sync.Utils;
+
+// Test vectors from
+// SHA-256: <https://github.com/ircmaxell/PHP-PasswordLib/blob/master/test/Data/Vectors/pbkdf2-draft-josefsson-sha256.test-vectors>
+//          <https://gitorious.org/scrypt/nettle-scrypt/blobs/37c0d5288e991604fe33dba2f1724986a8dddf56/testsuite/pbkdf2-test.c>
+public class TestNativeCrypto extends TestCase {
+
+  public final void testPBKDF2SHA256A() throws UnsupportedEncodingException, GeneralSecurityException {
+    String  p = "password";
+    String  s = "salt";
+    int dkLen = 32;
+
+    checkPBKDF2SHA256(p, s, 1, dkLen, "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b");
+    checkPBKDF2SHA256(p, s, 4096, dkLen, "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a");
+  }
+
+  public final void testPBKDF2SHA256B() throws UnsupportedEncodingException, GeneralSecurityException {
+    String  p = "passwordPASSWORDpassword";
+    String  s = "saltSALTsaltSALTsaltSALTsaltSALTsalt";
+    int dkLen = 40;
+
+    checkPBKDF2SHA256(p, s, 4096, dkLen, "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9");
+  }
+
+  public final void testPBKDF2SHA256scryptA() throws UnsupportedEncodingException, GeneralSecurityException {
+    String  p = "passwd";
+    String  s = "salt";
+    int dkLen = 64;
+
+    checkPBKDF2SHA256(p, s, 1, dkLen, "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783");
+  }
+
+  public final void testPBKDF2SHA256scryptB() throws UnsupportedEncodingException, GeneralSecurityException {
+    String  p = "Password";
+    String  s = "NaCl";
+    int dkLen = 64;
+
+    checkPBKDF2SHA256(p, s, 80000, dkLen, "4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d");
+  }
+
+  public final void testPBKDF2SHA256C() throws UnsupportedEncodingException, GeneralSecurityException {
+    String  p = "pass\0word";
+    String  s = "sa\0lt";
+    int dkLen = 16;
+
+    checkPBKDF2SHA256(p, s, 4096, dkLen, "89b69d0516f829893c696226650a8687");
+  }
+
+  /*
+  // This test takes two or three minutes to run, so we don't.
+  public final void testPBKDF2SHA256D() throws UnsupportedEncodingException, GeneralSecurityException {
+    String  p = "password";
+    String  s = "salt";
+    int dkLen = 32;
+
+    checkPBKDF2SHA256(p, s, 16777216, dkLen, "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e89b3b0bcb17ad10e3ac6eba46");
+  }
+  */
+
+  public final void testTimePBKDF2SHA256() throws UnsupportedEncodingException, GeneralSecurityException {
+    checkPBKDF2SHA256("password", "salt", 80000, 32, null);
+  }
+
+  private void checkPBKDF2SHA256(String p, String s, int c, int dkLen,
+                                final String expectedStr)
+                                                    throws GeneralSecurityException, UnsupportedEncodingException {
+    long start = System.currentTimeMillis();
+    byte[] key = NativeCrypto.pbkdf2SHA256(p.getBytes("US-ASCII"), s.getBytes("US-ASCII"), c, dkLen);
+    assertNotNull(key);
+
+    long end = System.currentTimeMillis();
+
+    System.err.println("SHA-256 " + c + " took " + (end - start) + "ms");
+    if (expectedStr == null) {
+      return;
+    }
+
+    assertEquals(dkLen, Utils.hex2Byte(expectedStr).length);
+    assertExpectedBytes(expectedStr, key);
+  }
+
+  private void assertExpectedBytes(final String expectedStr, byte[] key) {
+    assertEquals(expectedStr, Utils.byte2Hex(key));
+    byte[] expected = Utils.hex2Byte(expectedStr);
+
+    assertEquals(expected.length, key.length);
+    for (int i = 0; i < key.length; i++) {
+      assertEquals(expected[i], key[i]);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mozglue/android/NativeCrypto.cpp
@@ -0,0 +1,74 @@
+/* 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 "NativeCrypto.h"
+
+#include <jni.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "mozilla/SHA1.h"
+#include "pbkdf2_sha256.h"
+
+/**
+ * Helper function to invoke native PBKDF2 function with JNI
+ * arguments.
+ */
+extern "C" JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_pbkdf2SHA256
+    (JNIEnv *env, jclass jc, jbyteArray jpassword, jbyteArray jsalt, jint c, jint dkLen) {
+  if (dkLen < 0) {
+    env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
+                  "dkLen should not be less than 0");
+    return NULL;
+  }
+
+  jbyte *password = env->GetByteArrayElements(jpassword, NULL);
+  size_t passwordLen = env->GetArrayLength(jpassword);
+
+  jbyte *salt = env->GetByteArrayElements(jsalt, NULL);
+  size_t saltLen = env->GetArrayLength(jsalt);
+
+  uint8_t hashResult[dkLen];
+  PBKDF2_SHA256((uint8_t *) password, passwordLen, (uint8_t *) salt, saltLen,
+      (uint64_t) c, hashResult, (size_t) dkLen);
+
+  env->ReleaseByteArrayElements(jpassword, password, JNI_ABORT);
+  env->ReleaseByteArrayElements(jsalt, salt, JNI_ABORT);
+
+  jbyteArray out = env->NewByteArray(dkLen);
+  if (out == NULL) {
+    return NULL;
+  }
+  env->SetByteArrayRegion(out, 0, dkLen, (jbyte *) hashResult);
+
+  return out;
+}
+
+using namespace mozilla;
+
+/**
+ * Helper function to invoke native SHA-1 function with JNI arguments.
+ */
+extern "C" JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha1
+    (JNIEnv *env, jclass jc, jbyteArray jstr) {
+  jbyte *str = env->GetByteArrayElements(jstr, NULL);
+  size_t strLen = env->GetArrayLength(jstr);
+
+  SHA1Sum sha1;
+  SHA1Sum::Hash hashResult;
+  sha1.update((void *) str, (uint32_t) strLen);
+  sha1.finish(hashResult);
+
+  env->ReleaseByteArrayElements(jstr, str, JNI_ABORT);
+
+  jbyteArray out = env->NewByteArray(SHA1Sum::HashSize);
+  if (out == NULL) {
+    return NULL;
+  }
+  env->SetByteArrayRegion(out, 0, SHA1Sum::HashSize, (jbyte *) hashResult);
+
+  return out;
+}
new file mode 100644
--- /dev/null
+++ b/mozglue/android/NativeCrypto.h
@@ -0,0 +1,29 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_mozilla_gecko_background_nativecode_NativeCrypto */
+
+#ifndef _Included_org_mozilla_gecko_background_nativecode_NativeCrypto
+#define _Included_org_mozilla_gecko_background_nativecode_NativeCrypto
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     org_mozilla_gecko_background_nativecode_NativeCrypto
+ * Method:    pbkdf2SHA256
+ * Signature: ([B[BII)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_pbkdf2SHA256
+  (JNIEnv *, jclass, jbyteArray, jbyteArray, jint, jint);
+
+/*
+ * Class:     org_mozilla_gecko_background_nativecode_NativeCrypto
+ * Method:    sha1
+ * Signature: ([B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha1
+  (JNIEnv *, jclass, jbyteArray);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
--- a/mozglue/android/moz.build
+++ b/mozglue/android/moz.build
@@ -5,18 +5,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'APKOpen.h',
 ]
 
 SOURCES += [
     'APKOpen.cpp',
+    'NativeCrypto.cpp',
     'nsGeckoUtils.cpp',
     'NSSBridge.cpp',
+    'pbkdf2_sha256.c',
     'SQLiteBridge.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'mozglue'
 
 DEFINES['ANDROID_PACKAGE_NAME'] = '"%s"' % CONFIG['ANDROID_PACKAGE_NAME']
new file mode 100644
--- /dev/null
+++ b/mozglue/android/pbkdf2_sha256.c
@@ -0,0 +1,432 @@
+/*-
+ * Copyright 2005,2007,2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/types.h>
+
+#include <stdint.h>
+#include <string.h>
+
+#include <sys/endian.h>
+
+#include "pbkdf2_sha256.h"
+
+static inline uint32_t
+be32dec(const void *pp)
+{
+	const uint8_t *p = (uint8_t const *)pp;
+
+	return ((uint32_t)(p[3]) +
+		((uint32_t)(p[2]) << 8) +
+		((uint32_t)(p[1]) << 16) +
+		((uint32_t)(p[0]) << 24));
+}
+
+static inline void
+be32enc(void *pp, uint32_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[3] = x & 0xff;
+	p[2] = (x >> 8) & 0xff;
+	p[1] = (x >> 16) & 0xff;
+	p[0] = (x >> 24) & 0xff;
+}
+
+/*
+ * Encode a length len/4 vector of (uint32_t) into a length len vector of
+ * (unsigned char) in big-endian form.  Assumes len is a multiple of 4.
+ */
+static void
+be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len / 4; i++)
+		be32enc(dst + i * 4, src[i]);
+}
+
+/*
+ * Decode a big-endian length len vector of (unsigned char) into a length
+ * len/4 vector of (uint32_t).  Assumes len is a multiple of 4.
+ */
+static void
+be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len / 4; i++)
+		dst[i] = be32dec(src + i * 4);
+}
+
+/* Elementary functions used by SHA256 */
+#define Ch(x, y, z)	((x & (y ^ z)) ^ z)
+#define Maj(x, y, z)	((x & (y | z)) | (y & z))
+#define SHR(x, n)	(x >> n)
+#define ROTR(x, n)	((x >> n) | (x << (32 - n)))
+#define S0(x)		(ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define S1(x)		(ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define s0(x)		(ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3))
+#define s1(x)		(ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10))
+
+/* SHA256 round function */
+#define RND(a, b, c, d, e, f, g, h, k)			\
+	t0 = h + S1(e) + Ch(e, f, g) + k;		\
+	t1 = S0(a) + Maj(a, b, c);			\
+	d += t0;					\
+	h  = t0 + t1;
+
+/* Adjusted round function for rotating state */
+#define RNDr(S, W, i, k)			\
+	RND(S[(64 - i) % 8], S[(65 - i) % 8],	\
+	    S[(66 - i) % 8], S[(67 - i) % 8],	\
+	    S[(68 - i) % 8], S[(69 - i) % 8],	\
+	    S[(70 - i) % 8], S[(71 - i) % 8],	\
+	    W[i] + k)
+
+/*
+ * SHA256 block compression function.  The 256-bit state is transformed via
+ * the 512-bit input block to produce a new state.
+ */
+static void
+SHA256_Transform(uint32_t * state, const unsigned char block[64])
+{
+	uint32_t W[64];
+	uint32_t S[8];
+	uint32_t t0, t1;
+	int i;
+
+	/* 1. Prepare message schedule W. */
+	be32dec_vect(W, block, 64);
+	for (i = 16; i < 64; i++)
+		W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16];
+
+	/* 2. Initialize working variables. */
+	memcpy(S, state, 32);
+
+	/* 3. Mix. */
+	RNDr(S, W, 0, 0x428a2f98);
+	RNDr(S, W, 1, 0x71374491);
+	RNDr(S, W, 2, 0xb5c0fbcf);
+	RNDr(S, W, 3, 0xe9b5dba5);
+	RNDr(S, W, 4, 0x3956c25b);
+	RNDr(S, W, 5, 0x59f111f1);
+	RNDr(S, W, 6, 0x923f82a4);
+	RNDr(S, W, 7, 0xab1c5ed5);
+	RNDr(S, W, 8, 0xd807aa98);
+	RNDr(S, W, 9, 0x12835b01);
+	RNDr(S, W, 10, 0x243185be);
+	RNDr(S, W, 11, 0x550c7dc3);
+	RNDr(S, W, 12, 0x72be5d74);
+	RNDr(S, W, 13, 0x80deb1fe);
+	RNDr(S, W, 14, 0x9bdc06a7);
+	RNDr(S, W, 15, 0xc19bf174);
+	RNDr(S, W, 16, 0xe49b69c1);
+	RNDr(S, W, 17, 0xefbe4786);
+	RNDr(S, W, 18, 0x0fc19dc6);
+	RNDr(S, W, 19, 0x240ca1cc);
+	RNDr(S, W, 20, 0x2de92c6f);
+	RNDr(S, W, 21, 0x4a7484aa);
+	RNDr(S, W, 22, 0x5cb0a9dc);
+	RNDr(S, W, 23, 0x76f988da);
+	RNDr(S, W, 24, 0x983e5152);
+	RNDr(S, W, 25, 0xa831c66d);
+	RNDr(S, W, 26, 0xb00327c8);
+	RNDr(S, W, 27, 0xbf597fc7);
+	RNDr(S, W, 28, 0xc6e00bf3);
+	RNDr(S, W, 29, 0xd5a79147);
+	RNDr(S, W, 30, 0x06ca6351);
+	RNDr(S, W, 31, 0x14292967);
+	RNDr(S, W, 32, 0x27b70a85);
+	RNDr(S, W, 33, 0x2e1b2138);
+	RNDr(S, W, 34, 0x4d2c6dfc);
+	RNDr(S, W, 35, 0x53380d13);
+	RNDr(S, W, 36, 0x650a7354);
+	RNDr(S, W, 37, 0x766a0abb);
+	RNDr(S, W, 38, 0x81c2c92e);
+	RNDr(S, W, 39, 0x92722c85);
+	RNDr(S, W, 40, 0xa2bfe8a1);
+	RNDr(S, W, 41, 0xa81a664b);
+	RNDr(S, W, 42, 0xc24b8b70);
+	RNDr(S, W, 43, 0xc76c51a3);
+	RNDr(S, W, 44, 0xd192e819);
+	RNDr(S, W, 45, 0xd6990624);
+	RNDr(S, W, 46, 0xf40e3585);
+	RNDr(S, W, 47, 0x106aa070);
+	RNDr(S, W, 48, 0x19a4c116);
+	RNDr(S, W, 49, 0x1e376c08);
+	RNDr(S, W, 50, 0x2748774c);
+	RNDr(S, W, 51, 0x34b0bcb5);
+	RNDr(S, W, 52, 0x391c0cb3);
+	RNDr(S, W, 53, 0x4ed8aa4a);
+	RNDr(S, W, 54, 0x5b9cca4f);
+	RNDr(S, W, 55, 0x682e6ff3);
+	RNDr(S, W, 56, 0x748f82ee);
+	RNDr(S, W, 57, 0x78a5636f);
+	RNDr(S, W, 58, 0x84c87814);
+	RNDr(S, W, 59, 0x8cc70208);
+	RNDr(S, W, 60, 0x90befffa);
+	RNDr(S, W, 61, 0xa4506ceb);
+	RNDr(S, W, 62, 0xbef9a3f7);
+	RNDr(S, W, 63, 0xc67178f2);
+
+	/* 4. Mix local working variables into global state. */
+	for (i = 0; i < 8; i++)
+		state[i] += S[i];
+
+	/* Clean the stack. */
+	memset(W, 0, 256);
+	memset(S, 0, 32);
+	t0 = t1 = 0;
+}
+
+static unsigned char PAD[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* Add padding and terminating bit-count. */
+static void
+SHA256_Pad(SHA256_CTX * ctx)
+{
+	unsigned char len[8];
+	uint32_t r, plen;
+
+	/*
+	 * Convert length to a vector of bytes -- we do this now rather
+	 * than later because the length will change after we pad.
+	 */
+	be32enc_vect(len, ctx->count, 8);
+
+	/* Add 1--64 bytes so that the resulting length is 56 mod 64. */
+	r = (ctx->count[1] >> 3) & 0x3f;
+	plen = (r < 56) ? (56 - r) : (120 - r);
+	SHA256_Update(ctx, PAD, (size_t)plen);
+
+	/* Add the terminating bit-count. */
+	SHA256_Update(ctx, len, 8);
+}
+
+/* SHA-256 initialization.  Begins a SHA-256 operation. */
+void
+SHA256_Init(SHA256_CTX * ctx)
+{
+
+	/* Zero bits processed so far. */
+	ctx->count[0] = ctx->count[1] = 0;
+
+	/* Magic initialization constants. */
+	ctx->state[0] = 0x6A09E667;
+	ctx->state[1] = 0xBB67AE85;
+	ctx->state[2] = 0x3C6EF372;
+	ctx->state[3] = 0xA54FF53A;
+	ctx->state[4] = 0x510E527F;
+	ctx->state[5] = 0x9B05688C;
+	ctx->state[6] = 0x1F83D9AB;
+	ctx->state[7] = 0x5BE0CD19;
+}
+
+/* Add bytes into the hash. */
+void
+SHA256_Update(SHA256_CTX * ctx, const void *in, size_t len)
+{
+	uint32_t bitlen[2];
+	uint32_t r;
+	const unsigned char *src = in;
+
+	/* Number of bytes left in the buffer from previous updates. */
+	r = (ctx->count[1] >> 3) & 0x3f;
+
+	/* Convert the length into a number of bits. */
+	bitlen[1] = ((uint32_t)len) << 3;
+	bitlen[0] = (uint32_t)(len >> 29);
+
+	/* Update number of bits. */
+	if ((ctx->count[1] += bitlen[1]) < bitlen[1])
+		ctx->count[0]++;
+	ctx->count[0] += bitlen[0];
+
+	/* Handle the case where we don't need to perform any transforms. */
+	if (len < 64 - r) {
+		memcpy(&ctx->buf[r], src, len);
+		return;
+	}
+
+	/* Finish the current block. */
+	memcpy(&ctx->buf[r], src, 64 - r);
+	SHA256_Transform(ctx->state, ctx->buf);
+	src += 64 - r;
+	len -= 64 - r;
+
+	/* Perform complete blocks. */
+	while (len >= 64) {
+		SHA256_Transform(ctx->state, src);
+		src += 64;
+		len -= 64;
+	}
+
+	/* Copy left over data into buffer. */
+	memcpy(ctx->buf, src, len);
+}
+
+/*
+ * SHA-256 finalization.  Pads the input data, exports the hash value,
+ * and clears the context state.
+ */
+void
+SHA256_Final(unsigned char digest[32], SHA256_CTX * ctx)
+{
+
+	/* Add padding. */
+	SHA256_Pad(ctx);
+
+	/* Write the hash. */
+	be32enc_vect(digest, ctx->state, 32);
+
+	/* Clear the context state. */
+	memset((void *)ctx, 0, sizeof(*ctx));
+}
+
+/* Initialize an HMAC-SHA256 operation with the given key. */
+void
+HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen)
+{
+	unsigned char pad[64];
+	unsigned char khash[32];
+	const unsigned char * K = _K;
+	size_t i;
+
+	/* If Klen > 64, the key is really SHA256(K). */
+	if (Klen > 64) {
+		SHA256_Init(&ctx->ictx);
+		SHA256_Update(&ctx->ictx, K, Klen);
+		SHA256_Final(khash, &ctx->ictx);
+		K = khash;
+		Klen = 32;
+	}
+
+	/* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */
+	SHA256_Init(&ctx->ictx);
+	memset(pad, 0x36, 64);
+	for (i = 0; i < Klen; i++)
+		pad[i] ^= K[i];
+	SHA256_Update(&ctx->ictx, pad, 64);
+
+	/* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */
+	SHA256_Init(&ctx->octx);
+	memset(pad, 0x5c, 64);
+	for (i = 0; i < Klen; i++)
+		pad[i] ^= K[i];
+	SHA256_Update(&ctx->octx, pad, 64);
+
+	/* Clean the stack. */
+	memset(khash, 0, 32);
+}
+
+/* Add bytes to the HMAC-SHA256 operation. */
+void
+HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void *in, size_t len)
+{
+
+	/* Feed data to the inner SHA256 operation. */
+	SHA256_Update(&ctx->ictx, in, len);
+}
+
+/* Finish an HMAC-SHA256 operation. */
+void
+HMAC_SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX * ctx)
+{
+	unsigned char ihash[32];
+
+	/* Finish the inner SHA256 operation. */
+	SHA256_Final(ihash, &ctx->ictx);
+
+	/* Feed the inner hash to the outer SHA256 operation. */
+	SHA256_Update(&ctx->octx, ihash, 32);
+
+	/* Finish the outer SHA256 operation. */
+	SHA256_Final(digest, &ctx->octx);
+
+	/* Clean the stack. */
+	memset(ihash, 0, 32);
+}
+
+/**
+ * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen):
+ * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and
+ * write the output to buf.  The value dkLen must be at most 32 * (2^32 - 1).
+ */
+void
+PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt,
+    size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen)
+{
+	HMAC_SHA256_CTX PShctx, hctx;
+	size_t i;
+	uint8_t ivec[4];
+	uint8_t U[32];
+	uint8_t T[32];
+	uint64_t j;
+	int k;
+	size_t clen;
+
+	/* Compute HMAC state after processing P and S. */
+	HMAC_SHA256_Init(&PShctx, passwd, passwdlen);
+	HMAC_SHA256_Update(&PShctx, salt, saltlen);
+
+	/* Iterate through the blocks. */
+	for (i = 0; i * 32 < dkLen; i++) {
+		/* Generate INT(i + 1). */
+		be32enc(ivec, (uint32_t)(i + 1));
+
+		/* Compute U_1 = PRF(P, S || INT(i)). */
+		memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX));
+		HMAC_SHA256_Update(&hctx, ivec, 4);
+		HMAC_SHA256_Final(U, &hctx);
+
+		/* T_i = U_1 ... */
+		memcpy(T, U, 32);
+
+		for (j = 2; j <= c; j++) {
+			/* Compute U_j. */
+			HMAC_SHA256_Init(&hctx, passwd, passwdlen);
+			HMAC_SHA256_Update(&hctx, U, 32);
+			HMAC_SHA256_Final(U, &hctx);
+
+			/* ... xor U_j ... */
+			for (k = 0; k < 32; k++)
+				T[k] ^= U[k];
+		}
+
+		/* Copy as many bytes as necessary into buf. */
+		clen = dkLen - i * 32;
+		if (clen > 32)
+			clen = 32;
+		memcpy(&buf[i * 32], T, clen);
+	}
+
+	/* Clean PShctx, since we never called _Final on it. */
+	memset(&PShctx, 0, sizeof(HMAC_SHA256_CTX));
+}
new file mode 100644
--- /dev/null
+++ b/mozglue/android/pbkdf2_sha256.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright 2005,2007,2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/lib/libmd/sha256.h,v 1.2 2006/01/17 15:35:56 phk Exp $
+ */
+
+#ifndef _SHA256_H_
+#define _SHA256_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+
+#include <stdint.h>
+
+typedef struct SHA256Context {
+	uint32_t state[8];
+	uint32_t count[2];
+	unsigned char buf[64];
+} SHA256_CTX;
+
+typedef struct HMAC_SHA256Context {
+	SHA256_CTX ictx;
+	SHA256_CTX octx;
+} HMAC_SHA256_CTX;
+
+void	SHA256_Init(SHA256_CTX *);
+void	SHA256_Update(SHA256_CTX *, const void *, size_t);
+void	SHA256_Final(unsigned char [32], SHA256_CTX *);
+void	HMAC_SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t);
+void	HMAC_SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t);
+void	HMAC_SHA256_Final(unsigned char [32], HMAC_SHA256_CTX *);
+
+/**
+ * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen):
+ * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and
+ * write the output to buf.  The value dkLen must be at most 32 * (2^32 - 1).
+ */
+void	PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t,
+    uint64_t, uint8_t *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_SHA256_H_ */