merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 09 Oct 2013 10:15:37 +0200
changeset 150187 c22969eec61dd6ebc65469d25a5f9265ce7075b4
parent 150164 50e0868f52c53e7307371e8e8b5ee830b20503c0 (current diff)
parent 150186 e72312830067144fe591fd0f925fb1c5cadcd6af (diff)
child 150188 2c3bb1b8f2a17744b394dbdc43d08d6e68fd8d74
child 150236 9b2a7723a74c998d402b85b443440711d4d4bdb0
child 150258 130904eff2c5674bd32bf11c65506787342b1ad5
child 155888 f4d76e89a820d545d1c2849e57449ebccfbe4909
child 161542 15146c4fe36cec0e255273f298aecf2f35f4e984
push id34773
push usercbook@mozilla.com
push dateWed, 09 Oct 2013 10:27:17 +0000
treeherdermozilla-inbound@2c3bb1b8f2a1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
c22969eec61d / 27.0a1 / 20131009055629 / files
nightly linux64
c22969eec61d / 27.0a1 / 20131009055629 / files
nightly mac
c22969eec61d / 27.0a1 / 20131009055629 / files
nightly win32
c22969eec61d / 27.0a1 / 20131009055629 / files
nightly win64
c22969eec61d / 27.0a1 / 20131009055629 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central
browser/base/content/test/chrome/moz.build
browser/base/content/test/moz.build
browser/base/content/test/newtab/moz.build
browser/base/content/test/social/moz.build
browser/base/content/test/social/opengraph/Makefile.in
browser/base/content/test/social/opengraph/moz.build
browser/metro/base/tests/Makefile.in
browser/metro/base/tests/mochiperf/Makefile.in
browser/metro/base/tests/mochiperf/moz.build
browser/metro/base/tests/mochiperf/perfhelpers.js
browser/metro/base/tests/mochiperf/res/Makefile.in
browser/metro/base/tests/mochiperf/res/moz.build
browser/metro/base/tests/mochitest/Makefile.in
browser/metro/base/tests/mochitest/moz.build
browser/metro/base/tests/mochitest/res/Makefile.in
browser/metro/base/tests/mochitest/res/moz.build
browser/metro/base/tests/moz.build
mobile/android/app/mobile.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -319,17 +319,16 @@ pref("browser.safebrowsing.provider.0.ge
 // HTML report pages
 pref("browser.safebrowsing.provider.0.reportGenericURL", "http://{moz:locale}.phish-generic.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportErrorURL", "http://{moz:locale}.phish-error.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportPhishURL", "http://{moz:locale}.phish-report.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportMalwareURL", "http://{moz:locale}.malware-report.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportMalwareErrorURL", "http://{moz:locale}.malware-error.mozilla.com/?hl={moz:locale}");
 
 // FAQ URLs
-pref("browser.safebrowsing.warning.infoURL", "http://www.mozilla.com/%LOCALE%/%APP%/phishing-protection/");
 pref("browser.geolocation.warning.infoURL", "http://www.mozilla.com/%LOCALE%/%APP%/geolocation/");
 
 // Name of the about: page contributed by safebrowsing to handle display of error
 // pages on phishing/malware hits.  (bug 399233)
 pref("urlclassifier.alternate_error_page", "blocked");
 
 // The number of random entries to send with a gethash request.
 pref("urlclassifier.gethashnoise", 4);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -790,17 +790,16 @@ pref("browser.safebrowsing.keyURL", "htt
 pref("browser.safebrowsing.gethashURL", "http://safebrowsing.clients.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.safebrowsing.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/report?");
 pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
 
-pref("browser.safebrowsing.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/phishing-protection/");
 pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 // Since the application reputation query isn't hooked in anywhere yet, this
 // preference does not matter. To be extra safe, don't turn this preference on
 // for official builds without whitelisting (bug 842828).
 #ifndef MOZILLA_OFFICIAL
 pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download");
 #endif
 
--- a/browser/base/content/abouthome/aboutHome.xhtml
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -62,11 +62,11 @@
       <button class="launchButton" id="apps" hidden="true">&abouthome.appsButton.label;</button>
       <button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
       <button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
       <button class="launchButton" id="settings">&abouthome.settingsButton.label;</button>
       <div id="restorePreviousSessionSeparator"/>
       <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
     </div>
 
-    <a id="aboutMozilla" href="http://www.mozilla.org/about/"/>
+    <a id="aboutMozilla" href="https://www.mozilla.org/about/?utm_source=about-home&amp;utm_medium=Referral"/>
   </body>
 </html>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2399,21 +2399,17 @@ let BrowserOnClick = {
             let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
             reportURL += aOwnerDoc.location.href;
             content.location = reportURL;
           } catch (e) {
             Components.utils.reportError("Couldn't get malware report URL: " + e);
           }
         }
         else { // It's a phishing site, not malware
-          try {
-            content.location = formatURL("browser.safebrowsing.warning.infoURL", true);
-          } catch (e) {
-            Components.utils.reportError("Couldn't get phishing info URL: " + e);
-          }
+          openHelpLink("phishing-malware", false, "current");
         }
         break;
 
       case "ignoreWarningButton":
         secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
         this.ignoreWarningButton(isMalware);
         break;
     }
deleted file mode 100644
--- a/browser/base/content/test/chrome/moz.build
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
-
--- a/browser/base/content/test/general/moz.build
+++ b/browser/base/content/test/general/moz.build
@@ -1,10 +1,5 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-MOCHITEST_MANIFESTS += ['mochitest.ini']
-
-BROWSER_CHROME_MANIFESTS += ['browser.ini']
-
deleted file mode 100644
--- a/browser/base/content/test/moz.build
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DIRS += ['chrome', 'general', 'newtab', 'social']
-
deleted file mode 100644
--- a/browser/base/content/test/newtab/moz.build
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-BROWSER_CHROME_MANIFESTS += ['browser.ini']
-
--- a/browser/base/content/test/social/browser.ini
+++ b/browser/base/content/test/social/browser.ini
@@ -1,13 +1,18 @@
 [DEFAULT]
 support-files =
   blocklist.xml
   checked.jpg
   head.js
+  opengraph/og_invalid_url.html
+  opengraph/opengraph.html
+  opengraph/shortlink_linkrel.html
+  opengraph/shorturl_link.html
+  opengraph/shorturl_linkrel.html
   share.html
   social_activate.html
   social_activate_iframe.html
   social_chat.html
   social_flyout.html
   social_mark.html
   social_panel.html
   social_sidebar.html
deleted file mode 100644
--- a/browser/base/content/test/social/moz.build
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DIRS += ['opengraph']
-
-BROWSER_CHROME_MANIFESTS += ['browser.ini']
-
deleted file mode 100644
--- a/browser/base/content/test/social/opengraph/Makefile.in
+++ /dev/null
@@ -1,11 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-MOCHITEST_BROWSER_FILES := \
-                 opengraph.html \
-                 og_invalid_url.html \
-                 shortlink_linkrel.html \
-                 shorturl_link.html \
-                 shorturl_linkrel.html \
-                 $(NULL)
deleted file mode 100644
--- a/browser/base/content/test/social/opengraph/moz.build
+++ /dev/null
@@ -1,4 +0,0 @@
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -651,23 +651,26 @@ function isValidFeed(aLink, aPrincipal, 
     catch(ex) {
     }
   }
 
   return null;
 }
 
 // aCalledFromModal is optional
-function openHelpLink(aHelpTopic, aCalledFromModal) {
+function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
   var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                       .getService(Components.interfaces.nsIURLFormatter)
                       .formatURLPref("app.support.baseURL");
   url += aHelpTopic;
 
-  var where = aCalledFromModal ? "window" : "tab";
+  var where = aWhere;
+  if (!aWhere)
+    where = aCalledFromModal ? "window" : "tab";
+
   openUILinkIn(url, where);
 }
 
 function openPrefsHelp() {
   // non-instant apply prefwindows are usually modal, so we can't open in the topmost window, 
   // since its probably behind the window.
   var instantApply = getBoolPref("browser.preferences.instantApply");
 
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -1,7 +1,23 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-TEST_DIRS += ['content/test']
+TEST_DIRS += [
+    'content/test/general',
+]
+
+MOCHITEST_MANIFESTS += [
+    'content/test/general/mochitest.ini',
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+    'content/test/chrome/chrome.ini',
+]
+
+BROWSER_CHROME_MANIFESTS += [
+    'content/test/general/browser.ini',
+    'content/test/newtab/browser.ini',
+    'content/test/social/browser.ini',
+]
--- a/browser/components/downloads/src/DownloadsCommon.jsm
+++ b/browser/components/downloads/src/DownloadsCommon.jsm
@@ -752,30 +752,33 @@ DownloadsDataCtor.prototype = {
     }
   },
 
   /**
    * Updates the given data item and sends related notifications.
    */
   _updateDataItemState: function (aDataItem)
   {
+    let oldState = aDataItem.state;
     let wasInProgress = aDataItem.inProgress;
     let wasDone = aDataItem.done;
 
     aDataItem.updateFromJSDownload();
 
     if (wasInProgress && !aDataItem.inProgress) {
       aDataItem.endTime = Date.now();
     }
 
-    for (let view of this._views) {
-      try {
-        view.getViewItem(aDataItem).onStateChange({});
-      } catch (ex) {
-        Cu.reportError(ex);
+    if (oldState != aDataItem.state) {
+      for (let view of this._views) {
+        try {
+          view.getViewItem(aDataItem).onStateChange(oldState);
+        } catch (ex) {
+          Cu.reportError(ex);
+        }
       }
     }
 
     if (!aDataItem.newDownloadNotified) {
       aDataItem.newDownloadNotified = true;
       this._notifyDownloadEvent("start");
     }
 
--- a/browser/components/places/content/placesOverlay.xul
+++ b/browser/components/places/content/placesOverlay.xul
@@ -192,28 +192,28 @@
               label="&cmd.deleteDomainData.label;"
               accesskey="&cmd.deleteDomainData.accesskey;"
               closemenu="single"
               selection="link|host"
               selectiontype="single"
               hideifprivatebrowsing="true"
               forcehideselection="bookmark"/>
     <menuseparator id="placesContext_deleteSeparator"/>
+    <menuitem id="placesContext_sortBy:name"
+              command="placesCmd_sortBy:name"
+              label="&cmd.sortby_name.label;"
+              accesskey="&cmd.context_sortby_name.accesskey;"
+              closemenu="single"
+              selection="folder"/>
     <menuitem id="placesContext_reload"
               command="placesCmd_reload"
               label="&cmd.reloadLivebookmark.label;"
               accesskey="&cmd.reloadLivebookmark.accesskey;"
               closemenu="single"
               selection="livemark/feedURI"/>
-    <menuitem id="placesContext_sortBy:name"
-              command="placesCmd_sortBy:name"
-              label="&cmd.sortby_name.label;"
-              accesskey="&cmd.context_sortby_name.accesskey;"
-              closemenu="single"
-              selection="folder"/>
     <menuseparator id="placesContext_sortSeparator"/>
     <menuitem id="placesContext_show:info"
               command="placesCmd_show:info"
               label="&cmd.properties.label;" 
               accesskey="&cmd.properties.accesskey;"
               selection="bookmark|folder|query"
               forcehideselection="livemarkChild"/>
   </menupopup>
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -179,17 +179,17 @@ this.SessionStore = {
   getTabState: function ss_getTabState(aTab) {
     return SessionStoreInternal.getTabState(aTab);
   },
 
   setTabState: function ss_setTabState(aTab, aState) {
     SessionStoreInternal.setTabState(aTab, aState);
   },
 
-  duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta) {
+  duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta = 0) {
     return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
   },
 
   getNumberOfTabsClosedLast: function ss_getNumberOfTabsClosedLast(aWindow) {
     return SessionStoreInternal.getNumberOfTabsClosedLast(aWindow);
   },
 
   setNumberOfTabsClosedLast: function ss_setNumberOfTabsClosedLast(aWindow, aNumber) {
@@ -1451,17 +1451,17 @@ let SessionStoreInternal = {
       this._resetTabRestoringState(aTab);
     }
 
     TabStateCache.delete(aTab);
     this._setWindowStateBusy(window);
     this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
   },
 
-  duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta) {
+  duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
         !aWindow.getBrowser)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     // Duplicate the tab state
     let tabState = TabState.clone(aTab);
 
     tabState.index += aDelta;
--- a/browser/metro/base/content/ContentAreaObserver.js
+++ b/browser/metro/base/content/ContentAreaObserver.js
@@ -227,19 +227,21 @@ var ContentAreaObserver = {
 
     if (!aNewState) {
       this._shiftBrowserDeck(0);
       return;
     }
 
     // Request info about the target form element to see if we
     // need to reposition the browser above the keyboard.
-    Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:RepositionInfoRequest", {
-      viewHeight: this.viewableHeight,
-    });
+    if (SelectionHelperUI.layerMode === 2 /*kContentLayer*/) {
+      Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:RepositionInfoRequest", {
+        viewHeight: this.viewableHeight,
+      });
+    }
   },
 
   _onRepositionResponse: function _onRepositionResponse(aJsonMsg) {
     if (!aJsonMsg.reposition || !this.isKeyboardOpened) {
       this._shiftBrowserDeck(0);
       return;
     }
     this._shiftBrowserDeck(aJsonMsg.raiseContent);
--- a/browser/metro/base/content/WebProgress.js
+++ b/browser/metro/base/content/WebProgress.js
@@ -6,18 +6,16 @@
 const kHeartbeatDuration = 1000;
 // Start and end progress screen css margins as percentages
 const kProgressMarginStart = 30;
 const kProgressMarginEnd = 70;
 
 const WebProgress = {
   get _identityBox() { return document.getElementById("identity-box"); },
 
-  _progressActive: false,
-
   init: function init() {
     messageManager.addMessageListener("Content:StateChange", this);
     messageManager.addMessageListener("Content:LocationChange", this);
     messageManager.addMessageListener("Content:SecurityChange", this);
 
     Elements.progress.addEventListener("transitionend", this, true);
     Elements.tabList.addEventListener("TabSelect", this, true);
 
@@ -42,29 +40,29 @@ const WebProgress = {
 
         if (json.stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
           if (json.stateFlags & Ci.nsIWebProgressListener.STATE_START)
             this._networkStart(json, tab);
           else if (json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP)
             this._networkStop(json, tab);
         }
 
-        this._progressStep();
+        this._progressStep(tab);
         break;
       }
 
       case "Content:LocationChange": {
         this._locationChange(json, tab);
-        this._progressStep();
+        this._progressStep(tab);
         break;
       }
 
       case "Content:SecurityChange": {
         this._securityChange(json, tab);
-        this._progressStep();
+        this._progressStep(tab);
         break;
       }
     }
   },
 
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "transitionend":
@@ -148,67 +146,70 @@ const WebProgress = {
 
   _windowStop: function _windowStop(aJson, aTab) {
     this._progressStop(aJson, aTab);
   },
 
   _progressStart: function _progressStart(aJson, aTab) {
     // We will get multiple calls from _windowStart, so
     // only process once.
-    if (this._progressActive)
+    if (aTab._progressActive)
       return;
 
-    this._progressActive = true;
+    aTab._progressActive = true;
 
+    // 'Whoosh' in
+    aTab._progressCount = kProgressMarginStart;
+    this._showProgressBar(aTab);
+  },
+
+  _showProgressBar: function (aTab) {
     // display the track
     Elements.progressContainer.removeAttribute("collapsed");
-
-    // 'Whoosh' in
-    this._progressCount = kProgressMarginStart;
-    Elements.progress.style.width = this._progressCount + "%";
+    Elements.progress.style.width = aTab._progressCount + "%";
     Elements.progress.removeAttribute("fade");
 
     // Create a pulse timer to keep things moving even if we don't
     // collect any state changes.
     setTimeout(function() {
-      WebProgress._progressStepTimer();
+      WebProgress._progressStepTimer(aTab);
     }, kHeartbeatDuration, this);
   },
 
-  _stepProgressCount: function _stepProgressCount() {
+  _stepProgressCount: function _stepProgressCount(aTab) {
     // Step toward the end margin in smaller slices as we get closer
-    let left = kProgressMarginEnd - this._progressCount;
+    let left = kProgressMarginEnd - aTab._progressCount;
     let step = left * .05;
-    this._progressCount += Math.ceil(step);
+    aTab._progressCount += Math.ceil(step);
 
     // Don't go past the 'whoosh out' margin.
-    if (this._progressCount > kProgressMarginEnd) {
-      this._progressCount = kProgressMarginEnd;
+    if (aTab._progressCount > kProgressMarginEnd) {
+      aTab._progressCount = kProgressMarginEnd;
     }
   },
 
-  _progressStep: function _progressStep() {
-    if (!this._progressActive)
+  _progressStep: function _progressStep(aTab) {
+    if (!aTab._progressActive)
       return;
-    this._stepProgressCount();
-    Elements.progress.style.width = this._progressCount + "%";
+    this._stepProgressCount(aTab);
+    Elements.progress.style.width = aTab._progressCount + "%";
   },
 
-  _progressStepTimer: function _progressStepTimer() {
-    if (!this._progressActive)
+  _progressStepTimer: function _progressStepTimer(aTab) {
+    if (!aTab._progressActive)
       return;
-    this._progressStep();
+    this._progressStep(aTab);
 
     setTimeout(function() {
-      WebProgress._progressStepTimer();
+      WebProgress._progressStepTimer(aTab);
     }, kHeartbeatDuration, this);
   },
 
   _progressStop: function _progressStop(aJson, aTab) {
-    this._progressActive = false;
+    aTab._progressActive = false;
     // 'Whoosh out' and fade
     Elements.progress.style.width = "100%";
     Elements.progress.setAttribute("fade", true);
   },
 
   _progressTransEnd: function _progressTransEnd(aEvent) {
     if (!Elements.progress.hasAttribute("fade"))
       return;
@@ -217,14 +218,20 @@ const WebProgress = {
       Elements.progress.style.width = "0px";
       Elements.progressContainer.setAttribute("collapsed", true);
     }
   },
 
   _onTabSelect: function(aEvent) {
     let tab = Browser.getTabFromChrome(aEvent.originalTarget);
     this._identityBox.className = tab._identityState || "";
+    if (tab._progressActive) {
+      this._showProgressBar(tab);
+    } else {
+      Elements.progress.setAttribute("fade", true);
+      Elements.progressContainer.setAttribute("collapsed", true);
+    }
   },
 
   _onUrlBarInput: function(aEvent) {
     Browser.selectedTab._identityState = this._identityBox.className = "";
   },
 };
--- a/browser/metro/base/content/bindings/circularprogress.xml
+++ b/browser/metro/base/content/bindings/circularprogress.xml
@@ -4,16 +4,20 @@
    - 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/. -->
 
 <bindings xmlns="http://www.mozilla.org/xbl"
           xmlns:xbl="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:html="http://www.w3.org/1999/xhtml">
   <binding id="circular-progress-indicator">
+    <resources>
+      <stylesheet src="chrome://browser/skin/circularprogress.css"/>
+    </resources>
+
     <content>
       <xul:stack>
         <xul:toolbarbutton anonid="progressButton" class="circularprogressindicator-progressButton appbar-secondary"/>
         <html:div anonid="progressTrack" xbl:inherits="progress" class="circularprogressindicator-progressTrack"></html:div>
         <html:canvas anonid="progressRing" xbl:inherits="progress" class="circularprogressindicator-progressRing" width="40" height="40"></html:canvas>
       </xul:stack>
     </content>
     <implementation>
@@ -75,9 +79,9 @@
             this._progressCircleCtx.clearRect(0, 0,
               this._progressCanvas.width, this._progressCanvas.height);
             this.removeAttribute("progress");
           ]]>
         </body>
       </method>
     </implementation>
   </binding>
-</bindings>
\ No newline at end of file
+</bindings>
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -1055,33 +1055,57 @@ var BrowserUI = {
         break;
       case "cmd_closeTab":
         this.closeTab();
         break;
       case "cmd_undoCloseTab":
         this.undoCloseTab();
         break;
       case "cmd_sanitize":
-        SanitizeUI.onSanitize();
+        this.confirmSanitizeDialog();
         break;
       case "cmd_flyout_back":
         FlyoutPanelsUI.onBackButton();
         break;
       case "cmd_panel":
         PanelUI.toggle();
         break;
       case "cmd_openFile":
         this.openFile();
         break;
       case "cmd_savePage":
         this.savePage();
         break;
     }
   },
 
+  confirmSanitizeDialog: function () {
+    let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+    let title = bundle.GetStringFromName("clearPrivateData.title");
+    let message = bundle.GetStringFromName("clearPrivateData.message");
+    let clearbutton = bundle.GetStringFromName("clearPrivateData.clearButton");
+
+    let buttonPressed = Services.prompt.confirmEx(
+                          null,
+                          title,
+                          message,
+                          Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING +
+                          Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_CANCEL,
+                          clearbutton,
+                          null,
+                          null,
+                          null,
+                          { value: false });
+
+    // Clicking 'Clear' will call onSanitize().
+    if (buttonPressed === 0) {
+      SanitizeUI.onSanitize();
+    }
+  },
+
   crashReportingPrefChanged: function crashReportingPrefChanged(aState) {
     CrashReporter.submitReports = aState;
   }
 };
 
 var PanelUI = {
   get _panels() { return document.getElementById("panel-items"); },
 
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -672,23 +672,19 @@ var Browser = {
           reportURL += json.url;
           this.loadURI(reportURL);
         } catch (e) {
           Cu.reportError("Couldn't get malware report URL: " + e);
         }
         break;
       }
       case "report-phishing": {
-        // It's a phishing site, not malware
-        try {
-          let reportURL = formatter.formatURLPref("browser.safebrowsing.warning.infoURL");
-          this.loadURI(reportURL);
-        } catch (e) {
-          Cu.reportError("Couldn't get phishing info URL: " + e);
-        }
+        // It's a phishing site, just link to the generic information page
+        let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+        this.loadURI(url + "phishing-malware");
         break;
       }
     }
   },
 
   pinSite: function browser_pinSite() {
     // Get a path to our app tile
     var file = Components.classes["@mozilla.org/file/directory_service;1"].
@@ -1237,16 +1233,18 @@ function showDownloadManager(aWindowCont
   // TODO: Bug 883962: Toggle the downloads infobar as our current "download manager".
 }
 
 function Tab(aURI, aParams, aOwner) {
   this._id = null;
   this._browser = null;
   this._notification = null;
   this._loading = false;
+  this._progressActive = false;
+  this._progressCount = 0;
   this._chromeTab = null;
   this._eventDeferred = null;
   this._updateThumbnailTimeout = null;
 
   this.owner = aOwner || null;
 
   // Set to 0 since new tabs that have not been viewed yet are good tabs to
   // toss if app needs more memory.
--- a/browser/metro/base/content/contenthandlers/SelectionHandler.js
+++ b/browser/metro/base/content/contenthandlers/SelectionHandler.js
@@ -317,27 +317,16 @@ var SelectionHandler = {
   },
 
   /*
    * _repositionInfoRequest - fired at us by ContentAreaObserver when the
    * soft keyboard is being displayed. CAO wants to make a decision about
    * whether the browser deck needs repositioning.
    */
   _repositionInfoRequest: function _repositionInfoRequest(aJsonMsg) {
-    if (!this.isActive) {
-      Util.dumpLn("unexpected: repositionInfoRequest but selection isn't active.");
-      this.sendAsync("Content:RepositionInfoResponse", { reposition: false });
-      return;
-    }
-    
-    if (!this.targetIsEditable) {
-      Util.dumpLn("unexpected: repositionInfoRequest but targetIsEditable is false.");
-      this.sendAsync("Content:RepositionInfoResponse", { reposition: false });
-    }
-    
     let result = this._calcNewContentPosition(aJsonMsg.viewHeight);
 
     // no repositioning needed
     if (result == 0) {
       this.sendAsync("Content:RepositionInfoResponse", { reposition: false });
       return;
     }
 
@@ -417,19 +406,20 @@ var SelectionHandler = {
    * raised to move the focused form input out of the way of the soft
    * keyboard.
    *
    * @param aNewViewHeight the new content view height
    * @return 0 if no positioning is required or a positive val equal to the
    * distance content should be raised to center the target element.
    */
   _calcNewContentPosition: function _calcNewContentPosition(aNewViewHeight) {
-    // We don't support this on non-editable elements
-    if (!this._targetIsEditable) {
-      return 0;
+    // We have no target element but the keyboard is up
+    // so lets not cover content
+    if (!this._cache || !this._cache.element) {
+      return Services.metro.keyboardHeight;
     }
 
     let position = Util.centerElementInView(aNewViewHeight, this._cache.element);
     if (position !== undefined) {
       return position;
     }
 
     // Special case: we are dealing with an input that is taller than the
--- a/browser/metro/base/content/cursor.css
+++ b/browser/metro/base/content/cursor.css
@@ -1,19 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
 @namespace html url(http://www.w3.org/1999/xhtml);
 
-xul|*:-moz-system-metric(touch-enabled) {
-  cursor: none !important;
-}
-
-html|*:-moz-system-metric(touch-enabled) {
-  cursor: none !important;
-}
-
 /* For touch input, *all* select boxes use the SelectHelper popup. */
 select option, select optgroup {
   pointer-events: none;
 }
--- a/browser/metro/base/content/downloads.js
+++ b/browser/metro/base/content/downloads.js
@@ -9,16 +9,17 @@ const TOAST_URI_GENERIC_ICON_DOWNLOAD = 
 var MetroDownloadsView = {
   /**
    * _downloadCount keeps track of the number of downloads that a single
    * notification bar groups together. A download is grouped with other
    * downloads if it starts before other downloads have completed.
    */
   _downloadCount: 0,
   _downloadsInProgress: 0,
+  _lastDownload: null,
   _inited: false,
   _progressAlert: null,
   _lastSec: Infinity,
   _notificationBox: null,
   _progressNotification: null,
   _progressNotificationInfo: new Map(),
   _runDownloadBooleanMap: new Map(),
 
@@ -49,20 +50,18 @@ var MetroDownloadsView = {
       return;
 
     this._inited = true;
 
     Services.obs.addObserver(this, "dl-start", true);
     Services.obs.addObserver(this, "dl-done", true);
     Services.obs.addObserver(this, "dl-run", true);
     Services.obs.addObserver(this, "dl-failed", true);
-    Services.obs.addObserver(this, "dl-request", true);
 
     this._notificationBox = Browser.getNotificationBox();
-    this._notificationBox.addEventListener('AlertClose', this.handleEvent, true);
 
     this._progress = new DownloadProgressListener(this);
     this.manager.addListener(this._progress);
 
     this._downloadProgressIndicator = document.getElementById("download-progress");
 
     if (this.manager.activeDownloadCount) {
       setTimeout (this._restartWithActiveDownloads.bind(this), 0);
@@ -70,17 +69,16 @@ var MetroDownloadsView = {
   },
 
   uninit: function dh_uninit() {
     if (this._inited) {
       Services.obs.removeObserver(this, "dl-start");
       Services.obs.removeObserver(this, "dl-done");
       Services.obs.removeObserver(this, "dl-run");
       Services.obs.removeObserver(this, "dl-failed");
-      Services.obs.removeObserver(this, "dl-request");
     }
   },
 
   _restartWithActiveDownloads: function() {
     let activeDownloads = this.manager.activeDownloads;
 
     while (activeDownloads.hasMoreElements()) {
       let dl = activeDownloads.getNext();
@@ -88,17 +86,17 @@ var MetroDownloadsView = {
         case 0: // Downloading
         case 5: // Queued
           this.watchDownload(dl);
           this.updateInfobar();
           break;
       }
     }
     if (this.manager.activeDownloadCount) {
-      Services.obs.notifyObservers(null, "dl-request", "");
+      ContextUI.displayNavbar();
     }
   },
 
   openDownload: function dh_openDownload(aDownload) {
     let fileURI = aDownload.target
 
     if (!(fileURI && fileURI.spec)) {
       Util.dumpLn("Cant open download "+id+", fileURI is invalid");
@@ -228,102 +226,107 @@ var MetroDownloadsView = {
           MetroDownloadsView._downloadProgressIndicator.reset();
         }
       }
     ];
     this.showNotification("download-failed", message, buttons,
       this._notificationBox.PRIORITY_WARNING_HIGH);
   },
 
-  _showDownloadCompleteNotification: function (aDownload) {
+  _showDownloadCompleteNotification: function () {
     let message = "";
     let showInFilesButtonText = Strings.browser.GetStringFromName("downloadShowInFiles");
 
     let buttons = [
       {
         label: showInFilesButtonText,
         accessKey: "",
         callback: function() {
-          let fileURI = aDownload.target;
+          let fileURI = MetroDownloadsView._lastDownload.target;
           let file = MetroDownloadsView._getLocalFile(fileURI);
           file.reveal();
+          MetroDownloadsView._resetCompletedDownloads();
         }
       }
     ];
 
     if (this._downloadCount > 1) {
       message = PluralForm.get(this._downloadCount,
                                Strings.browser.GetStringFromName("alertMultipleDownloadsComplete"))
                                .replace("#1", this._downloadCount)
     } else {
       let runButtonText =
         Strings.browser.GetStringFromName("downloadRun");
       message = Strings.browser.formatStringFromName("alertDownloadsDone2",
-        [aDownload.displayName], 1);
+        [this._lastDownload.displayName], 1);
 
       buttons.unshift({
         isDefault: true,
         label: runButtonText,
         accessKey: "",
         callback: function() {
-          MetroDownloadsView.openDownload(aDownload);
+          MetroDownloadsView.openDownload(MetroDownloadsView._lastDownload);
+          MetroDownloadsView._resetCompletedDownloads();
         }
       });
     }
+    this._removeNotification("download-complete");
     this.showNotification("download-complete", message, buttons,
       this._notificationBox.PRIORITY_WARNING_MEDIUM);
   },
 
-  _showDownloadCompleteToast: function (aDownload) {
+  _showDownloadCompleteToast: function () {
     let name = "DownloadComplete";
     let msg = "";
     let title = "";
     let observer = null;
     if (this._downloadCount > 1) {
       title = PluralForm.get(this._downloadCount,
                              Strings.browser.GetStringFromName("alertMultipleDownloadsComplete"))
                              .replace("#1", this._downloadCount)
       msg = PluralForm.get(2, Strings.browser.GetStringFromName("downloadShowInFiles"));
 
       observer = {
         observe: function (aSubject, aTopic, aData) {
           switch (aTopic) {
             case "alertclickcallback":
-              let fileURI = aDownload.target;
+              let fileURI = MetroDownloadsView._lastDownload.target;
               let file = MetroDownloadsView._getLocalFile(fileURI);
               file.reveal();
-
-              let downloadCompleteNotification =
-                MetroDownloadsView._notificationBox.getNotificationWithValue("download-complete");
-              MetroDownloadsView._notificationBox.removeNotification(downloadCompleteNotification);
+              MetroDownloadsView._resetCompletedDownloads();
               break;
           }
         }
       }
     } else {
       title = Strings.browser.formatStringFromName("alertDownloadsDone",
-        [aDownload.displayName], 1);
+        [this._lastDownload.displayName], 1);
       msg = Strings.browser.GetStringFromName("downloadRunNow");
       observer = {
         observe: function (aSubject, aTopic, aData) {
           switch (aTopic) {
             case "alertclickcallback":
-              MetroDownloadsView.openDownload(aDownload);
-
-              let downloadCompleteNotification =
-                MetroDownloadsView._notificationBox.getNotificationWithValue("download-complete");
-              MetroDownloadsView._notificationBox.removeNotification(downloadCompleteNotification);
+              MetroDownloadsView.openDownload(MetroDownloadsView._lastDownload);
+              MetroDownloadsView._resetCompletedDownloads();
               break;
           }
         }
       }
     }
     this.showAlert(name, msg, title, null, observer);
   },
 
+  _resetCompletedDownloads: function () {
+    this._progressNotificationInfo.clear();
+    this._downloadCount = 0;
+    this._lastDownload = null;
+    this._downloadProgressIndicator.reset();
+    this._removeNotification("download-complete");
+  },
+
   _updateCircularProgressMeter: function dv_updateCircularProgressMeter() {
     if (!this._progressNotificationInfo) {
       return;
     }
 
     let totPercent = 0;
     for (let [guid, info] of this._progressNotificationInfo) {
       // info.download => nsIDownload
@@ -376,26 +379,36 @@ var MetroDownloadsView = {
       this._progressNotificationInfo.set(aDownload.guid, {});
     }
     let infoObj = this._progressNotificationInfo.get(aDownload.guid);
     infoObj.download = aDownload;
     this._progressNotificationInfo.set(aDownload.guid, infoObj);
   },
 
   onDownloadButton: function dv_onDownloadButton() {
-    if (this._progressNotification) {
-      let progressBar = this._notificationBox.getNotificationWithValue("download-progress");
-      if (progressBar) {
-        this._notificationBox.removeNotification(progressBar);
-      } else {
+    if (this._downloadsInProgress) {
+      if (!this._removeNotification("download-progress")) {
         this.updateInfobar();
       }
+    } else if (this._downloadCount) {
+      if (!this._removeNotification("download-complete")) {
+        this._showDownloadCompleteNotification();
+      }
     }
   },
 
+  _removeNotification: function (aValue) {
+    let notification = this._notificationBox.getNotificationWithValue(aValue);
+    if (!notification) {
+      return false;
+    }
+    this._notificationBox.removeNotification(notification);
+    return true;
+  },
+
   updateInfobar: function dv_updateInfobar() {
     let message = this._computeDownloadProgressString();
     this._updateCircularProgressMeter();
 
     if (this._progressNotification == null ||
         !this._notificationBox.getNotificationWithValue("download-progress")) {
       let cancelButtonText =
               Strings.browser.GetStringFromName("downloadCancel");
@@ -410,16 +423,18 @@ var MetroDownloadsView = {
             MetroDownloadsView._downloadProgressIndicator.reset();
           }
         }
       ];
 
       this._progressNotification =
         this.showNotification("download-progress", message, buttons,
         this._notificationBox.PRIORITY_WARNING_LOW);
+
+      ContextUI.displayNavbar();
     } else {
       this._progressNotification.label = message;
     }
   },
 
   updateDownload: function dv_updateDownload(aDownload) {
     if (this._progressNotification != null) {
       this._saveDownloadData(aDownload);
@@ -437,27 +452,16 @@ var MetroDownloadsView = {
       this._progressNotificationInfo.set(aDownload.guid, {});
     }
     if (!this._progressAlert) {
       this._progressAlert = new AlertDownloadProgressListener();
       this.manager.addListener(this._progressAlert);
     }
   },
 
-  handleEvent: function handleEvent(aEvent) {
-    switch (aEvent.type) {
-      case "AlertClose":
-        if (aEvent.notification.value == "download-complete" &&
-            !MetroDownloadsView._notificationBox.getNotificationWithValue("download-complete")) {
-          MetroDownloadsView._downloadProgressIndicator.reset();
-        }
-        break;
-    }
-  },
-
   observe: function (aSubject, aTopic, aData) {
     let message = "";
     let msgTitle = "";
 
     switch (aTopic) {
       case "dl-run":
         let file = aSubject.QueryInterface(Ci.nsIFile);
         this._runDownloadBooleanMap.set(file.path, (aData == 'true'));
@@ -465,42 +469,36 @@ var MetroDownloadsView = {
       case "dl-start":
         let download = aSubject.QueryInterface(Ci.nsIDownload);
         this.watchDownload(download);
         this.updateInfobar();
         break;
       case "dl-done":
         this._downloadsInProgress--;
         download = aSubject.QueryInterface(Ci.nsIDownload);
+        this._lastDownload = download;
         let runAfterDownload = this._runDownloadBooleanMap.get(download.targetFile.path);
         if (runAfterDownload) {
           this.openDownload(download);
         }
 
         this._runDownloadBooleanMap.delete(download.targetFile.path);
         if (this._downloadsInProgress == 0) {
           if (this._downloadCount > 1 || !runAfterDownload) {
-            this._showDownloadCompleteToast(download);
-            this._showDownloadCompleteNotification(download);
+            this._showDownloadCompleteToast();
+            this._showDownloadCompleteNotification();
           }
-          this._progressNotificationInfo.clear();
-          this._downloadCount = 0;
           this._notificationBox.removeNotification(this._progressNotification);
           this._progressNotification = null;
         }
         break;
       case "dl-failed":
         download = aSubject.QueryInterface(Ci.nsIDownload);
         this._showDownloadFailedNotification(download);
         break;
-      case "dl-request":
-        setTimeout(function() {
-          ContextUI.displayNavbar();
-        }, 1000);
-        break;
     }
   },
 
   QueryInterface: function (aIID) {
     if (!aIID.equals(Ci.nsIObserver) &&
         !aIID.equals(Ci.nsISupportsWeakReference) &&
         !aIID.equals(Ci.nsISupports))
       throw Components.results.NS_ERROR_NO_INTERFACE;
--- a/browser/metro/base/moz.build
+++ b/browser/metro/base/moz.build
@@ -1,6 +1,8 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+METRO_CHROME_MANIFESTS += ['tests/mochiperf/metro.ini', 'tests/mochitest/metro.ini']
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
deleted file mode 100644
--- a/browser/metro/base/tests/Makefile.in
+++ /dev/null
@@ -1,12 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# For now we're copying the actual Util code.
-# We should make this into a jsm module. See bug 848137
-XPCSHELL_RESOURCES = \
-  $(DEPTH)/browser/metro/base/content/Util.js \
-  $(NULL)
-
-libs:: $(XPCSHELL_RESOURCES)
-	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/xpcshell/$(relativesrcdir)/unit/
deleted file mode 100644
--- a/browser/metro/base/tests/mochiperf/Makefile.in
+++ /dev/null
@@ -1,17 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-ifndef MOZ_DEBUG
-MOCHITEST_METRO_FILES = \
-  ../mochitest/head.js \
-  perfhelpers.js \
-  browser_miscgfx_01.js \
-  browser_tabs_01.js \
-  browser_deck_01.js \
-  browser_msgmgr_01.js \
-  msgmanagerecho.js \
-  browser_layers_01.js \
-  browser_firstx.js \
-  $(NULL)
-endif
--- a/browser/metro/base/tests/mochiperf/browser_deck_01.js
+++ b/browser/metro/base/tests/mochiperf/browser_deck_01.js
@@ -1,16 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 function test() {
-  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-  Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   runTests();
 }
 
 gTests.push({
   desc: "deck offset",
   run: function run() {
     yield addTab("about:mozilla");
     yield hideContextUI();
--- a/browser/metro/base/tests/mochiperf/browser_firstx.js
+++ b/browser/metro/base/tests/mochiperf/browser_firstx.js
@@ -1,16 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 function test() {
-  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-  Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   runTests();
 }
 
 gTests.push({
   desc: "first x metrics",
   run: function run() {
     PerfTest.declareTest("5F2A456E-2BB2-4073-A751-936F222FEAE0",
                          "startup perf metrics", "browser", "ux",
--- a/browser/metro/base/tests/mochiperf/browser_layers_01.js
+++ b/browser/metro/base/tests/mochiperf/browser_layers_01.js
@@ -1,16 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 function test() {
-  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-  Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   runTests();
 }
 
 gTests.push({
   desc: "rotating divs",
   run: function run() {
     yield addTab(chromeRoot + "res/divs_test.html", true);
 
--- a/browser/metro/base/tests/mochiperf/browser_miscgfx_01.js
+++ b/browser/metro/base/tests/mochiperf/browser_miscgfx_01.js
@@ -1,16 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 function test() {
-  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-  Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   requestLongerTimeout(2);
   runTests();
 }
 
 function tapRadius() {
   let dpi = Util.displayDPI;
   return Services.prefs.getIntPref("ui.dragThresholdX") / 240 * dpi; // 27.999998728434246
 }
--- a/browser/metro/base/tests/mochiperf/browser_msgmgr_01.js
+++ b/browser/metro/base/tests/mochiperf/browser_msgmgr_01.js
@@ -1,16 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 function test() {
-  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-  Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   runTests();
 }
 
 var EchoServer = {
   _deferred: null,
   _stopwatch: null,
   _browser: null,
 
--- a/browser/metro/base/tests/mochiperf/browser_tabs_01.js
+++ b/browser/metro/base/tests/mochiperf/browser_tabs_01.js
@@ -1,16 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 function test() {
-  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-  Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   runTests();
 }
 
 function timeTab(aUrl) {
   return Task.spawn(function() {
     let stopwatch = new StopWatch(true);
     let tab = Browser.addTab(aUrl, true);
     yield tab.pageShowPromise;
rename from browser/metro/base/tests/mochiperf/perfhelpers.js
rename to browser/metro/base/tests/mochiperf/head.js
--- a/browser/metro/base/tests/mochiperf/perfhelpers.js
+++ b/browser/metro/base/tests/mochiperf/head.js
@@ -1,13 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+// Load common code from ../mochitest/head.js
+let mochitestDir = getRootDirectory(gTestPath).replace('/mochiperf', '/mochitest');
+Services.scriptloader.loadSubScript(mochitestDir + "head.js", this);
+
 // Misc. constants
 const kInfoHeader = "PERF-TEST | ";
 const kDeclareId = "DECLARE ";
 const kResultsId = "RESULTS ";
 
 // Mochitest log data format version
 const kDataSetVersion = "1";
 
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochiperf/metro.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+# Don't run performance tests in debug builds.
+skip-if = debug
+support-files =
+  head.js
+  msgmanagerecho.js
+  res/ripples.html
+  res/scroll_test.html
+  res/tidevideo.html
+  res/tide.mp4
+  res/divs_test.html
+  res/fx.png
+
+[browser_miscgfx_01.js]
+[browser_tabs_01.js]
+[browser_deck_01.js]
+[browser_msgmgr_01.js]
+[browser_layers_01.js]
+[browser_firstx.js]
deleted file mode 100644
--- a/browser/metro/base/tests/mochiperf/moz.build
+++ /dev/null
@@ -1,6 +0,0 @@
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-TEST_DIRS += ['res']
deleted file mode 100644
--- a/browser/metro/base/tests/mochiperf/res/Makefile.in
+++ /dev/null
@@ -1,14 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-ifndef MOZ_DEBUG
-MOCHITEST_METRO_FILES = \
-  ripples.html \
-  scroll_test.html \
-  tidevideo.html \
-  tide.mp4 \
-  divs_test.html \
-  fx.png \
-  $(NULL)
-endif
deleted file mode 100644
--- a/browser/metro/base/tests/mochiperf/res/moz.build
+++ /dev/null
@@ -1,4 +0,0 @@
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
deleted file mode 100644
--- a/browser/metro/base/tests/mochitest/Makefile.in
+++ /dev/null
@@ -1,63 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-MOCHITEST_METRO_FILES = \
-  head.js \
-  helpers/BookmarksHelper.js \
-  helpers/HistoryHelper.js \
-  helpers/ViewStateHelper.js \
-  browser_bookmarks.js \
-  browser_canonizeURL.js \
-  browser_circular_progress_indicator.js \
-  browser_crashprompt.js \
-  browser_context_menu_tests.js \
-  browser_context_menu_tests_01.html \
-  browser_context_menu_tests_02.html \
-  browser_context_menu_tests_03.html \
-  browser_context_menu_tests_04.html \
-  browser_context_ui.js \
-  browser_downloads.js \
-  browser_findbar.js \
-  browser_findbar.html \
-  browser_form_auto_complete.js \
-  browser_form_auto_complete.html \
-  browser_history.js \
-  browser_inputsource.js \
-  browser_onscreen_keyboard.js \
-  browser_onscreen_keyboard.html \
-  browser_prefs_ui.js \
-  browser_progress_indicator.xul \
-  browser_remotetabs.js \
-  browser_snappedState.js \
-  browser_tabs.js \
-  browser_test.js \
-  browser_tiles.js \
-  browser_tilegrid.xul \
-  browser_topsites.js \
-  browser_urlbar.js \
-  browser_urlbar_highlightURLs.js \
-  browser_urlbar_trimURLs.js \
-  $(NULL)
-
-ifndef MOZ_DEBUG
-MOCHITEST_METRO_FILES += \
-  browser_selection_basic.js \
-  browser_selection_basic.html \
-  browser_selection_textarea.js \
-  browser_selection_textarea.html \
-  browser_selection_frame_content.js \
-  browser_selection_frame_content.html \
-  browser_selection_inputs.js \
-  browser_selection_inputs.html \
-  browser_selection_frame_textarea.js \
-  browser_selection_frame_textarea.html \
-  browser_selection_frame_inputs.js \
-  browser_selection_frame_inputs.html \
-  browser_selection_urlbar.js \
-  browser_selection_contenteditable.js \
-  browser_selection_contenteditable.html \
-  browser_selection_caretfocus.js \
-  browser_selection_caretfocus.html \
-  $(NULL)
-endif
--- a/browser/metro/base/tests/mochitest/browser_prefs_ui.js
+++ b/browser/metro/base/tests/mochitest/browser_prefs_ui.js
@@ -1,46 +1,99 @@
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cc = Components.classes;
+
+const CONTRACT_ID = "@mozilla.org/xre/runtime;1";
+
+function MockAppInfo() {
+}
+
+MockAppInfo.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULRuntime]),
+}
+
+let newFactory = {
+  createInstance: function(aOuter, aIID) {
+    if (aOuter)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return new MockAppInfo().QueryInterface(aIID);
+  },
+  lockFactory: function(aLock) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
 var SanitizeHelper = {
   _originalSanitizer: null,
 
   MockSanitizer: {
     clearCalled: [],
     clearItem: function clearItem(aItemName) {
       info("Clear item called for: " + aItemName);
       this.clearCalled.push(aItemName);
     }
   },
 
   setUp: function setUp() {
     SanitizeHelper._originalSanitizer = SanitizeUI._sanitizer;
     SanitizeUI._sanitizer = SanitizeHelper.MockSanitizer;
+
+    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+    this.gAppInfoClassID = registrar.contractIDToCID(CONTRACT_ID);
+    this.gIncOldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+    registrar.unregisterFactory(this.gAppInfoClassID, this.gIncOldFactory);
+    let components = [MockAppInfo];
+    registrar.registerFactory(this.gAppInfoClassID, "", CONTRACT_ID, newFactory);
+    this.gIncOldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+
+    this.oldPrompt = Services.prompt;
   },
 
   tearDown: function tearDown() {
     SanitizeUI._sanitizer = SanitizeHelper._originalSanitizer;
+
+    if (this.gIncOldFactory) {
+      var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+      registrar.unregisterFactory(this.gAppInfoClassID, newFactory);
+      registrar.registerFactory(this.gAppInfoClassID, "", CONTRACT_ID, this.gIncOldFactory);
+    }
+    this.gIncOldFactory = null;
+
+    Services.prompt = this.oldPrompt;
   },
 };
 
 function getAllSelected() {
   return document.getElementById("prefs-privdata").querySelectorAll(
     "#prefs-privdata-history[checked], " +
       "#prefs-privdata-other[checked] + #prefs-privdata-subitems .privdata-subitem-item[checked]");
 }
 
 gTests.push({
+  setUp: SanitizeHelper.setUp,
   tearDown: SanitizeHelper.tearDown,
   desc: "Test sanitizer UI",
   run: function testSanitizeUI() {
+    // We want to be able to simulate that a specific button
+    // of the 'clear private data' prompt was pressed.
+    Services.prompt = {
+      confirmEx: function() {
+        return this.retVal;
+      }
+    };
+
     // Show options flyout
     let promise = waitForEvent(FlyoutPanelsUI.PrefsFlyoutPanel._topmostElement, "PopupChanged", 2000);
     FlyoutPanelsUI.show('PrefsFlyoutPanel');
     yield promise;
 
     // Make sure it's opened
     yield waitForEvent(FlyoutPanelsUI.PrefsFlyoutPanel._topmostElement, "transitionend", 1000);
 
@@ -67,18 +120,26 @@ gTests.push({
     // Select only downloads and passwords
     let callItems = ["downloads", "passwords"];
     for (let checkbox of allSelected) {
       if (callItems.indexOf(checkbox.getAttribute("itemName")) === -1) {
         checkbox.removeAttribute("checked");
       }
     }
 
+    // Simulate clicking "button 1", cancel.
+    Services.prompt.retVal = 1;
     let clearButton = document.getElementById("prefs-clear-data");
     clearButton.doCommand();
+    ok(SanitizeHelper.MockSanitizer.clearCalled.length == 0, "Nothing was cleared");
+
+    // We will simulate that "button 0" (which should be the clear button)
+    // was pressed
+    Services.prompt.retVal = 0;
+    clearButton.doCommand();
 
     let clearNotificationDeck = document.getElementById("clear-notification");
     let clearNotificationDone = document.getElementById("clear-notification-done");
 
     // Wait until command is done.
     yield waitForCondition(function (){
       return clearNotificationDeck.selectedPanel == clearNotificationDone;
     }, 1000);
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -29,19 +29,19 @@ if (!splitPath[splitPath.length-1]) {
 // ../mochitest to make sure we're looking for the libs on the right path
 // even for mochiperf tests.
 splitPath.pop();
 splitPath.push('mochitest');
 
 const mochitestPath = splitPath.join('/') + '/';
 
 [
-  "BookmarksHelper.js",
-  "HistoryHelper.js",
-  "ViewStateHelper.js"
+  "helpers/BookmarksHelper.js",
+  "helpers/HistoryHelper.js",
+  "helpers/ViewStateHelper.js"
 ].forEach(function(lib) {
   Services.scriptloader.loadSubScript(mochitestPath + lib, this);
 }, this);
 
 /*=============================================================================
   Metro ui helpers
 =============================================================================*/
 
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/metro.ini
@@ -0,0 +1,74 @@
+[DEFAULT]
+support-files =
+  browser_context_menu_tests_01.html
+  browser_context_menu_tests_02.html
+  browser_context_menu_tests_03.html
+  browser_context_menu_tests_04.html
+  browser_findbar.html
+  browser_form_auto_complete.html
+  browser_onscreen_keyboard.html
+  browser_progress_indicator.xul
+  browser_selection_basic.html
+  browser_selection_caretfocus.html
+  browser_selection_contenteditable.html
+  browser_selection_frame_content.html
+  browser_selection_frame_inputs.html
+  browser_selection_frame_textarea.html
+  browser_selection_inputs.html
+  browser_selection_textarea.html
+  browser_tilegrid.xul
+  head.js
+  helpers/BookmarksHelper.js
+  helpers/HistoryHelper.js
+  helpers/ViewStateHelper.js
+  res/image01.png
+  res/textblock01.html
+  res/textinput01.html
+  res/textarea01.html
+  res/testEngine.xml
+  res/blankpage1.html
+  res/blankpage2.html
+  res/blankpage3.html
+
+[browser_bookmarks.js]
+[browser_canonizeURL.js]
+[browser_circular_progress_indicator.js]
+[browser_crashprompt.js]
+[browser_context_menu_tests.js]
+[browser_context_ui.js]
+[browser_downloads.js]
+[browser_findbar.js]
+[browser_form_auto_complete.js]
+[browser_history.js]
+[browser_inputsource.js]
+[browser_onscreen_keyboard.js]
+[browser_prefs_ui.js]
+[browser_remotetabs.js]
+[browser_snappedState.js]
+[browser_tabs.js]
+[browser_test.js]
+[browser_tiles.js]
+[browser_topsites.js]
+[browser_urlbar.js]
+[browser_urlbar_highlightURLs.js]
+[browser_urlbar_trimURLs.js]
+
+# These tests have known failures in debug builds
+[browser_selection_basic.js]
+skip-if = debug
+[browser_selection_textarea.js]
+skip-if = debug
+[browser_selection_frame_content.js]
+skip-if = debug
+[browser_selection_inputs.js]
+skip-if = debug
+[browser_selection_frame_textarea.js]
+skip-if = debug
+[browser_selection_frame_inputs.js]
+skip-if = debug
+[browser_selection_urlbar.js]
+skip-if = debug
+[browser_selection_contenteditable.js]
+skip-if = debug
+[browser_selection_caretfocus.js]
+skip-if = debug
deleted file mode 100644
--- a/browser/metro/base/tests/mochitest/moz.build
+++ /dev/null
@@ -1,6 +0,0 @@
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-TEST_DIRS += ['res']
deleted file mode 100644
--- a/browser/metro/base/tests/mochitest/res/Makefile.in
+++ /dev/null
@@ -1,14 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-MOCHITEST_METRO_FILES = \
-  image01.png \
-  textblock01.html \
-  textinput01.html \
-  textarea01.html \
-  testEngine.xml \
-  blankpage1.html \
-  blankpage2.html \
-  blankpage3.html \
-  $(NULL)
deleted file mode 100644
--- a/browser/metro/base/tests/mochitest/res/moz.build
+++ /dev/null
@@ -1,4 +0,0 @@
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
deleted file mode 100644
--- a/browser/metro/base/tests/moz.build
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-TEST_DIRS += ['mochitest']
-TEST_DIRS += ['mochiperf']
-
-XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
--- a/browser/metro/base/tests/unit/test_util_extend.js
+++ b/browser/metro/base/tests/unit/test_util_extend.js
@@ -1,11 +1,12 @@
 "use strict";
 
-load("Util.js");
+Components.utils.import("resource:///modules/ContentUtil.jsm");
+let Util = ContentUtil;
 
 function run_test() {
   do_print("Testing Util.extend");
 
   do_print("Check if function is defined");
   do_check_true(!!Util.extend);
 
   do_print("No parameter or null should return a new object");
--- a/browser/metro/base/tests/unit/test_util_populateFragmentFromString.js
+++ b/browser/metro/base/tests/unit/test_util_populateFragmentFromString.js
@@ -1,19 +1,17 @@
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-let Cu = Components.utils;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource:///modules/ContentUtil.jsm");
-load("Util.js");
+Components.utils.import("resource:///modules/ContentUtil.jsm");
+let Util = ContentUtil;
 
 function empty_node(node) {
   let cnode;
   while((cnode = node.firstChild))
       node.removeChild(cnode);
   return node;
 }
 
@@ -29,18 +27,16 @@ function serializeContents(node) {
 }
 
 function run_test() {
   let doc, body, str, expectedResult, frag;
 
   do_print("Testing Util.populateFragmentFromString");
 
   do_check_true(!!Util.populateFragmentFromString);
-  do_check_true(!!ContentUtil.populateFragmentFromString);
-  do_check_eq(ContentUtil.populateFragmentFromString, Util.populateFragmentFromString);
 
   do_print("Loading blank document");
   doc = do_parse_document("blank.xhtml", "application/xhtml+xml");
 
   // sanity check
   do_check_eq(doc.nodeType, 9);
   do_check_true(doc.documentElement.localName != "parsererror");
 
@@ -82,9 +78,9 @@ function run_test() {
                                     frag, str,
                                     { text: "About the" }
                                   );
 
   empty_node(body);
   body.appendChild(frag);
   expectedResult = "<span>About the</span> &lt;body&gt; tag. &amp;copy; 2000 - Some Corp™"
   do_check_eq(serializeContents(body), expectedResult);
-}
\ No newline at end of file
+}
--- a/browser/metro/components/HelperAppDialog.js
+++ b/browser/metro/components/HelperAppDialog.js
@@ -60,17 +60,16 @@ HelperAppLauncherDialog.prototype = {
                             .rootTreeItem
                             .QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindow)
                             .QueryInterface(Ci.nsIDOMChromeWindow);
      return chromeWin;
   },
 
   _showDownloadInfobar: function do_showDownloadInfobar(aLauncher) {
-    Services.obs.notifyObservers(null, "dl-request", "");
     let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
 
     let runButtonText =
               browserBundle.GetStringFromName("downloadRun");
     let saveButtonText =
               browserBundle.GetStringFromName("downloadSave");
     let cancelButtonText =
               browserBundle.GetStringFromName("downloadCancel");
--- a/browser/metro/locales/en-US/chrome/browser.properties
+++ b/browser/metro/locales/en-US/chrome/browser.properties
@@ -31,16 +31,21 @@ contextAppbar2.delete=Delete
 
 # LOCALIZATION NOTE (contextAppbar2.restore): Undoes a previous deletion.
 # Button with this label only appears immediately after a deletion.
 contextAppbar2.restore=Undo delete
 
 # LOCALIZATION NOTE (contextAppbar2.clear): Unselects pages without modification.
 contextAppbar2.clear=Clear selection
 
+# Clear private data
+clearPrivateData.clearButton=Clear
+clearPrivateData.title=Clear Private Data
+clearPrivateData.message=Clear your private data?
+
 # Settings Charms
 aboutCharm1=About
 optionsCharm=Options
 helpOnlineCharm=Help (online)
 
 # General
 # LOCALIZATION NOTE (browserForSaveLocation): Title for the "Save..." file picker dialog
 browserForSaveLocation=Save Location
--- a/browser/metro/moz.build
+++ b/browser/metro/moz.build
@@ -10,11 +10,8 @@ DIRS += [
     'modules',
     'theme',
     'profile',
     'locales',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['shell']
-
-TEST_DIRS += ['base/tests']
-
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -608,17 +608,16 @@ pref("browser.safebrowsing.provider.0.ge
 // HTML report pages
 pref("browser.safebrowsing.provider.0.reportGenericURL", "http://{moz:locale}.phish-generic.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportErrorURL", "http://{moz:locale}.phish-error.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportPhishURL", "http://{moz:locale}.phish-report.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportMalwareURL", "http://{moz:locale}.malware-report.mozilla.com/?hl={moz:locale}");
 pref("browser.safebrowsing.provider.0.reportMalwareErrorURL", "http://{moz:locale}.malware-error.mozilla.com/?hl={moz:locale}");
 
 // FAQ URLs
-pref("browser.safebrowsing.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/phishing-protection/");
 pref("browser.geolocation.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/geolocation/");
 
 // Name of the about: page contributed by safebrowsing to handle display of error
 // pages on phishing/malware hits.  (bug 399233)
 pref("urlclassifier.alternate_error_page", "blocked");
 
 // The number of random entries to send with a gethash request.
 pref("urlclassifier.gethashnoise", 4);
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -443,38 +443,16 @@ documenttab[selected] .documenttab-selec
  * toolbar portion of the navbar. */
 #navbar {
   visibility: visible;
 }
 #navbar:not([hiding]):not([visible]) > #toolbar-overlay {
   visibility: hidden;
 }
 
-.circularprogressindicator-progressRing {
-  visibility: visible;
-  margin: 0 @toolbar_horizontal_spacing@;
-  pointer-events:none;
-  position: absolute;
-}
-.circularprogressindicator-progressTrack {
-  visibility: visible;
-  margin: 0 @toolbar_horizontal_spacing@;
-  pointer-events: none;
-  position: absolute;
-  width: 40px;
-  height: 40px;
-  background-repeat: no-repeat;
-  background-size: 40px 40px;
-  background-image: url(chrome://browser/skin/images/progresscircle-bg.png);
-}
-.circularprogressindicator-progressRing:not([progress]),
-.circularprogressindicator-progressTrack:not([progress]) {
-  visibility: hidden;
-}
-
 /* Progress meter ---------------------------------------------------------- */
 
 #progress-container {
   display: block;
   position: absolute;
   top: -@progress_height@;
   height: @progress_height@;
   width: 100%;
@@ -760,24 +738,27 @@ documenttab[selected] .documenttab-selec
   visibility: hidden;
   pointer-events: none;
 }
 
 #toolbar[viewstate="snapped"] #toolbar-contextual {
   visibility: collapse;
 }
 
-.circularprogressindicator-progressButton {
-  margin: 0 @toolbar_horizontal_spacing@;
+#download-progress:not([progress]) {
+  visibility: collapse;
+}
+
+#download-progress {
   -moz-image-region: rect(0px, 40px, 40px, 0px) !important;
 }
-.circularprogressindicator-progressButton:hover {
+#download-progress:hover {
   -moz-image-region: rect(40px, 40px, 80px, 0px) !important;
 }
-.circularprogressindicator-progressButton:active {
+#download-progress:active {
   -moz-image-region: rect(80px, 40px, 120px, 0px) !important;
 }
 
 #pin-button {
   list-style-image: url(chrome://browser/skin/images/navbar-pin.png);
 }
 
 #star-button {
new file mode 100644
--- /dev/null
+++ b/browser/metro/theme/circularprogress.css
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%filter substitution
+%include defines.inc
+
+.circularprogressindicator-progressButton,
+.circularprogressindicator-progressRing,
+.circularprogressindicator-progressTrack {
+  margin: 0 @toolbar_horizontal_spacing@;
+}
+
+.circularprogressindicator-progressRing,
+.circularprogressindicator-progressTrack {
+  pointer-events:none;
+  position: absolute;
+}
+
+.circularprogressindicator-progressTrack {
+  width: 40px;
+  height: 40px;
+  background-repeat: no-repeat;
+  background-size: 40px 40px;
+  background-image: url(chrome://browser/skin/images/progresscircle-bg.png);
+}
+
+.circularprogressindicator-progressRing:not([progress]),
+.circularprogressindicator-progressTrack:not([progress]) {
+  visibility: hidden;
+}
--- a/browser/metro/theme/jar.mn
+++ b/browser/metro/theme/jar.mn
@@ -5,16 +5,17 @@
 
 
 chrome.jar:
 % skin browser classic/1.0 %skin/
   skin/aboutPage.css                        (aboutPage.css)
   skin/aboutAddons.css                      (aboutAddons.css)
   skin/about.css                            (about.css)
 * skin/flyoutpanel.css                      (flyoutpanel.css)
+* skin/circularprogress.css                 (circularprogress.css)
 * skin/cssthrobber.css                      (cssthrobber.css)
 * skin/browser.css                          (browser.css)
 * skin/content.css                          (content.css)
   skin/config.css                           (config.css)
 * skin/platform.css                         (platform.css)
 * skin/tiles.css                            (tiles.css)
   skin/touchcontrols.css                    (touchcontrols.css)
   skin/netError.css                         (netError.css)
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -583,17 +583,16 @@ pref("browser.safebrowsing.keyURL", "htt
 pref("browser.safebrowsing.gethashURL", "http://safebrowsing.clients.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.safebrowsing.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/report?");
 pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
 
-pref("browser.safebrowsing.warning.infoURL", "http://www.mozilla.com/%LOCALE%/firefox/phishing-protection/");
 pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 
 pref("browser.safebrowsing.id", @MOZ_APP_UA_NAME@);
 
 // Name of the about: page contributed by safebrowsing to handle display of error
 // pages on phishing/malware hits.  (bug 399233)
 pref("urlclassifier.alternate_error_page", "blocked");
 
--- a/mobile/android/base/sync/CryptoRecord.java
+++ b/mobile/android/base/sync/CryptoRecord.java
@@ -205,17 +205,17 @@ public class CryptoRecord extends Record
     if (this.keyBundle == null) {
       throw new NoKeyBundleException();
     }
     String cleartext = payload.toJSONString();
     byte[] cleartextBytes = cleartext.getBytes("UTF-8");
     CryptoInfo info = CryptoInfo.encrypt(cleartextBytes, keyBundle);
     String message = new String(Base64.encodeBase64(info.getMessage()));
     String iv      = new String(Base64.encodeBase64(info.getIV()));
-    String hmac    = Utils.byte2hex(info.getHMAC());
+    String hmac    = Utils.byte2Hex(info.getHMAC());
     ExtendedJSONObject ciphertext = new ExtendedJSONObject();
     ciphertext.put(KEY_CIPHERTEXT, message);
     ciphertext.put(KEY_HMAC, hmac);
     ciphertext.put(KEY_IV, iv);
     this.payload = ciphertext;
     return this;
   }
 
--- a/mobile/android/base/sync/Utils.java
+++ b/mobile/android/base/sync/Utils.java
@@ -77,36 +77,30 @@ public class Utils {
    */
   public static void reseedSharedRandom() {
     sharedSecureRandom.setSeed(sharedSecureRandom.generateSeed(8));
   }
 
   /**
    * Helper to convert a byte array to a hex-encoded string
    */
-  public static String byte2hex(byte[] b) {
-    // StringBuffer should be used instead.
-    String hs = "";
+  public static String byte2Hex(final byte[] b) {
+    final StringBuilder hs = new StringBuilder(b.length * 2);
     String stmp;
 
     for (int n = 0; n < b.length; n++) {
-      stmp = java.lang.Integer.toHexString(b[n] & 0XFF);
+      stmp = Integer.toHexString(b[n] & 0XFF);
 
       if (stmp.length() == 1) {
-        hs = hs + "0" + stmp;
-      } else {
-        hs = hs + stmp;
+        hs.append("0");
       }
-
-      if (n < b.length - 1) {
-        hs = hs + "";
-      }
+      hs.append(stmp);
     }
 
-    return hs;
+    return hs.toString();
   }
 
   public static byte[] concatAll(byte[] first, byte[]... rest) {
     int totalLength = first.length;
     for (byte[] array : rest) {
       totalLength += array.length;
     }
 
@@ -531,9 +525,9 @@ public class Utils {
   }
 
   /**
    * Replace "foo@bar.com" with "XXX@XXX.XXX".
    */
   public static String obfuscateEmail(final String in) {
     return in.replaceAll("[^@\\.]", "X");
   }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/sync/jpake/JPakeClient.java
+++ b/mobile/android/base/sync/jpake/JPakeClient.java
@@ -303,17 +303,17 @@ public class JPakeClient {
     ExtendedJSONObject payload = new ExtendedJSONObject();
 
     String message64 = new String(Base64.encodeBase64(encrypted.getMessage()));
     String iv64 = new String(Base64.encodeBase64(encrypted.getIV()));
 
     payload.put(Constants.JSON_KEY_CIPHERTEXT, message64);
     payload.put(Constants.JSON_KEY_IV, iv64);
     if (makeHmac) {
-      String hmacHex = Utils.byte2hex(encrypted.getHMAC());
+      String hmacHex = Utils.byte2Hex(encrypted.getHMAC());
       payload.put(Constants.JSON_KEY_HMAC, hmacHex);
     }
     return payload;
   }
 
   /*
    * Helper for turning a JSON object into a payload.
    *
--- a/mobile/android/base/util/ThreadUtils.java
+++ b/mobile/android/base/util/ThreadUtils.java
@@ -23,17 +23,24 @@ public final class ThreadUtils {
     // function call of the getter was harming performance. (Bug 897123))
     // Once Bug 709230 is resolved we should reconsider this as ProGuard should be able to optimise
     // this out at compile time.
     public static Handler sGeckoHandler;
     public static MessageQueue sGeckoQueue;
     public static Thread sGeckoThread;
 
     // Delayed Runnable that resets the Gecko thread priority.
-    private static volatile Runnable sPriorityResetRunnable;
+    private static final Runnable sPriorityResetRunnable = new Runnable() {
+        @Override
+        public void run() {
+            resetGeckoPriority();
+        }
+    };
+
+    private static boolean sIsGeckoPriorityReduced;
 
     @SuppressWarnings("serial")
     public static class UiThreadBlockedException extends RuntimeException {
         public UiThreadBlockedException() {
             super();
         }
 
         public UiThreadBlockedException(String msg) {
@@ -136,31 +143,27 @@ public final class ThreadUtils {
      * (such as those related to the UI and database) to take precedence.
      *
      * Note that there are no guards in place to prevent multiple calls
      * to this method from conflicting with each other.
      *
      * @param timeout Timeout in ms after which the priority will be reset
      */
     public static void reduceGeckoPriority(long timeout) {
-        sGeckoThread.setPriority(Thread.MIN_PRIORITY);
-        sPriorityResetRunnable = new Runnable() {
-            @Override
-            public void run() {
-                resetGeckoPriority();
-            }
-        };
-        getUiHandler().postDelayed(sPriorityResetRunnable, timeout);
+        if (!sIsGeckoPriorityReduced) {
+            sIsGeckoPriorityReduced = true;
+            sGeckoThread.setPriority(Thread.MIN_PRIORITY);
+            getUiHandler().postDelayed(sPriorityResetRunnable, timeout);
+        }
     }
 
     /**
      * Resets the priority of a thread whose priority has been reduced
      * by reduceGeckoPriority.
      */
     public static void resetGeckoPriority() {
-        if (sPriorityResetRunnable != null) {
+        if (sIsGeckoPriorityReduced) {
+            sIsGeckoPriorityReduced = false;
             sGeckoThread.setPriority(Thread.NORM_PRIORITY);
-
             getUiHandler().removeCallbacks(sPriorityResetRunnable);
-            sPriorityResetRunnable = null;
         }
     }
 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4957,23 +4957,19 @@ var ErrorPageEventHandler = {
               try {
                 let reportURL = formatter.formatURLPref("browser.safebrowsing.malware.reportURL");
                 reportURL += errorDoc.location.href;
                 BrowserApp.selectedBrowser.loadURI(reportURL);
               } catch (e) {
                 Cu.reportError("Couldn't get malware report URL: " + e);
               }
             } else {
-              // It's a phishing site, not malware. (There's no report URL)
-              try {
-                let reportURL = formatter.formatURLPref("browser.safebrowsing.warning.infoURL");
-                BrowserApp.selectedBrowser.loadURI(reportURL);
-              } catch (e) {
-                Cu.reportError("Couldn't get phishing info URL: " + e);
-              }
+              // It's a phishing site, just link to the generic information page
+              let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+              BrowserApp.selectedBrowser.loadURI(url + "phishing-malware");
             }
           } else if (target == errorDoc.getElementById("ignoreWarningButton")) {
             Telemetry.addData("SECURITY_UI", nsISecTel[bucketName + "IGNORE_WARNING"]);
 
             // Allow users to override and continue through to the site,
             let webNav = BrowserApp.selectedBrowser.docShell.QueryInterface(Ci.nsIWebNavigation);
             let location = BrowserApp.selectedBrowser.contentWindow.location;
             webNav.loadURI(location, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, null, null, null);
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -223,16 +223,17 @@ class TreeMetadataEmitter(LoggingMixin):
         #     manifest.
         #
         # We ideally don't filter out inactive tests. However, not every test
         # harness can yet deal with test filtering. Once all harnesses can do
         # this, this feature can be dropped.
         test_manifests = dict(
             A11Y=('a11y', 'testing/mochitest/a11y', True),
             BROWSER_CHROME=('browser-chrome', 'testing/mochitest/browser', True),
+            METRO_CHROME=('metro-chrome', 'testing/mochitest/metro', True),
             MOCHITEST=('mochitest', 'testing/mochitest/tests', True),
             MOCHITEST_CHROME=('chrome', 'testing/mochitest/chrome', True),
             WEBRTC_SIGNALLING_TEST=('steeplechase', 'steeplechase', True),
             XPCSHELL_TESTS=('xpcshell', 'xpcshell', False),
         )
 
         for prefix, info in test_manifests.items():
             for path in sandbox.get('%s_MANIFESTS' % prefix, []):
--- a/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
+++ b/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
@@ -472,16 +472,20 @@ VARIABLES = {
     'A11Y_MANIFESTS': (StrictOrderingOnAppendList, list, [],
         """List of manifest files defining a11y tests.
         """, None),
 
     'BROWSER_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list, [],
         """List of manifest files defining browser chrome tests.
         """, None),
 
+    'METRO_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list, [],
+        """List of manifest files defining metro browser chrome tests.
+        """, None),
+
     'MOCHITEST_MANIFESTS': (StrictOrderingOnAppendList, list, [],
         """List of manifest files defining mochitest tests.
         """, None),
 
     'MOCHITEST_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list, [],
         """List of manifest files defining mochitest chrome tests.
         """, None),
 
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/metro.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_metro.js]
--- a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build
@@ -1,8 +1,9 @@
 # Any copyright is dedicated to the Public Domain.
 # http://creativecommons.org/publicdomain/zero/1.0/
 
 A11Y_MANIFESTS += ['a11y.ini']
 BROWSER_CHROME_MANIFESTS += ['browser.ini']
+METRO_CHROME_MANIFESTS += ['metro.ini']
 MOCHITEST_MANIFESTS += ['mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini']
new file mode 100644
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -236,17 +236,17 @@ class TestEmitterBasic(unittest.TestCase
 
     def test_test_manifest_keys_extracted(self):
         """Ensure all metadata from test manifests is extracted."""
         reader = self.reader('test-manifest-keys-extracted')
 
         objs = [o for o in self.read_topsrcdir(reader)
                 if isinstance(o, TestManifest)]
 
-        self.assertEqual(len(objs), 5)
+        self.assertEqual(len(objs), 6)
 
         metadata = {
             'a11y.ini': {
                 'flavor': 'a11y',
                 'installs': {
                     'a11y.ini',
                     'test_a11y.js',
                     # From ** wildcard.
@@ -258,16 +258,23 @@ class TestEmitterBasic(unittest.TestCase
                 'flavor': 'browser-chrome',
                 'installs': {
                     'browser.ini',
                     'test_browser.js',
                     'support1',
                     'support2',
                 },
             },
+            'metro.ini': {
+                'flavor': 'metro-chrome',
+                'installs': {
+                    'metro.ini',
+                    'test_metro.js',
+                },
+            },
             'mochitest.ini': {
                 'flavor': 'mochitest',
                 'installs': {
                     'mochitest.ini',
                     'test_mochitest.js',
                 },
                 'external': {
                     'external1',
--- a/toolkit/components/viewsource/content/viewSource.js
+++ b/toolkit/components/viewsource/content/viewSource.js
@@ -258,23 +258,20 @@ function onClickContent(event) {
         // append the current url, and go there.
         try {
           let reportURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.malware.reportURL", true);
           reportURL += errorDoc.location.href.slice(12);
           openURL(reportURL);
         } catch (e) {
           Components.utils.reportError("Couldn't get malware report URL: " + e);
         }
-      } else { // It's a phishing site, not malware
-        try {
-          var infoURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.warning.infoURL", true);
-          openURL(infoURL);
-        } catch (e) {
-          Components.utils.reportError("Couldn't get phishing info URL: " + e);
-        }
+      } else {
+        // It's a phishing site, just link to the generic information page
+        let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+        openURL(url + "phishing-malware");
       }
     } else if (target == errorDoc.getElementById('ignoreWarningButton')) {
       // Allow users to override and continue through to the site
       gBrowser.loadURIWithFlags(content.location.href,
                                 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                                 null, null, null);
     }
   }
--- a/toolkit/webapps/WebappsInstaller.jsm
+++ b/toolkit/webapps/WebappsInstaller.jsm
@@ -303,16 +303,17 @@ const APP_DATA_DIR = OS.Constants.Path.w
  *
  * @param aData the data object provided to the install function
  */
 function WinNativeApp(aData) {
   NativeApp.call(this, aData);
 
   if (aData.isPackage) {
     this.size = aData.app.updateManifest.size / 1024;
+    this.isPackaged = true;
   }
 
   let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
 
   this.appNameAsFilename = this.appNameAsFilename.replace(filenameRE, "");
   if (this.appNameAsFilename == "") {
     this.appNameAsFilename = "webapp";
   }
@@ -505,16 +506,19 @@ WinNativeApp.prototype = {
     writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
 
     // ${UninstallDir}/uninstall.log
     let uninstallContent = 
       "File: \\webapp.ini\r\n" +
       "File: \\webapp.json\r\n" +
       "File: \\webapprt.old\r\n" +
       "File: \\chrome\\icons\\default\\default.ico";
+    if (this.isPackaged) {
+      uninstallContent += "\r\nFile: \\application.zip";
+    }
 
     writeToFile(OS.Path.join(this.tmpInstallDir, this.uninstallDir,
                              "uninstall.log"),
                 uninstallContent);
   },
 
   /**
    * Writes the keys to the system registry that are necessary for the app operation
--- a/webapprt/PaymentUIGlue.js
+++ b/webapprt/PaymentUIGlue.js
@@ -95,40 +95,47 @@ PaymentUI.prototype = {
                            null);
 
     // Store a reference to the window so that we can close it when the payment
     // succeeds or fails.
     payments[aRequestId] = { win: win, handled: false };
 
     // Inject paymentSuccess and paymentFailed methods into the document after
     // its loaded.
-    win.addEventListener("DOMWindowCreated", function() {
+    win.addEventListener("DOMContentLoaded", function onDOMContentLoaded() {
+      win.removeEventListener("DOMContentLoaded", onDOMContentLoaded);
+
       let browserElement = win.document.getElementById("content");
       browserElement.
         setAttribute("src", aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt);
 
-      browserElement.addEventListener("DOMWindowCreated", function() {
-        win.document.getElementById("content").contentDocument.defaultView
-           .wrappedJSObject.mozPaymentProvider = {
-             __exposedProps__: {
-               paymentSuccess: 'r',
-               paymentFailed: 'r'
-             },
-             paymentSuccess: paymentSuccess(aRequestId),
-             paymentFailed: paymentFailed(aRequestId)
-           };
-      }, true);
+      browserElement.addEventListener("DOMWindowCreated",
+        function onDOMWindowCreated() {
+          browserElement.removeEventListener("DOMWindowCreated",
+                                             onDOMWindowCreated);
+
+
+          win.document.getElementById("content").contentDocument.defaultView
+             .wrappedJSObject.mozPaymentProvider = {
+               __exposedProps__: {
+                 paymentSuccess: 'r',
+                 paymentFailed: 'r'
+               },
+               paymentSuccess: paymentSuccess(aRequestId),
+               paymentFailed: paymentFailed(aRequestId)
+             };
+        }, true);
     });
 
     let winObserver = function(aClosedWin, aTopic) {
       if (aTopic == "domwindowclosed") {
         // Fail the payment if the window is closed.
         if (aClosedWin == win) {
           Services.ww.unregisterNotification(winObserver);
-          if (!payments[aRequestId].handled) {
+          if (payments[aRequestId] && !payments[aRequestId].handled) {
             aErrorCb.onresult(aRequestId, "USER_CANCELLED");
           }
         }
       }
     }
 
     Services.ww.registerNotification(winObserver);
   },
--- a/webapprt/test/chrome/Makefile.in
+++ b/webapprt/test/chrome/Makefile.in
@@ -27,9 +27,14 @@ MOCHITEST_WEBAPPRT_CHROME_FILES = \
     geolocation-prompt-noperm.webapp \
     geolocation-prompt-noperm.webapp^headers^ \
     geolocation-prompt-perm.html \
     geolocation-prompt-noperm.html \
   browser_debugger.js \
     debugger.webapp \
     debugger.webapp^headers^ \
     debugger.html \
+  browser_mozpay.js \
+    mozpay.webapp \
+    mozpay.webapp^headers^ \
+    mozpay.html \
+    mozpay-success.html \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/browser_mozpay.js
@@ -0,0 +1,70 @@
+Cu.import("resource://gre/modules/Services.jsm");
+let { PaymentManager } = Cu.import("resource://gre/modules/Payment.jsm", {});
+
+function test() {
+  waitForExplicitFinish();
+
+  let providerWindow = null;
+  let providerUri = "https://example.com:443/webapprtChrome/webapprt/test/chrome/mozpay-success.html?req=";
+  let jwt = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJhdWQiOiAibW9j" +
+            "a3BheXByb3ZpZGVyLnBocGZvZ2FwcC5jb20iLCAiaXNzIjogIkVudGVyI" +
+            "HlvdSBhcHAga2V5IGhlcmUhIiwgInJlcXVlc3QiOiB7Im5hbWUiOiAiUG" +
+            "llY2Ugb2YgQ2FrZSIsICJwcmljZSI6ICIxMC41MCIsICJwcmljZVRpZXI" +
+            "iOiAxLCAicHJvZHVjdGRhdGEiOiAidHJhbnNhY3Rpb25faWQ9ODYiLCAi" +
+            "Y3VycmVuY3lDb2RlIjogIlVTRCIsICJkZXNjcmlwdGlvbiI6ICJWaXJ0d" +
+            "WFsIGNob2NvbGF0ZSBjYWtlIHRvIGZpbGwgeW91ciB2aXJ0dWFsIHR1bW" +
+            "15In0sICJleHAiOiAxMzUyMjMyNzkyLCAiaWF0IjogMTM1MjIyOTE5Miw" +
+            "gInR5cCI6ICJtb2NrL3BheW1lbnRzL2luYXBwL3YxIn0.QZxc62USCy4U" +
+            "IyKIC1TKelVhNklvk-Ou1l_daKntaFI";
+
+  PaymentManager.registeredProviders = {};
+  PaymentManager.registeredProviders["mock/payments/inapp/v1"] = {
+    name: "mockprovider",
+    description: "Mock Payment Provider",
+    uri: providerUri,
+    requestMethod: "GET"
+  };
+
+  let winObserver = function(win, topic) {
+    if (topic == "domwindowopened") {
+      win.addEventListener("load", function onLoadWindow() {
+        win.removeEventListener("load", onLoadWindow, false);
+
+        if (win.document.getElementById("content").getAttribute("src") ==
+            (providerUri + jwt)) {
+          ok(true, "Payment provider window shown.");
+          providerWindow = win;
+        }
+      }, false);
+    }
+  }
+
+  Services.ww.registerNotification(winObserver);
+
+  let mutObserver = null;
+
+  loadWebapp("mozpay.webapp", undefined, function onLoad() {
+    let msg = gAppBrowser.contentDocument.getElementById("msg");
+    mutObserver = new MutationObserver(function(mutations) {
+      if (msg.textContent == "Success.") {
+        ok(true, "Payment success.");
+      } else {
+        ok(false, "Payment success.");
+      }
+
+      if (providerWindow == null) {
+        ok(false, "Payment provider window shown.");
+      } else {
+        providerWindow.close();
+      }
+
+      finish();
+    });
+    mutObserver.observe(msg, { childList: true });
+  });
+
+  registerCleanupFunction(function() {
+    Services.ww.unregisterNotification(winObserver);
+    mutObserver.disconnect();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/mozpay-success.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <script>
+    mozPaymentProvider.paymentSuccess();
+    </script>
+    <p id="msg">Webapp waiting to pay...</p>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/mozpay.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <script>
+    // Payload
+    //  {
+    //    "aud": "mockpayprovider.phpfogapp.com",
+    //    "iss": "Enter you app key here!",
+    //    "request": {
+    //      "name": "Piece of Cake",
+    //      "price": "10.50",
+    //      "priceTier": 1,
+    //      "productdata": "transaction_id=86",
+    //      "currencyCode": "USD",
+    //      "description": "Virtual chocolate cake to fill your virtual tummy"
+    //    },
+    //    "exp": 1352232792,
+    //    "iat": 1352229192,
+    //    "typ": "mock/payments/inapp/v1"
+    //  }
+    var jwt = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJhdWQiOiAibW9j" +
+              "a3BheXByb3ZpZGVyLnBocGZvZ2FwcC5jb20iLCAiaXNzIjogIkVudGVyI" +
+              "HlvdSBhcHAga2V5IGhlcmUhIiwgInJlcXVlc3QiOiB7Im5hbWUiOiAiUG" +
+              "llY2Ugb2YgQ2FrZSIsICJwcmljZSI6ICIxMC41MCIsICJwcmljZVRpZXI" +
+              "iOiAxLCAicHJvZHVjdGRhdGEiOiAidHJhbnNhY3Rpb25faWQ9ODYiLCAi" +
+              "Y3VycmVuY3lDb2RlIjogIlVTRCIsICJkZXNjcmlwdGlvbiI6ICJWaXJ0d" +
+              "WFsIGNob2NvbGF0ZSBjYWtlIHRvIGZpbGwgeW91ciB2aXJ0dWFsIHR1bW" +
+              "15In0sICJleHAiOiAxMzUyMjMyNzkyLCAiaWF0IjogMTM1MjIyOTE5Miw" +
+              "gInR5cCI6ICJtb2NrL3BheW1lbnRzL2luYXBwL3YxIn0.QZxc62USCy4U" +
+              "IyKIC1TKelVhNklvk-Ou1l_daKntaFI";
+
+    var request = navigator.mozPay(jwt);
+    request.onsuccess = function onsuccess() {
+      document.getElementById("msg").textContent = "Success.";
+    };
+    request.onerror = function onerror() {
+      document.getElementById("msg").textContent = "Failure.";
+    };
+    </script>
+    <p id="msg">Webapp waiting to be paid...</p>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/mozpay.webapp
@@ -0,0 +1,5 @@
+{
+  "name": "mozPay Test Webapp",
+  "description": "A webapp that wants to be paid (and shall be paid).",
+  "launch_path": "/webapprtChrome/webapprt/test/chrome/mozpay.html"
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/mozpay.webapp^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/x-web-app-manifest+json