Bug 857419 - Implement about:healthreport on Android. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Fri, 10 May 2013 21:30:57 -0700
changeset 142604 dec449c6ac152d0a456656b388cc504bb02a570f
parent 142603 5b345b6dc9281e7c3fd38167aa31511d9a180a30
child 142605 2d5c2f19192085903b3d024e3f8530596bd61d18
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs857419
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 857419 - Implement about:healthreport on Android. r=rnewman
mobile/android/chrome/Makefile.in
mobile/android/chrome/content/aboutHealthReport.js
mobile/android/chrome/content/aboutHealthReport.xhtml
mobile/android/chrome/content/healthreport-prefs.js
mobile/android/chrome/jar.mn
mobile/android/components/AboutRedirector.js
mobile/android/components/MobileComponents.manifest
mobile/android/locales/en-US/chrome/aboutHealthReport.dtd
mobile/android/locales/jar.mn
mobile/android/themes/core/aboutHealthReport.css
mobile/android/themes/core/jar.mn
modules/libpref/src/Makefile.in
--- a/mobile/android/chrome/Makefile.in
+++ b/mobile/android/chrome/Makefile.in
@@ -7,11 +7,12 @@ topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 DEFINES += -DAB_CD=$(MOZ_UI_LOCALE) \
            -DPACKAGE=browser \
            -DMOZ_APP_VERSION=$(MOZ_APP_VERSION) \
+           -DANDROID_PACKAGE_NAME=$(ANDROID_PACKAGE_NAME) \
            $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/aboutHealthReport.js
@@ -0,0 +1,204 @@
+#filter substitution
+// -*- 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource://gre/modules/OrderedBroadcast.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/SharedPreferences.jsm");
+
+// Name of Android SharedPreference controlling whether to upload
+// health reports.
+const PREF_UPLOAD_ENABLED = "android.not_a_preference.healthreport.uploadEnabled";
+
+// Action sent via Android Ordered Broadcast to background service.
+const BROADCAST_ACTION_HEALTH_REPORT = "@ANDROID_PACKAGE_NAME@" + ".healthreport.request";
+
+// Name of Gecko Pref specifying report content location.
+const PREF_REPORTURL = "datareporting.healthreport.about.reportUrl";
+
+function sendMessageToJava(message) {
+  return Cc["@mozilla.org/android/bridge;1"]
+    .getService(Ci.nsIAndroidBridge)
+    .handleGeckoMessage(JSON.stringify(message));
+}
+
+// Default preferences for the application.
+let sharedPrefs = new SharedPreferences();
+
+let reporter = {
+  onInit: function () {
+    let deferred = Promise.defer();
+    deferred.resolve();
+
+    return deferred.promise;
+  },
+
+  collectAndObtainJSONPayload: function () {
+    let deferred = Promise.defer();
+
+    let callback = function (data, token, action) {
+      if (data) {
+        // Bug 870992: the FHR report content expects FHR report data
+        // in string form.  This costs us a JSON parsing round trip,
+        // since the ordered broadcast module parses the stringified
+        // JSON returned from Java.  Since the FHR report content
+        // expects updates to preferences as a Javascript object, we
+        // cannot handle the situation uniformly, and we pay the price
+        // here, stringifying a huge chunk of JSON.
+        deferred.resolve(JSON.stringify(data));
+      } else {
+        deferred.reject();
+      }
+    };
+
+    sendOrderedBroadcast(BROADCAST_ACTION_HEALTH_REPORT, null, callback);
+
+    return deferred.promise;
+  },
+};
+
+let policy = {
+  get healthReportUploadEnabled() {
+    return sharedPrefs.getBoolPref(PREF_UPLOAD_ENABLED);
+  },
+
+  recordHealthReportUploadEnabled: function (enabled) {
+    sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, !!enabled);
+  },
+};
+
+let healthReportWrapper = {
+  init: function () {
+    reporter.onInit().then(healthReportWrapper.refreshPayload,
+                           healthReportWrapper.handleInitFailure);
+
+    let iframe = document.getElementById("remote-report");
+    iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
+    let report = this._getReportURI();
+    iframe.src = report.spec;
+
+    sharedPrefs.addObserver(PREF_UPLOAD_ENABLED, this, false);
+  },
+
+  observe: function (subject, topic, data) {
+    if (topic != PREF_UPLOAD_ENABLED) {
+      return;
+    }
+
+    subject.updatePrefState();
+  },
+
+  uninit: function () {
+    sharedPrefs.removeObserver(PREF_UPLOAD_ENABLED, this);
+  },
+
+  _getReportURI: function () {
+    let url = Services.urlFormatter.formatURLPref(PREF_REPORTURL);
+    return Services.io.newURI(url, null, null);
+  },
+
+  onOptIn: function () {
+    policy.recordHealthReportUploadEnabled(true,
+                                           "Health report page sent opt-in command.");
+    this.updatePrefState();
+  },
+
+  onOptOut: function () {
+    policy.recordHealthReportUploadEnabled(false,
+                                           "Health report page sent opt-out command.");
+    this.updatePrefState();
+  },
+
+  updatePrefState: function () {
+    try {
+      let prefs = {
+        enabled: policy.healthReportUploadEnabled,
+      };
+      this.injectData("prefs", prefs);
+    } catch (e) {
+      this.reportFailure(this.ERROR_PREFS_FAILED);
+    }
+  },
+
+  refreshPayload: function () {
+    reporter.collectAndObtainJSONPayload().then(healthReportWrapper.updatePayload,
+                                                healthReportWrapper.handlePayloadFailure);
+  },
+
+  updatePayload: function (data) {
+    healthReportWrapper.injectData("payload", data);
+  },
+
+  injectData: function (type, content) {
+    let report = this._getReportURI();
+
+    // file URIs can't be used for targetOrigin, so we use "*" for this special case
+    // in all other cases, pass in the URL to the report so we properly restrict the message dispatch
+
+    let reportUrl = report.scheme == "file" ? "*" : report.spec;
+
+    let data = {
+      type: type,
+      content: content,
+    };
+
+    let iframe = document.getElementById("remote-report");
+    iframe.contentWindow.postMessage(data, reportUrl);
+  },
+
+  handleRemoteCommand: function (evt) {
+    switch (evt.detail.command) {
+      case "DisableDataSubmission":
+        this.onOptOut();
+        break;
+      case "EnableDataSubmission":
+        this.onOptIn();
+        break;
+      case "RequestCurrentPrefs":
+        this.updatePrefState();
+        break;
+      case "RequestCurrentPayload":
+        this.refreshPayload();
+        break;
+      default:
+        Cu.reportError("Unexpected remote command received: " + evt.detail.command +
+                       ". Ignoring command.");
+        break;
+    }
+  },
+
+  initRemotePage: function () {
+    let iframe = document.getElementById("remote-report").contentDocument;
+    iframe.addEventListener("RemoteHealthReportCommand",
+                            function onCommand(e) {healthReportWrapper.handleRemoteCommand(e);},
+                            false);
+    healthReportWrapper.updatePrefState();
+  },
+
+  // error handling
+  ERROR_INIT_FAILED:    1,
+  ERROR_PAYLOAD_FAILED: 2,
+  ERROR_PREFS_FAILED:   3,
+
+  reportFailure: function (error) {
+    let details = {
+      errorType: error,
+    };
+    healthReportWrapper.injectData("error", details);
+  },
+
+  handleInitFailure: function () {
+    healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED);
+  },
+
+  handlePayloadFailure: function () {
+    healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED);
+  },
+};
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/aboutHealthReport.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+%globalDTD;
+<!ENTITY % aboutHealthReportDTD SYSTEM "chrome://browser/locale/aboutHealthReport.dtd" >
+%aboutHealthReportDTD;
+]>
+
+<!-- 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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+   <title>&abouthealth.pagetitle;</title>
+   <link rel="icon" type="image/png" sizes="64x64"
+     href="chrome://branding/content/favicon64.png" />
+   <link rel="stylesheet"
+     href="chrome://browser/skin/aboutHealthReport.css"
+     type="text/css" />
+   <script type="text/javascript;version=1.8"
+     src="chrome://browser/content/aboutHealthReport.js" />
+  </head>
+  <body onload="healthReportWrapper.init();"
+        onunload="healthReportWrapper.uninit();">
+    <iframe id="remote-report"/>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/healthreport-prefs.js
@@ -0,0 +1,6 @@
+#filter substitution
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pref("datareporting.healthreport.about.reportUrl", "https://fhr.cdn.mozilla.net/%LOCALE%/");
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -33,25 +33,29 @@ chrome.jar:
   content/bindings/settings.xml        (content/bindings/settings.xml)
   content/exceptions.js                (content/exceptions.js)
   content/downloads.js                 (content/downloads.js)
   content/netError.xhtml               (content/netError.xhtml)
   content/SelectHelper.js              (content/SelectHelper.js)
   content/SelectionHandler.js          (content/SelectionHandler.js)
   content/HelperApps.js                (content/HelperApps.js)
   content/dbg-browser-actors.js        (content/dbg-browser-actors.js)
-* content/WebAppRT.js                  (content/WebAppRT.js)
+  content/WebAppRT.js                  (content/WebAppRT.js)
   content/InputWidgetHelper.js         (content/InputWidgetHelper.js)
   content/WebrtcUI.js                  (content/WebrtcUI.js)
   content/MemoryObserver.js            (content/MemoryObserver.js)
   content/ConsoleAPI.js                (content/ConsoleAPI.js)
   content/PluginHelper.js              (content/PluginHelper.js)
   content/OfflineApps.js               (content/OfflineApps.js)
   content/MasterPassword.js            (content/MasterPassword.js)
   content/FindHelper.js                (content/FindHelper.js)
   content/PermissionsHelper.js         (content/PermissionsHelper.js)
   content/FeedHandler.js               (content/FeedHandler.js)
+#ifdef MOZ_SERVICES_HEALTHREPORT
+  content/aboutHealthReport.xhtml      (content/aboutHealthReport.xhtml)
+* content/aboutHealthReport.js         (content/aboutHealthReport.js)
+#endif
 
 % content branding %content/branding/
 
 % override chrome://global/content/config.xul chrome://browser/content/config.xhtml
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
 % override chrome://mozapps/content/extensions/extensions.xul chrome://browser/content/aboutAddons.xhtml
--- a/mobile/android/components/AboutRedirector.js
+++ b/mobile/android/components/AboutRedirector.js
@@ -66,17 +66,23 @@ let modules = {
   },
   feedback: {
     uri: "chrome://browser/content/aboutFeedback.xhtml",
     privileged: true
   },
   privatebrowsing: {
     uri: "chrome://browser/content/aboutPrivateBrowsing.xhtml",
     privileged: true
-  }
+  },
+#ifdef MOZ_SERVICES_HEALTHREPORT
+  healthreport: {
+    uri: "chrome://browser/content/aboutHealthReport.xhtml",
+    privileged: true
+  },
+#endif
 }
 
 function AboutRedirector() {}
 AboutRedirector.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
   classID: Components.ID("{322ba47e-7047-4f71-aebf-cb7d69325cd9}"),
 
   _getModuleInfo: function (aURI) {
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -7,16 +7,19 @@ contract @mozilla.org/network/protocol/a
 contract @mozilla.org/network/protocol/about;1?what=rights {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=certerror {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=home {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=apps {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=downloads {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=reader {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=feedback {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=privatebrowsing {322ba47e-7047-4f71-aebf-cb7d69325cd9}
+#ifdef MOZ_SERVICES_HEALTHREPORT
+contract @mozilla.org/network/protocol/about;1?what=healthreport {322ba47e-7047-4f71-aebf-cb7d69325cd9}
+#endif
 #ifdef MOZ_SAFE_BROWSING
 contract @mozilla.org/network/protocol/about;1?what=blocked {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 #endif
 
 # DirectoryProvider.js
 component {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b} DirectoryProvider.js
 contract @mozilla.org/browser/directory-provider;1 {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b}
 category xpcom-directory-providers browser-directory-provider @mozilla.org/browser/directory-provider;1
new file mode 100644
--- /dev/null
+++ b/mobile/android/locales/en-US/chrome/aboutHealthReport.dtd
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+
+<!-- LOCALIZATION NOTE (abouthealth.pagetitle): Firefox Health Report is a proper noun in en-US, please keep this in mind. -->
+<!ENTITY abouthealth.pagetitle "&brandShortName; Health Report">
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -11,16 +11,19 @@
   locale/@AB_CD@/browser/aboutAddons.properties   (%chrome/aboutAddons.properties)
   locale/@AB_CD@/browser/aboutApps.dtd            (%chrome/aboutApps.dtd)
   locale/@AB_CD@/browser/aboutCertError.dtd       (%chrome/aboutCertError.dtd)
   locale/@AB_CD@/browser/aboutDownloads.dtd       (%chrome/aboutDownloads.dtd)
   locale/@AB_CD@/browser/aboutDownloads.properties (%chrome/aboutDownloads.properties)
   locale/@AB_CD@/browser/aboutFeedback.dtd        (%chrome/aboutFeedback.dtd)
   locale/@AB_CD@/browser/aboutPrivateBrowsing.dtd (%chrome/aboutPrivateBrowsing.dtd)
   locale/@AB_CD@/browser/aboutReader.properties   (%chrome/aboutReader.properties)
+#ifdef MOZ_SERVICES_HEALTHREPORT
+  locale/@AB_CD@/browser/aboutHealthReport.dtd    (%chrome/aboutHealthReport.dtd)
+#endif
   locale/@AB_CD@/browser/browser.properties       (%chrome/browser.properties)
   locale/@AB_CD@/browser/config.dtd               (%chrome/config.dtd)
   locale/@AB_CD@/browser/config.properties        (%chrome/config.properties)
   locale/@AB_CD@/browser/localepicker.properties  (%chrome/localepicker.properties)
   locale/@AB_CD@/browser/checkbox.dtd             (%chrome/checkbox.dtd)
   locale/@AB_CD@/browser/notification.dtd         (%chrome/notification.dtd)
   locale/@AB_CD@/browser/pippki.properties        (%chrome/pippki.properties)
   locale/@AB_CD@/browser/sync.dtd                 (%chrome/sync.dtd)
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/aboutHealthReport.css
@@ -0,0 +1,15 @@
+* {
+  margin: 0;
+  padding: 0;
+}
+
+html, body {
+  height: 100%;
+}
+
+#remote-report {
+  width: 100%;
+  height: 100%;
+  border: 0;
+  display: flex;
+}
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -8,16 +8,19 @@ chrome.jar:
 % skin browser classic/1.0 %skin/
   skin/aboutPage.css                        (aboutPage.css)
   skin/about.css                            (about.css)
   skin/aboutAddons.css                      (aboutAddons.css)
   skin/aboutApps.css                        (aboutApps.css)
   skin/aboutBase.css                        (aboutBase.css)
 * skin/aboutDownloads.css                   (aboutDownloads.css)
   skin/aboutFeedback.css                    (aboutFeedback.css)
+#ifdef MOZ_SERVICES_HEALTHREPORT
+  skin/aboutHealthReport.css                (aboutHealthReport.css)
+#endif
   skin/aboutMemory.css                      (aboutMemory.css)
 * skin/aboutPrivateBrowsing.css             (aboutPrivateBrowsing.css)
   skin/aboutReader.css                      (aboutReader.css)
   skin/aboutSupport.css                     (aboutSupport.css)
 * skin/browser.css                          (browser.css)
 * skin/content.css                          (content.css)
   skin/config.css                           (config.css)
   skin/touchcontrols.css                    (touchcontrols.css)
--- a/modules/libpref/src/Makefile.in
+++ b/modules/libpref/src/Makefile.in
@@ -41,17 +41,21 @@ GARBAGE		+= greprefs.js
 # TODO bug 813259 external files should be defined near their location in the source tree.
 grepref_files = $(topsrcdir)/netwerk/base/public/security-prefs.js $(srcdir)/init/all.js
 
 ifdef MOZ_DATA_REPORTING
 grepref_files += $(topsrcdir)/services/datareporting/datareporting-prefs.js
 endif
 
 ifdef MOZ_SERVICES_HEALTHREPORT
+ifneq (android,$(MOZ_WIDGET_TOOLKIT))
 grepref_files += $(topsrcdir)/services/healthreport/healthreport-prefs.js
+else
+grepref_files += $(topsrcdir)/mobile/android/chrome/content/healthreport-prefs.js
+endif
 endif
 
 # Optimizer bug with GCC 3.2.2 on OS/2
 ifeq ($(OS_ARCH), OS2)
 nsPrefService.$(OBJ_SUFFIX): nsPrefService.cpp
 	$(REPORT_BUILD)
 	@$(MAKE_DEPS_AUTO_CXX)
 	$(ELOG) $(CCC) $(OUTOPTION)$@ -c $(COMPILE_CXXFLAGS:-O2=-O1) $(_VPATH_SRCS)