Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 02 May 2013 07:39:49 -0400
changeset 130615 ff9eed6225d7ae571c71d5b2301161e936c0b118
parent 130614 3519bc0b981ac86943560db32ce5df0784184d44 (current diff)
parent 130565 e474b6cfebce4bc92055737bc1331ad002d7d0c1 (diff)
child 130616 866fa5faea3f447f5f0ce02e21706bb420ee0c85
push id1579
push userphilringnalda@gmail.com
push dateSat, 04 May 2013 04:38:04 +0000
treeherderfx-team@a56432a42a41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
Merge m-c to inbound.
dom/mobilemessage/src/MmsMessage.cpp
dom/push/src/PushService.js
dom/push/src/PushService.manifest
js/src/tests/js1_5/extensions/regress-336409-2.js
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -7,16 +7,17 @@
 Cu.import('resource://gre/modules/ContactService.jsm');
 Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
 #ifdef MOZ_B2G_FM
 Cu.import('resource://gre/modules/DOMFMRadioParent.jsm');
 #endif
 Cu.import('resource://gre/modules/AlarmService.jsm');
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
+Cu.import('resource://gre/modules/PushService.jsm');
 Cu.import('resource://gre/modules/ObjectWrapper.jsm');
 Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
 Cu.import('resource://gre/modules/Keyboard.jsm');
 Cu.import('resource://gre/modules/ErrorPage.jsm');
 #ifdef MOZ_B2G_RIL
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -505,18 +505,16 @@
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
-@BINPATH@/components/PushService.js
-@BINPATH@/components/PushService.manifest
 
 @BINPATH@/components/nsDOMIdentity.js
 @BINPATH@/components/nsIDService.js
 @BINPATH@/components/Identity.manifest
 
 @BINPATH@/components/SystemMessageInternal.js
 @BINPATH@/components/SystemMessageManager.js
 @BINPATH@/components/SystemMessageManager.manifest
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ b/dom/bluetooth/BluetoothOppManager.cpp
@@ -528,17 +528,17 @@ BluetoothOppManager::CreateDeviceStorage
   nsresult rv = mimeSvc->GetTypeFromFile(aFile, mimeType);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("image/"))) {
     return new DeviceStorageFile(NS_LITERAL_STRING("pictures"), storagePath);
   } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("video/"))) {
-    return new DeviceStorageFile(NS_LITERAL_STRING("movies"), storagePath);
+    return new DeviceStorageFile(NS_LITERAL_STRING("videos"), storagePath);
   } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("audio/"))) {
     return new DeviceStorageFile(NS_LITERAL_STRING("music"), storagePath);
   } else {
     NS_WARNING("Couldn't recognize the mimetype of received file.");
     return nullptr;
   }
 }
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -171,16 +171,21 @@ parent:
                     int32_t focusChange);
 
     /**
      * Gets the DPI of the screen corresponding to this browser.
      */
     sync GetDPI() returns (float value);
 
     /**
+     * Gets the default scaling factor of the screen corresponding to this browser.
+     */
+    sync GetDefaultScale() returns (double value);
+
+    /**
      * Return native data of root widget
      */
     sync GetWidgetNativeData() returns (WindowsHandle value);
 
     SetCursor(uint32_t value);
     SetBackgroundColor(nscolor color);
 
     /**
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2165,16 +2165,27 @@ TabChild::GetDPI(float* aDPI)
     if (!mRemoteFrame) {
         return;
     }
 
     SendGetDPI(aDPI);
 }
 
 void
+TabChild::GetDefaultScale(double* aScale)
+{
+    *aScale = -1.0;
+    if (!mRemoteFrame) {
+        return;
+    }
+
+    SendGetDefaultScale(aScale);
+}
+
+void
 TabChild::NotifyPainted()
 {
     // Normally we only need to notify the content process once, but with BasicCompositor
     // we need to notify content every change so that it can compute an invalidation
     // region and send that to the widget.
     if (UseDirectCompositor() &&
         (!mNotified || mTextureFactoryIdentifier.mParentBackend == LAYERS_BASIC)) {
         mRemoteFrame->SendNotifyCompositorTransaction();
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -291,16 +291,17 @@ public:
     nsIWebNavigation* WebNavigation() { return mWebNav; }
 
     JSContext* GetJSContext() { return mCx; }
 
     nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
     /** Return the DPI of the widget this TabChild draws to. */
     void GetDPI(float* aDPI);
+    void GetDefaultScale(double *aScale);
 
     gfxSize GetZoom() { return mLastMetrics.mZoom; }
 
     ScreenOrientation GetOrientation() { return mOrientation; }
 
     void SetBackgroundColor(const nscolor& aColor);
 
     void NotifyPainted();
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -195,33 +195,34 @@ TabParent::TabParent(const TabContext& a
   , mIMECompositionEnding(false)
   , mIMECompositionStart(0)
   , mIMESeqno(0)
   , mEventCaptureDepth(0)
   , mRect(0, 0, 0, 0)
   , mDimensions(0, 0)
   , mOrientation(0)
   , mDPI(0)
+  , mDefaultScale(0)
   , mShown(false)
   , mUpdatedDimensions(false)
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
   , mAppPackageFileDescriptorSent(false)
 {
 }
 
 TabParent::~TabParent()
 {
 }
 
 void
 TabParent::SetOwnerElement(nsIDOMElement* aElement)
 {
   mFrameElement = aElement;
-  TryCacheDPI();
+  TryCacheDPIAndScale();
 }
 
 void
 TabParent::GetAppType(nsAString& aOut)
 {
   aOut.Truncate();
   nsCOMPtr<Element> elem = do_QueryInterface(mFrameElement);
   if (!elem) {
@@ -1038,25 +1039,36 @@ TabParent::RecvSetInputContext(const int
   observerService->NotifyObservers(nullptr, "ime-enabled-state-changed", state.get());
 
   return true;
 }
 
 bool
 TabParent::RecvGetDPI(float* aValue)
 {
-  TryCacheDPI();
+  TryCacheDPIAndScale();
 
   NS_ABORT_IF_FALSE(mDPI > 0,
                     "Must not ask for DPI before OwnerElement is received!");
   *aValue = mDPI;
   return true;
 }
 
 bool
+TabParent::RecvGetDefaultScale(double* aValue)
+{
+  TryCacheDPIAndScale();
+
+  NS_ABORT_IF_FALSE(mDefaultScale > 0,
+                    "Must not ask for scale before OwnerElement is received!");
+  *aValue = mDefaultScale;
+  return true;
+}
+
+bool
 TabParent::RecvGetWidgetNativeData(WindowsHandle* aValue)
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
   if (content) {
     nsIPresShell* shell = content->OwnerDoc()->GetShell();
     if (shell) {
       nsViewManager* vm = shell->GetViewManager();
       nsCOMPtr<nsIWidget> widget;
@@ -1365,17 +1377,17 @@ TabParent::AllowContentIME()
 already_AddRefed<nsFrameLoader>
 TabParent::GetFrameLoader() const
 {
   nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(mFrameElement);
   return frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr;
 }
 
 void
-TabParent::TryCacheDPI()
+TabParent::TryCacheDPIAndScale()
 {
   if (mDPI > 0) {
     return;
   }
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
 
   if (!widget && mFrameElement) {
@@ -1385,16 +1397,17 @@ TabParent::TryCacheDPI()
     mFrameElement->GetOwnerDocument(getter_AddRefs(ownerDoc));
 
     nsCOMPtr<nsIDocument> doc = do_QueryInterface(ownerDoc);
     widget = nsContentUtils::WidgetForDocument(doc);
   }
 
   if (widget) {
     mDPI = widget->GetDPI();
+    mDefaultScale = widget->GetDefaultScale();
   }
 }
 
 already_AddRefed<nsIWidget>
 TabParent::GetWidget() const
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
   if (!content)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -144,16 +144,17 @@ public:
                                      const nsString& aType,
                                      const nsString& aInputmode,
                                      const nsString& aActionHint,
                                      const int32_t& aCause,
                                      const int32_t& aFocusChange);
     virtual bool RecvSetCursor(const uint32_t& aValue);
     virtual bool RecvSetBackgroundColor(const nscolor& aValue);
     virtual bool RecvGetDPI(float* aValue);
+    virtual bool RecvGetDefaultScale(double* aValue);
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue);
     virtual bool RecvZoomToRect(const gfxRect& aRect);
     virtual bool RecvUpdateZoomConstraints(const bool& aAllowZoom,
                                            const float& aMinZoom,
                                            const float& aMaxZoom);
     virtual bool RecvContentReceivedTouch(const bool& aPreventDefault);
     virtual PContentDialogParent* AllocPContentDialog(const uint32_t& aType,
                                                       const nsCString& aName,
@@ -288,24 +289,25 @@ protected:
 
     // The number of event series we're currently capturing.
     int32_t mEventCaptureDepth;
 
     nsRect mRect;
     nsIntSize mDimensions;
     ScreenOrientation mOrientation;
     float mDPI;
+    double mDefaultScale;
     bool mShown;
     bool mUpdatedDimensions;
 
 private:
     already_AddRefed<nsFrameLoader> GetFrameLoader() const;
     already_AddRefed<nsIWidget> GetWidget() const;
     layout::RenderFrameParent* GetRenderFrame();
-    void TryCacheDPI();
+    void TryCacheDPIAndScale();
 
     // When true, we create a pan/zoom controller for our frame and
     // notify it of input events targeting us.
     bool UseAsyncPanZoom();
     // If we have a render frame currently, notify it that we're about
     // to dispatch |aEvent| to our child.  If there's a relevant
     // transform in place, |aOutEvent| is the transformed |aEvent| to
     // dispatch to content.
--- a/dom/mobilemessage/src/MmsMessage.cpp
+++ b/dom/mobilemessage/src/MmsMessage.cpp
@@ -372,21 +372,16 @@ MmsMessage::GetSender(nsAString& aSender
 {
   aSender = mSender;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MmsMessage::GetReceivers(JSContext* aCx, JS::Value* aReceivers)
 {
-  uint32_t length = mReceivers.Length();
-  if (length == 0) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
   JSObject* reveiversObj = nullptr;
   nsresult rv = nsTArrayToJSArray(aCx, mReceivers, &reveiversObj);
   NS_ENSURE_SUCCESS(rv, rv);
 
   aReceivers->setObject(*reveiversObj);
   return NS_OK;
 }
 
--- a/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
@@ -1004,23 +1004,31 @@ MobileMessageDatabaseService.prototype =
     let self = this.getRilIccInfoMsisdn();
     let threadParticipants = [aMessage.sender];
     if (aMessage.type == "sms") {
       // TODO Bug 853384 - for some SIMs we cannot retrieve the vaild
       // phone number, thus setting the SMS' receiver to be null.
       aMessage.receiver = self;
     } else if (aMessage.type == "mms") {
       let receivers = aMessage.receivers;
-      if (!receivers.length) {
-        // TODO Bug 853384 - we cannot expose empty receivers for
-        // an MMS message. Returns 'myself' when .msisdn isn't valid.
-        receivers.push(self ? self : "myself");
-      } else {
-        // TODO Bug 853384 - we cannot correcly exclude the phone number
-        // from the receivers, thus wrongly building the index.
+      // We need to add the receivers (excluding our own) into the participants
+      // of a thread. Some cases we might encounter here:
+      // 1. receivers.length == 0
+      //    This usually happens when receiving an MMS notification indication
+      //    which doesn't carry any receivers.
+      // 2. receivers.length == 1
+      //    If the receivers contain single phone number, we don't need to
+      //    add it into participants because we know that number is our own.
+      // 3. receivers.length >= 2
+      //    If the receivers contain multiple phone numbers, we need to add all
+      //    of them but not our own into participants.
+      if (receivers.length >= 2) {
+        // TODO Bug 853384 - for some SIM cards, the phone number might not be
+        // available, so we cannot correcly exclude our own from the receivers,
+        // thus wrongly building the thread index.
         let slicedReceivers = receivers.slice();
         if (self) {
           let found = slicedReceivers.indexOf(self);
           if (found !== -1) {
             slicedReceivers.splice(found, 1);
           }
         }
         threadParticipants = threadParticipants.concat(slicedReceivers);
--- a/dom/push/src/Makefile.in
+++ b/dom/push/src/Makefile.in
@@ -7,14 +7,16 @@ topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 EXTRA_COMPONENTS = \
   Push.js \
   Push.manifest \
-  PushService.js \
-  PushService.manifest \
+  $(NULL)
+
+EXTRA_JS_MODULES = \
+  PushService.jsm \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
rename from dom/push/src/PushService.js
rename to dom/push/src/PushService.jsm
--- a/dom/push/src/PushService.js
+++ b/dom/push/src/PushService.jsm
@@ -1,30 +1,32 @@
 /* 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";
 
 function debug(s) {
-  // dump("-*- PushService.js: " + s + "\n");
+  // dump("-*- PushService.jsm: " + s + "\n");
 }
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 
+this.EXPORTED_SYMBOLS = ["PushService"];
+
 const prefs = new Preferences("services.push.");
 
 const kPUSHDB_DB_NAME = "push";
 const kPUSHDB_DB_VERSION = 1; // Change this if the IndexedDB format changes
 const kPUSHDB_STORE_NAME = "push";
 const kCONFLICT_RETRY_ATTEMPTS = 3; // If channelID registration says 409, how
                                     // many times to retry with a new channelID
 
@@ -256,57 +258,35 @@ this.PushWebSocketListener.prototype = {
 
   onServerClose: function(context, aStatusCode, aReason) {
     if (!this._pushService)
         return;
     this._pushService._wsOnServerClose(context, aStatusCode, aReason);
   }
 }
 
-/**
- * The implementation of the SimplePush system. This runs in the B2G parent
- * process and is started on boot. It uses WebSockets to communicate with the
- * server and PushDB (IndexedDB) for persistence.
- */
-function PushService()
-{
-  debug("PushService Constructor.");
-}
-
 // websocket states
 // websocket is off
 const STATE_SHUT_DOWN = 0;
 // websocket has been opened on client side, waiting for successful open
 // (_wsOnStart)
 const STATE_WAITING_FOR_WS_START = 1;
 // websocket opened, hello sent, waiting for server reply (_handleHelloReply)
 const STATE_WAITING_FOR_HELLO = 2;
 // websocket operational, handshake completed, begin protocol messaging
 const STATE_READY = 3;
 
-PushService.prototype = {
-  classID : Components.ID("{0ACE8D15-9B15-41F4-992F-C88820421DBF}"),
-
-  QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
-                                          Ci.nsIUDPServerSocketListener]),
-
+/**
+ * The implementation of the SimplePush system. This runs in the B2G parent
+ * process and is started on boot. It uses WebSockets to communicate with the
+ * server and PushDB (IndexedDB) for persistence.
+ */
+this.PushService = {
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
-      case "app-startup":
-
-        if (!prefs.get("enabled"))
-          return;
-
-        Services.obs.addObserver(this, "final-ui-startup", false);
-        Services.obs.addObserver(this, "profile-change-teardown", false);
-        Services.obs.addObserver(this,
-                                 "network-interface-state-changed",
-                                 false);
-        Services.obs.addObserver(this, "webapps-uninstall", false);
-        break;
       case "final-ui-startup":
         Services.obs.removeObserver(this, "final-ui-startup");
         this.init();
         break;
       case "profile-change-teardown":
         Services.obs.removeObserver(this, "profile-change-teardown");
         this._shutdown();
         break;
@@ -378,16 +358,17 @@ PushService.prototype = {
               debug("Had a connection, so telling the server");
               this._request("unregister", {channelID: records[i].channelID});
             }
           }
         }.bind(this), function() {
           debug("Error in getAllByManifestURL: url " + app.manifestURL);
         });
 
+        break;
     }
   },
 
   get _UAID() {
     return prefs.get("userAgentID");
   },
 
   set _UAID(newID) {
@@ -437,16 +418,23 @@ PushService.prototype = {
    * shouldn't re-establish the connection. If the server says that it will
    * wake up the client over UDP, this is set to true in wsOnServerClose. It is
    * checked in wsOnStop.
    */
   _willBeWokenUpByUDP: false,
 
   init: function() {
     debug("init()");
+    if (!prefs.get("enabled"))
+        return null;
+
+    Services.obs.addObserver(this, "profile-change-teardown", false);
+    Services.obs.addObserver(this, "network-interface-state-changed",
+                             false);
+    Services.obs.addObserver(this, "webapps-uninstall", false);
     this._db = new PushDB(this);
 
     let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                  .getService(Ci.nsIMessageBroadcaster);
 
     kCHILD_PROCESS_MESSAGES.forEach(function addMessage(msgName) {
         ppmm.addMessageListener(msgName, this);
     }.bind(this));
@@ -482,28 +470,44 @@ PushService.prototype = {
     try {
         this._ws.close(0, null);
     } catch (e) {}
     this._ws = null;
   },
 
   _shutdown: function() {
     debug("_shutdown()");
-    this._db.close();
-    this._db = null;
+
+    Services.obs.removeObserver(this, "network-interface-state-changed",
+                                false);
+    Services.obs.removeObserver(this, "webapps-uninstall", false);
+
+    if (this._db) {
+      this._db.close();
+      this._db = null;
+    }
 
     if (this._udpServer) {
       this._udpServer.close();
     }
 
     // All pending requests (ideally none) are dropped at this point. We
     // shouldn't have any applications performing registration/unregistration
     // or receiving notifications.
     this._shutdownWS();
 
+    // At this point, profile-change-net-teardown has already fired, so the
+    // WebSocket has been closed with NS_ERROR_ABORT (if it was up) and will
+    // try to reconnect. Stop the timer.
+    if (this._retryTimeoutTimer)
+      this._retryTimeoutTimer.cancel();
+
+    if (this._requestTimeoutTimer)
+      this._requestTimeoutTimer.cancel();
+
     debug("shutdown complete!");
   },
 
   // aStatusCode is an NS error from Components.results
   _socketError: function(aStatusCode) {
     debug("socketError()");
 
     // Calculate new timeout, but cap it to
@@ -1168,16 +1172,17 @@ PushService.prototype = {
    * This statusCode is not the websocket protocol status code, but the TCP
    * connection close status code.
    *
    * If we do not explicitly call ws.close() then statusCode is always
    * NS_BASE_STREAM_CLOSED, even on a successful close.
    */
   _wsOnStop: function(context, statusCode) {
     debug("wsOnStop()");
+
     if (statusCode != Cr.NS_OK &&
         !(statusCode == Cr.NS_BASE_STREAM_CLOSED && this._willBeWokenUpByUDP)) {
       debug("Socket error " + statusCode);
       this._socketError(statusCode);
     }
 
     this._shutdownWS();
   },
@@ -1315,9 +1320,9 @@ PushService.prototype = {
     return {
       mcc: 0,
       mnc: 0,
       ip: undefined
     };
   }
 }
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PushService]);
+PushService.init();
deleted file mode 100644
--- a/dom/push/src/PushService.manifest
+++ /dev/null
@@ -1,4 +0,0 @@
-component {0ACE8D15-9B15-41F4-992F-C88820421DBF} PushService.js
-contract @mozilla.org/PushService;1 {0ACE8D15-9B15-41F4-992F-C88820421DBF}
-category app-startup PushService service,@mozilla.org/PushService;1
-
--- a/python/mozbuild/mozbuild/backend/base.py
+++ b/python/mozbuild/mozbuild/backend/base.py
@@ -66,18 +66,19 @@ class BackendConsumeSummary(object):
         return 'Backend executed in {:.2f}s'.format(self.backend_execution_time)
 
     def backend_detailed_summary(self):
         """Backend summary to be supplied by BuildBackend implementations."""
         return None
 
     @property
     def total_summary(self):
+        efficiency_value = self.cpu_time / self.wall_time if self.wall_time else 100
         return 'Total wall time: {:.2f}s; CPU time: {:.2f}s; Efficiency: {:.0%}'.format(
-            self.wall_time, self.cpu_time, self.cpu_time / self.wall_time)
+            self.wall_time, self.cpu_time, efficiency_value)
 
     def summaries(self):
         yield self.reader_summary
         yield self.backend_summary
 
         detailed = self.backend_detailed_summary()
         if detailed:
             yield detailed
--- a/services/healthreport/HealthReport.jsm
+++ b/services/healthreport/HealthReport.jsm
@@ -4,16 +4,17 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "HealthReporter",
   "AddonsProvider",
   "AppInfoProvider",
   "CrashesProvider",
+  "HealthReportProvider",
   "Metrics",
   "PlacesProvider",
   "ProfileMetadataProvider",
   "SearchesProvider",
   "SessionsProvider",
   "SysInfoProvider",
 ];
 
--- a/services/healthreport/HealthReportComponents.manifest
+++ b/services/healthreport/HealthReportComponents.manifest
@@ -1,12 +1,13 @@
 # Register Firefox Health Report providers.
 category healthreport-js-provider-default AddonsProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default AppInfoProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default CrashesProvider resource://gre/modules/HealthReport.jsm
+category healthreport-js-provider-default HealthReportProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default PlacesProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default ProfileMetadataProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default SearchesProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default SessionsProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default SysInfoProvider resource://gre/modules/HealthReport.jsm
 
 # No Aurora or Beta providers yet; use the categories
 # "healthreport-js-provider-aurora", "healthreport-js-provider-beta".
--- a/services/healthreport/healthreporter.jsm
+++ b/services/healthreport/healthreporter.jsm
@@ -42,17 +42,16 @@ const DEFAULT_DATABASE_NAME = "healthrep
 const TELEMETRY_INIT = "HEALTHREPORT_INIT_MS";
 const TELEMETRY_INIT_FIRSTRUN = "HEALTHREPORT_INIT_FIRSTRUN_MS";
 const TELEMETRY_DB_OPEN = "HEALTHREPORT_DB_OPEN_MS";
 const TELEMETRY_DB_OPEN_FIRSTRUN = "HEALTHREPORT_DB_OPEN_FIRSTRUN_MS";
 const TELEMETRY_GENERATE_PAYLOAD = "HEALTHREPORT_GENERATE_JSON_PAYLOAD_MS";
 const TELEMETRY_JSON_PAYLOAD_SERIALIZE = "HEALTHREPORT_JSON_PAYLOAD_SERIALIZE_MS";
 const TELEMETRY_PAYLOAD_SIZE_UNCOMPRESSED = "HEALTHREPORT_PAYLOAD_UNCOMPRESSED_BYTES";
 const TELEMETRY_PAYLOAD_SIZE_COMPRESSED = "HEALTHREPORT_PAYLOAD_COMPRESSED_BYTES";
-const TELEMETRY_SAVE_LAST_PAYLOAD = "HEALTHREPORT_SAVE_LAST_PAYLOAD_MS";
 const TELEMETRY_UPLOAD = "HEALTHREPORT_UPLOAD_MS";
 const TELEMETRY_SHUTDOWN_DELAY = "HEALTHREPORT_SHUTDOWN_DELAY_MS";
 const TELEMETRY_COLLECT_CONSTANT = "HEALTHREPORT_COLLECT_CONSTANT_DATA_MS";
 const TELEMETRY_COLLECT_DAILY = "HEALTHREPORT_COLLECT_DAILY_MS";
 const TELEMETRY_SHUTDOWN = "HEALTHREPORT_SHUTDOWN_MS";
 const TELEMETRY_COLLECT_CHECKPOINT = "HEALTHREPORT_POST_COLLECT_CHECKPOINT_MS";
 
 /**
@@ -98,19 +97,25 @@ function AbstractHealthReporter(branch, 
 
   // Yes, this will probably run concurrently with remaining constructor work.
   let hasFirstRun = this._prefs.get("service.firstRun", false);
   this._initHistogram = hasFirstRun ? TELEMETRY_INIT : TELEMETRY_INIT_FIRSTRUN;
   this._dbOpenHistogram = hasFirstRun ? TELEMETRY_DB_OPEN : TELEMETRY_DB_OPEN_FIRSTRUN;
 
   TelemetryStopwatch.start(this._initHistogram, this);
 
-  this._ensureDirectoryExists(this._stateDir)
-      .then(this._onStateDirCreated.bind(this),
-            this._onInitError.bind(this));
+  // As soon as we could have storage, we need to register cleanup or
+  // else bad things (like hangs) happen on shutdown.
+  Services.obs.addObserver(this, "quit-application", false);
+  Services.obs.addObserver(this, "profile-before-change", false);
+
+  this._storageInProgress = true;
+  TelemetryStopwatch.start(this._dbOpenHistogram, this);
+  Metrics.Storage(this._dbName).then(this._onStorageCreated.bind(this),
+                                     this._onInitError.bind(this));
 }
 
 AbstractHealthReporter.prototype = Object.freeze({
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   /**
    * Whether the service is fully initialized and running.
    *
@@ -136,28 +141,16 @@ AbstractHealthReporter.prototype = Objec
     this._initializeHadError = true;
     this._initiateShutdown();
     this._initializedDeferred.reject(error);
 
     // FUTURE consider poisoning prototype's functions so calls fail with a
     // useful error message.
   },
 
-  _onStateDirCreated: function () {
-    // As soon as we have could storage, we need to register cleanup or
-    // else bad things happen on shutdown.
-    Services.obs.addObserver(this, "quit-application", false);
-    Services.obs.addObserver(this, "profile-before-change", false);
-
-    this._storageInProgress = true;
-    TelemetryStopwatch.start(this._dbOpenHistogram, this);
-    Metrics.Storage(this._dbName).then(this._onStorageCreated.bind(this),
-                                       this._onInitError.bind(this));
-  },
-
   // Called when storage has been opened.
   _onStorageCreated: function (storage) {
     TelemetryStopwatch.finish(this._dbOpenHistogram, this);
     delete this._dbOpenHistogram;
     this._log.info("Storage initialized.");
     this._storage = storage;
     this._storageInProgress = false;
 
@@ -424,16 +417,20 @@ AbstractHealthReporter.prototype = Objec
   /**
    * Obtain a provider from its name.
    *
    * This will only return providers that are currently initialized. If
    * a provider is lazy initialized (like pull-only providers) this
    * will likely not return anything.
    */
   getProvider: function (name) {
+    if (!this._providerManager) {
+      return null;
+    }
+
     return this._providerManager.getProvider(name);
   },
 
   _initProvider: function (provider) {
     provider.healthReporter = this;
   },
 
   /**
@@ -805,59 +802,16 @@ AbstractHealthReporter.prototype = Objec
 
         deferred.reject(error);
       }
     );
 
     return deferred.promise;
   },
 
-  get _lastPayloadPath() {
-    return OS.Path.join(this._stateDir, "lastpayload.json");
-  },
-
-  _saveLastPayload: function (payload) {
-    let path = this._lastPayloadPath;
-    let pathTmp = path + ".tmp";
-
-    let encoder = new TextEncoder();
-    let buffer = encoder.encode(payload);
-
-    return OS.File.writeAtomic(path, buffer, {tmpPath: pathTmp});
-  },
-
-  /**
-   * Obtain the last uploaded payload.
-   *
-   * The promise is resolved to a JSON-decoded object on success. The promise
-   * is rejected if the last uploaded payload could not be found or there was
-   * an error reading or parsing it.
-   *
-   * This reads the last payload from disk. If you are looking for a
-   * current snapshot of the data, see `getJSONPayload` and
-   * `collectAndObtainJSONPayload`.
-   *
-   * @return Promise<object>
-   */
-  getLastPayload: function () {
-    let path = this._lastPayloadPath;
-
-    return OS.File.read(path).then(
-      function onData(buffer) {
-        let decoder = new TextDecoder();
-        let json = JSON.parse(decoder.decode(buffer));
-
-        return CommonUtils.laterTickResolvingPromise(json);
-      },
-      function onError(error) {
-        return Promise.reject(error);
-      }
-    );
-  },
-
   _now: function _now() {
     return new Date();
   },
 
   // These are stolen from AppInfoProvider.
   appInfoVersion: 1,
   appInfoFields: {
     // From nsIXULAppInfo.
@@ -1148,41 +1102,54 @@ HealthReporter.prototype = Object.freeze
       };
 
       this._uploadData(request);
     }
 
     return result;
   },
 
-  _onBagheeraResult: function (request, isDelete, result) {
+  _onBagheeraResult: function (request, isDelete, date, result) {
     this._log.debug("Received Bagheera result.");
 
     let promise = CommonUtils.laterTickResolvingPromise(null);
+    let hrProvider = this.getProvider("org.mozilla.healthreport");
 
     if (!result.transportSuccess) {
+      // The built-in provider may not be initialized if this instance failed
+      // to initialize fully.
+      if (hrProvider && !isDelete) {
+        hrProvider.recordEvent("uploadTransportFailure", date);
+      }
+
       request.onSubmissionFailureSoft("Network transport error.");
       return promise;
     }
 
     if (!result.serverSuccess) {
+      if (hrProvider && !isDelete) {
+        hrProvider.recordEvent("uploadServerFailure", date);
+      }
+
       request.onSubmissionFailureHard("Server failure.");
       return promise;
     }
 
-    let now = this._now();
+    if (hrProvider && !isDelete) {
+      hrProvider.recordEvent("uploadSuccess", date);
+    }
 
     if (isDelete) {
       this.lastSubmitID = null;
     } else {
       this.lastSubmitID = result.id;
-      this.lastPingDate = now;
+      this.lastPingDate = date;
     }
 
-    request.onSubmissionSuccess(now);
+    request.onSubmissionSuccess(this._now());
 
 #ifdef PRERELEASE_BUILD
     // Intended to be temporary until we a) assess the impact b) bug 846133
     // deploys more robust storage for state.
     try {
       Services.prefs.savePrefFile(null);
     } catch (ex) {
       this._log.warn("Error forcing prefs save: " + CommonUtils.exceptionStr(ex));
@@ -1203,48 +1170,50 @@ HealthReporter.prototype = Object.freeze
   },
 
   _uploadData: function (request) {
     let id = CommonUtils.generateUUID();
 
     this._log.info("Uploading data to server: " + this.serverURI + " " +
                    this.serverNamespace + ":" + id);
     let client = new BagheeraClient(this.serverURI);
+    let now = this._now();
 
     return Task.spawn(function doUpload() {
       let payload = yield this.getJSONPayload();
 
       let histogram = Services.telemetry.getHistogramById(TELEMETRY_PAYLOAD_SIZE_UNCOMPRESSED);
       histogram.add(payload.length);
 
-      TelemetryStopwatch.start(TELEMETRY_SAVE_LAST_PAYLOAD, this);
-      try {
-        yield this._saveLastPayload(payload);
-        TelemetryStopwatch.finish(TELEMETRY_SAVE_LAST_PAYLOAD, this);
-      } catch (ex) {
-        TelemetryStopwatch.cancel(TELEMETRY_SAVE_LAST_PAYLOAD, this);
-        throw ex;
+      let hrProvider = this.getProvider("org.mozilla.healthreport");
+      if (hrProvider) {
+        let event = this.lastSubmitID ? "continuationUploadAttempt"
+                                      : "firstDocumentUploadAttempt";
+        hrProvider.recordEvent(event, now);
       }
 
       TelemetryStopwatch.start(TELEMETRY_UPLOAD, this);
       let result;
       try {
         let options = {
           deleteID: this.lastSubmitID,
           telemetryCompressed: TELEMETRY_PAYLOAD_SIZE_COMPRESSED,
         };
         result = yield client.uploadJSON(this.serverNamespace, id, payload,
                                          options);
         TelemetryStopwatch.finish(TELEMETRY_UPLOAD, this);
       } catch (ex) {
         TelemetryStopwatch.cancel(TELEMETRY_UPLOAD, this);
+        if (hrProvider) {
+          hrProvider.recordEvent("uploadClientFailure", now);
+        }
         throw ex;
       }
 
-      yield this._onBagheeraResult(request, false, result);
+      yield this._onBagheeraResult(request, false, now, result);
     }.bind(this));
   },
 
   /**
    * Request deletion of remote data.
    *
    * @param request
    *        (DataSubmissionRequest) Tracks progress of this request.
@@ -1255,13 +1224,13 @@ HealthReporter.prototype = Object.freeze
       request.onNoDataAvailable();
       return;
     }
 
     this._log.warn("Deleting remote data.");
     let client = new BagheeraClient(this.serverURI);
 
     return client.deleteDocument(this.serverNamespace, this.lastSubmitID)
-                 .then(this._onBagheeraResult.bind(this, request, true),
+                 .then(this._onBagheeraResult.bind(this, request, true, this._now()),
                        this._onSubmitDataRequestFailure.bind(this));
   },
 });
 
--- a/services/healthreport/providers.jsm
+++ b/services/healthreport/providers.jsm
@@ -16,16 +16,17 @@
 
 #ifndef MERGED_COMPARTMENT
 
 this.EXPORTED_SYMBOLS = [
   "AddonsProvider",
   "AppInfoProvider",
   "CrashDirectoryService",
   "CrashesProvider",
+  "HealthReportProvider",
   "PlacesProvider",
   "SearchesProvider",
   "SessionsProvider",
   "SysInfoProvider",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
@@ -1389,8 +1390,46 @@ this.SearchesProvider.prototype = Object
     let id = m.interestingEngines[engine] || "other";
     let field = id + "." + source;
     return this.enqueueStorageOperation(function recordSearch() {
       return m.incrementDailyCounter(field);
     });
   },
 });
 
+function HealthReportSubmissionMeasurement1() {
+  Metrics.Measurement.call(this);
+}
+
+HealthReportSubmissionMeasurement1.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+
+  name: "submissions",
+  version: 1,
+
+  fields: {
+    firstDocumentUploadAttempt: DAILY_COUNTER_FIELD,
+    continuationUploadAttempt: DAILY_COUNTER_FIELD,
+    uploadSuccess: DAILY_COUNTER_FIELD,
+    uploadTransportFailure: DAILY_COUNTER_FIELD,
+    uploadServerFailure: DAILY_COUNTER_FIELD,
+    uploadClientFailure: DAILY_COUNTER_FIELD,
+  },
+});
+
+this.HealthReportProvider = function () {
+  Metrics.Provider.call(this);
+}
+
+HealthReportProvider.prototype = Object.freeze({
+  __proto__: Metrics.Provider.prototype,
+
+  name: "org.mozilla.healthreport",
+
+  measurementTypes: [HealthReportSubmissionMeasurement1],
+
+  recordEvent: function (event, date=new Date()) {
+    let m = this.getMeasurement("submissions", 1);
+    return this.enqueueStorageOperation(function recordCounter() {
+      return m.incrementDailyCounter(event, date);
+    });
+  },
+});
--- a/services/healthreport/tests/xpcshell/test_healthreporter.js
+++ b/services/healthreport/tests/xpcshell/test_healthreporter.js
@@ -5,19 +5,21 @@
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://services-common/observers.js");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource://gre/modules/Metrics.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
+Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
 Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://testing-common/services-common/bagheeraserver.js");
 Cu.import("resource://testing-common/services/metrics/mocks.jsm");
 Cu.import("resource://testing-common/services/healthreport/utils.jsm");
 
 
 const SERVER_HOSTNAME = "localhost";
 const SERVER_PORT = 8080;
 const SERVER_URI = "http://" + SERVER_HOSTNAME + ":" + SERVER_PORT;
@@ -58,18 +60,25 @@ function getJustReporter(name, uri=SERVE
 
   let type = inspected ? InspectedHealthReporter : HealthReporter;
   reporter = new type(branch + "healthreport.", policy);
 
   return reporter;
 }
 
 function getReporter(name, uri, inspected) {
-  let reporter = getJustReporter(name, uri, inspected);
-  return reporter.onInit();
+  return Task.spawn(function init() {
+    let reporter = getJustReporter(name, uri, inspected);
+    yield reporter.onInit();
+
+    yield reporter._providerManager.registerProviderFromType(
+      HealthReportProvider);
+
+    throw new Task.Result(reporter);
+  });
 }
 
 function getReporterAndServer(name, namespace="test") {
   return Task.spawn(function get() {
     let reporter = yield getReporter(name, SERVER_URI);
     reporter.serverNamespace = namespace;
 
     let server = new BagheeraServer(SERVER_URI);
@@ -83,16 +92,36 @@ function getReporterAndServer(name, name
 
 function shutdownServer(server) {
   let deferred = Promise.defer();
   server.stop(deferred.resolve.bind(deferred));
 
   return deferred.promise;
 }
 
+function getHealthReportProviderValues(reporter, day=null) {
+  return Task.spawn(function getValues() {
+    let p = reporter.getProvider("org.mozilla.healthreport");
+    do_check_neq(p, null);
+    let m = p.getMeasurement("submissions", 1);
+    do_check_neq(m, null);
+
+    let data = yield reporter._storage.getMeasurementValues(m.id);
+    if (!day) {
+      throw new Task.Result(data);
+    }
+
+    do_check_true(data.days.hasDay(day));
+    let serializer = m.serializer(m.SERIALIZE_JSON)
+    let json = serializer.daily(data.days.getDay(day));
+
+    throw new Task.Result(json);
+  });
+}
+
 function run_test() {
   makeFakeAppDir().then(run_next_test, do_throw);
 }
 
 add_task(function test_constructor() {
   let reporter = yield getReporter("constructor");
 
   try {
@@ -184,27 +213,27 @@ add_task(function test_pull_only_provide
                       "resource://testing-common/services/metrics/mocks.jsm",
                       false, true);
   cm.addCategoryEntry(category, "DummyConstantProvider",
                       "resource://testing-common/services/metrics/mocks.jsm",
                       false, true);
 
   let reporter = yield getReporter("constant_only_providers");
   try {
-    do_check_eq(reporter._providerManager._providers.size, 0);
+    let initCount = reporter._providerManager.providers.length;
     yield reporter._providerManager.registerProvidersFromCategoryManager(category);
-    do_check_eq(reporter._providerManager._providers.size, 1);
+    do_check_eq(reporter._providerManager._providers.size, initCount + 1);
     do_check_true(reporter._storage.hasProvider("DummyProvider"));
     do_check_false(reporter._storage.hasProvider("DummyConstantProvider"));
     do_check_neq(reporter.getProvider("DummyProvider"), null);
     do_check_null(reporter.getProvider("DummyConstantProvider"));
 
     yield reporter.collectMeasurements();
 
-    do_check_eq(reporter._providerManager._providers.size, 1);
+    do_check_eq(reporter._providerManager._providers.size, initCount + 1);
     do_check_true(reporter._storage.hasProvider("DummyConstantProvider"));
 
     let mID = reporter._storage.measurementID("DummyConstantProvider", "DummyMeasurement", 1);
     let values = yield reporter._storage.getMeasurementValues(mID);
     do_check_true(values.singular.size > 0);
   } finally {
     reporter._shutdown();
   }
@@ -319,47 +348,48 @@ add_task(function test_constant_only_pro
                       "resource://testing-common/services/metrics/mocks.jsm",
                       false, true);
   cm.addCategoryEntry(category, "DummyConstantProvider",
                       "resource://testing-common/services/metrics/mocks.jsm",
                       false, true);
 
   let reporter = yield getReporter("constant_only_providers_in_json_payload");
   try {
+    let initCount = reporter._providerManager.providers.length;
     yield reporter._providerManager.registerProvidersFromCategoryManager(category);
 
     let payload = yield reporter.collectAndObtainJSONPayload();
     let o = JSON.parse(payload);
     do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
     do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
 
     let providers = reporter._providerManager.providers;
-    do_check_eq(providers.length, 1);
+    do_check_eq(providers.length, initCount + 1);
 
     // Do it again for good measure.
     payload = yield reporter.collectAndObtainJSONPayload();
     o = JSON.parse(payload);
     do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
     do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
 
     providers = reporter._providerManager.providers;
-    do_check_eq(providers.length, 1);
+    do_check_eq(providers.length, initCount + 1);
 
     // Ensure throwing getJSONPayload is handled properly.
     Object.defineProperty(reporter, "_getJSONPayload", {
       value: function () {
         throw new Error("Silly error.");
       },
     });
 
     let deferred = Promise.defer();
 
     reporter.collectAndObtainJSONPayload().then(do_throw, function onError() {
       providers = reporter._providerManager.providers;
-      do_check_eq(providers.length, 1);
+      do_check_eq(providers.length, initCount + 1);
       deferred.resolve();
     });
 
     yield deferred.promise;
   } finally {
     reporter._shutdown();
   }
 });
@@ -504,44 +534,85 @@ add_task(function test_data_submission_t
     reporter.serverNamespace = "test00";
 
     let deferred = Promise.defer();
     let request = new DataSubmissionRequest(deferred, new Date(Date.now + 30000));
     reporter.requestDataUpload(request);
 
     yield deferred.promise;
     do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
+
+    let data = yield getHealthReportProviderValues(reporter, new Date());
+    do_check_eq(data._v, 1);
+    do_check_eq(data.firstDocumentUploadAttempt, 1);
+    do_check_eq(data.uploadTransportFailure, 1);
+    do_check_eq(Object.keys(data).length, 3);
   } finally {
     reporter._shutdown();
   }
 });
 
+add_task(function test_data_submission_server_failure() {
+  let [reporter, server] = yield getReporterAndServer("data_submission_server_failure");
+  try {
+    Object.defineProperty(server, "_handleNamespaceSubmitPost", {
+      value: function (ns, id, request, response) {
+        throw HTTP_500;
+      },
+      writable: true,
+    });
+
+    let deferred = Promise.defer();
+    let now = new Date();
+    let request = new DataSubmissionRequest(deferred, now);
+    reporter.requestDataUpload(request);
+    yield deferred.promise;
+    do_check_eq(request.state, request.SUBMISSION_FAILURE_HARD);
+
+    let data = yield getHealthReportProviderValues(reporter, now);
+    do_check_eq(data._v, 1);
+    do_check_eq(data.firstDocumentUploadAttempt, 1);
+    do_check_eq(data.uploadServerFailure, 1);
+    do_check_eq(Object.keys(data).length, 3);
+  } finally {
+    yield shutdownServer(server);
+    reporter._shutdown();
+  }
+});
+
 add_task(function test_data_submission_success() {
   let [reporter, server] = yield getReporterAndServer("data_submission_success");
   try {
     yield reporter._providerManager.registerProviderFromType(DummyProvider);
     yield reporter._providerManager.registerProviderFromType(DummyConstantProvider);
 
     do_check_eq(reporter.lastPingDate.getTime(), 0);
     do_check_false(reporter.haveRemoteData());
 
     let deferred = Promise.defer();
 
-    let request = new DataSubmissionRequest(deferred, new Date());
+    let now = new Date();
+    let request = new DataSubmissionRequest(deferred, now);
     reporter.requestDataUpload(request);
     yield deferred.promise;
     do_check_eq(request.state, request.SUBMISSION_SUCCESS);
     do_check_true(reporter.lastPingDate.getTime() > 0);
     do_check_true(reporter.haveRemoteData());
 
     // Ensure data from providers made it to payload.
-    let o = yield reporter.getLastPayload();
+    let o = yield reporter.getJSONPayload(true);
     do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
     do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
 
+    let data = yield getHealthReportProviderValues(reporter, now);
+    do_check_eq(data._v, 1);
+    do_check_eq(data.firstDocumentUploadAttempt, 1);
+    do_check_eq(data.uploadSuccess, 1);
+    do_check_eq(Object.keys(data).length, 3);
+
     reporter._shutdown();
   } finally {
     yield shutdownServer(server);
   }
 });
 
 add_task(function test_recurring_daily_pings() {
   let [reporter, server] = yield getReporterAndServer("recurring_daily_pings");
@@ -564,16 +635,25 @@ add_task(function test_recurring_daily_p
     // Skip forward to next scheduled submission time.
     defineNow(policy, policy.nextDataSubmissionDate);
     promise = policy.checkStateAndTrigger();
     do_check_neq(promise, null);
     yield promise;
     do_check_neq(reporter.lastSubmitID, lastID);
     do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
     do_check_false(server.hasDocument(reporter.serverNamespace, lastID));
+
+    // now() on the health reporter instance wasn't munged. So, we should see
+    // both requests attributed to the same day.
+    let data = yield getHealthReportProviderValues(reporter, new Date());
+    do_check_eq(data._v, 1);
+    do_check_eq(data.firstDocumentUploadAttempt, 1);
+    do_check_eq(data.continuationUploadAttempt, 1);
+    do_check_eq(data.uploadSuccess, 2);
+    do_check_eq(Object.keys(data).length, 4);
   } finally {
     reporter._shutdown();
     yield shutdownServer(server);
   }
 });
 
 add_task(function test_request_remote_data_deletion() {
   let [reporter, server] = yield getReporterAndServer("request_remote_data_deletion");
@@ -619,32 +699,16 @@ add_task(function test_policy_accept_rej
     do_check_false(policy.dataSubmissionPolicyAccepted);
     do_check_false(reporter.willUploadData);
   } finally {
     reporter._shutdown();
     yield shutdownServer(server);
   }
 });
 
-add_task(function test_upload_save_payload() {
-  let [reporter, server] = yield getReporterAndServer("upload_save_payload");
-
-  try {
-    let deferred = Promise.defer();
-    let request = new DataSubmissionRequest(deferred, new Date(), false);
-
-    yield reporter._uploadData(request);
-    let json = yield reporter.getLastPayload();
-    do_check_true("thisPingDate" in json);
-  } finally {
-    reporter._shutdown();
-    yield shutdownServer(server);
-  }
-});
-
 add_task(function test_error_message_scrubbing() {
   let reporter = yield getReporter("error_message_scrubbing");
 
   try {
     let profile = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
     reporter._recordError("Foo " + profile);
 
     do_check_eq(reporter._errors.length, 1);
@@ -746,22 +810,22 @@ add_task(function test_upload_on_init_fa
   reporter.onInitializeProviderManagerFinished = function () {
     throw new Error("Fake error during provider manager initialization.");
   };
 
   let deferred = Promise.defer();
 
   let oldOnResult = reporter._onBagheeraResult;
   Object.defineProperty(reporter, "_onBagheeraResult", {
-    value: function (request, isDelete, result) {
+    value: function (request, isDelete, date, result) {
       do_check_false(isDelete);
       do_check_true(result.transportSuccess);
       do_check_true(result.serverSuccess);
 
-      oldOnResult.call(reporter, request, isDelete, result);
+      oldOnResult.call(reporter, request, isDelete, new Date(), result);
       deferred.resolve();
     },
   });
 
   reporter._policy.recordUserAcceptance();
   let error = false;
   try {
     yield reporter.onInit();
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3104,23 +3104,16 @@
   },
   "HEALTHREPORT_UPLOAD_MS": {
     "kind": "exponential",
     "high": "60000",
     "n_buckets": 20,
     "extended_statistics_ok": true,
     "description": "Time (ms) it takes to upload the Health Report payload."
   },
-  "HEALTHREPORT_SAVE_LAST_PAYLOAD_MS": {
-    "kind": "exponential",
-    "high": "10000",
-    "n_buckets": 10,
-    "extended_statistics_ok": true,
-    "description": "Time (ms) it takes to persist the last submitted Health Report payload to disk."
-  },
   "HEALTHREPORT_COLLECT_CONSTANT_DATA_MS": {
     "kind": "exponential",
     "high": "20000",
     "n_buckets": 15,
     "description": "Time (ms) it takes FHR to collect constant data."
   },
   "HEALTHREPORT_COLLECT_DAILY_MS": {
     "kind": "exponential",
--- a/widget/gonk/nsWindow.cpp
+++ b/widget/gonk/nsWindow.cpp
@@ -536,16 +536,27 @@ nsWindow::MakeFullScreen(bool aFullScree
 }
 
 float
 nsWindow::GetDPI()
 {
     return NativeWindow()->xdpi;
 }
 
+double
+nsWindow::GetDefaultScaleInternal()
+{
+    double rawscale = GetDPI() / 192.0;
+    if (rawscale < 1.25)
+        return 1;
+    else if (rawscale < 1.75)
+        return 1.5;
+    return 2;
+}
+
 LayerManager *
 nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
                           LayersBackend aBackendHint,
                           LayerManagerPersistence aPersistence,
                           bool* aAllowRetaining)
 {
     if (aAllowRetaining)
         *aAllowRetaining = true;
--- a/widget/gonk/nsWindow.h
+++ b/widget/gonk/nsWindow.h
@@ -88,16 +88,17 @@ public:
     {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
     NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent);
 
     NS_IMETHOD MakeFullScreen(bool aFullScreen) /*MOZ_OVERRIDE*/;
 
     virtual float GetDPI();
+    virtual double GetDefaultScaleInternal();
     virtual mozilla::layers::LayerManager*
         GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                         LayersBackend aBackendHint = mozilla::layers::LAYERS_NONE,
                         LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                         bool* aAllowRetaining = nullptr);
     gfxASurface* GetThebesSurface();
 
     NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
--- a/widget/xpwidgets/PuppetWidget.cpp
+++ b/widget/xpwidgets/PuppetWidget.cpp
@@ -69,16 +69,17 @@ MightNeedIMEFocus(const nsWidgetInitData
 const size_t PuppetWidget::kMaxDimension = 4000;
 
 NS_IMPL_ISUPPORTS_INHERITED1(PuppetWidget, nsBaseWidget,
                              nsISupportsWeakReference)
 
 PuppetWidget::PuppetWidget(TabChild* aTabChild)
   : mTabChild(aTabChild)
   , mDPI(-1)
+  , mDefaultScale(-1)
 {
   MOZ_COUNT_CTOR(PuppetWidget);
 }
 
 PuppetWidget::~PuppetWidget()
 {
   MOZ_COUNT_DTOR(PuppetWidget);
 }
@@ -629,16 +630,30 @@ PuppetWidget::GetDPI()
     } else {
       mDPI = 96.0;
     }
   }
 
   return mDPI;
 }
 
+double
+PuppetWidget::GetDefaultScaleInternal()
+{
+  if (mDefaultScale < 0) {
+    if (mTabChild) {
+      mTabChild->GetDefaultScale(&mDefaultScale);
+    } else {
+      mDefaultScale = 1;
+    }
+  }
+
+  return mDefaultScale;
+}
+
 void*
 PuppetWidget::GetNativeData(uint32_t aDataType)
 {
   switch (aDataType) {
   case NS_NATIVE_SHAREABLE_WINDOW: {
     NS_ABORT_IF_FALSE(mTabChild, "Need TabChild to get the nativeWindow from!");
     mozilla::WindowsHandle nativeData = 0;
     if (mTabChild) {
--- a/widget/xpwidgets/PuppetWidget.h
+++ b/widget/xpwidgets/PuppetWidget.h
@@ -164,16 +164,17 @@ public:
     return nsBaseWidget::SetCursor(aCursor, aHotspotX, aHotspotY);
   }
 
   // Gets the DPI of the screen corresponding to this widget.
   // Contacts the parent process which gets the DPI from the
   // proper widget there. TODO: Handle DPI changes that happen
   // later on.
   virtual float GetDPI();
+  virtual double GetDefaultScaleInternal();
 
   virtual bool NeedsPaint() MOZ_OVERRIDE;
 
   virtual TabChild* GetOwningTabChild() MOZ_OVERRIDE { return mTabChild; }
 
 private:
   nsresult Paint();
 
@@ -218,16 +219,17 @@ private:
   // arriving events with seqno up to this should be discarded
   // Note that if seqno overflows (~50 days at 1 ms increment rate),
   // events will be discarded until new focus/blur occurs
   uint32_t mIMELastBlurSeqno;
   bool mNeedIMEStateInit;
 
   // The DPI of the screen corresponding to this widget
   float mDPI;
+  double mDefaultScale;
 };
 
 class PuppetScreen : public nsBaseScreen
 {
 public:
     PuppetScreen(void* nativeScreen);
     ~PuppetScreen();