michael@0: #filter substitution michael@0: // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Messaging.jsm"); michael@0: Cu.import("resource://gre/modules/SharedPreferences.jsm"); michael@0: michael@0: // Name of Android SharedPreference controlling whether to upload michael@0: // health reports. michael@0: const PREF_UPLOAD_ENABLED = "android.not_a_preference.healthreport.uploadEnabled"; michael@0: michael@0: // Name of Gecko Pref specifying report content location. michael@0: const PREF_REPORTURL = "datareporting.healthreport.about.reportUrl"; michael@0: michael@0: // Monotonically increasing wrapper API version number. michael@0: const WRAPPER_VERSION = 1; michael@0: michael@0: const EVENT_HEALTH_REQUEST = "HealthReport:Request"; michael@0: const EVENT_HEALTH_RESPONSE = "HealthReport:Response"; michael@0: michael@0: // about:healthreport prefs are stored in Firefox's default Android michael@0: // SharedPreferences. michael@0: let sharedPrefs = new SharedPreferences(); michael@0: michael@0: let healthReportWrapper = { michael@0: init: function () { michael@0: let iframe = document.getElementById("remote-report"); michael@0: iframe.addEventListener("load", healthReportWrapper.initRemotePage, false); michael@0: let report = this._getReportURI(); michael@0: iframe.src = report.spec; michael@0: console.log("AboutHealthReport: loading content from " + report.spec); michael@0: michael@0: sharedPrefs.addObserver(PREF_UPLOAD_ENABLED, this, false); michael@0: Services.obs.addObserver(this, EVENT_HEALTH_RESPONSE, false); michael@0: }, michael@0: michael@0: observe: function (subject, topic, data) { michael@0: if (topic == PREF_UPLOAD_ENABLED) { michael@0: this.updatePrefState(); michael@0: } else if (topic == EVENT_HEALTH_RESPONSE) { michael@0: this.updatePayload(data); michael@0: } michael@0: }, michael@0: michael@0: uninit: function () { michael@0: sharedPrefs.removeObserver(PREF_UPLOAD_ENABLED, this); michael@0: Services.obs.removeObserver(this, EVENT_HEALTH_RESPONSE); michael@0: }, michael@0: michael@0: _getReportURI: function () { michael@0: let url = Services.urlFormatter.formatURLPref(PREF_REPORTURL); michael@0: // This handles URLs that already have query parameters. michael@0: let uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL); michael@0: uri.query += ((uri.query != "") ? "&v=" : "v=") + WRAPPER_VERSION; michael@0: return uri; michael@0: }, michael@0: michael@0: onOptIn: function () { michael@0: console.log("AboutHealthReport: page sent opt-in command."); michael@0: sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, true); michael@0: this.updatePrefState(); michael@0: }, michael@0: michael@0: onOptOut: function () { michael@0: console.log("AboutHealthReport: page sent opt-out command."); michael@0: sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, false); michael@0: this.updatePrefState(); michael@0: }, michael@0: michael@0: updatePrefState: function () { michael@0: console.log("AboutHealthReport: sending pref state to page."); michael@0: try { michael@0: let prefs = { michael@0: enabled: sharedPrefs.getBoolPref(PREF_UPLOAD_ENABLED), michael@0: }; michael@0: this.injectData("prefs", prefs); michael@0: } catch (e) { michael@0: this.reportFailure(this.ERROR_PREFS_FAILED); michael@0: } michael@0: }, michael@0: michael@0: refreshPayload: function () { michael@0: console.log("AboutHealthReport: page requested fresh payload."); michael@0: sendMessageToJava({ michael@0: type: EVENT_HEALTH_REQUEST, michael@0: }); michael@0: }, michael@0: michael@0: updatePayload: function (data) { michael@0: healthReportWrapper.injectData("payload", data); michael@0: // Data is supposed to be a string, so the length should be michael@0: // defined. Just in case, we do this after injecting the data. michael@0: console.log("AboutHealthReport: sending payload to page " + michael@0: "(" + typeof(data) + " of length " + data.length + ")."); michael@0: }, michael@0: michael@0: injectData: function (type, content) { michael@0: let report = this._getReportURI(); michael@0: michael@0: // file: URIs can't be used for targetOrigin, so we use "*" for michael@0: // this special case. In all other cases, pass in the URL to the michael@0: // report so we properly restrict the message dispatch. michael@0: let reportUrl = (report.scheme == "file") ? "*" : report.spec; michael@0: michael@0: let data = { michael@0: type: type, michael@0: content: content, michael@0: }; michael@0: michael@0: let iframe = document.getElementById("remote-report"); michael@0: iframe.contentWindow.postMessage(data, reportUrl); michael@0: }, michael@0: michael@0: showSettings: function () { michael@0: console.log("AboutHealthReport: showing settings."); michael@0: sendMessageToJava({ michael@0: type: "Settings:Show", michael@0: resource: "preferences_vendor", michael@0: }); michael@0: }, michael@0: michael@0: launchUpdater: function () { michael@0: console.log("AboutHealthReport: launching updater."); michael@0: sendMessageToJava({ michael@0: type: "Updater:Launch", michael@0: }); michael@0: }, michael@0: michael@0: handleRemoteCommand: function (evt) { michael@0: switch (evt.detail.command) { michael@0: case "DisableDataSubmission": michael@0: this.onOptOut(); michael@0: break; michael@0: case "EnableDataSubmission": michael@0: this.onOptIn(); michael@0: break; michael@0: case "RequestCurrentPrefs": michael@0: this.updatePrefState(); michael@0: break; michael@0: case "RequestCurrentPayload": michael@0: this.refreshPayload(); michael@0: break; michael@0: case "ShowSettings": michael@0: this.showSettings(); michael@0: break; michael@0: case "LaunchUpdater": michael@0: this.launchUpdater(); michael@0: break; michael@0: default: michael@0: Cu.reportError("Unexpected remote command received: " + evt.detail.command + michael@0: ". Ignoring command."); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: initRemotePage: function () { michael@0: let iframe = document.getElementById("remote-report").contentDocument; michael@0: iframe.addEventListener("RemoteHealthReportCommand", michael@0: function onCommand(e) {healthReportWrapper.handleRemoteCommand(e);}, michael@0: false); michael@0: healthReportWrapper.injectData("begin", null); michael@0: }, michael@0: michael@0: // error handling michael@0: ERROR_INIT_FAILED: 1, michael@0: ERROR_PAYLOAD_FAILED: 2, michael@0: ERROR_PREFS_FAILED: 3, michael@0: michael@0: reportFailure: function (error) { michael@0: let details = { michael@0: errorType: error, michael@0: }; michael@0: healthReportWrapper.injectData("error", details); michael@0: }, michael@0: michael@0: handleInitFailure: function () { michael@0: healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED); michael@0: }, michael@0: michael@0: handlePayloadFailure: function () { michael@0: healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED); michael@0: }, michael@0: }; michael@0: michael@0: window.addEventListener("load", healthReportWrapper.init.bind(healthReportWrapper), false); michael@0: window.addEventListener("unload", healthReportWrapper.uninit.bind(healthReportWrapper), false);