// -*- 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"; var { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Messaging.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"; // Name of Gecko Pref specifying report content location. const PREF_REPORTURL = "datareporting.healthreport.about.reportUrl"; // Monotonically increasing wrapper API version number. const WRAPPER_VERSION = 1; const EVENT_HEALTH_REQUEST = "HealthReport:Request"; const EVENT_HEALTH_RESPONSE = "HealthReport:Response"; // about:healthreport prefs are stored in Firefox's default Android // SharedPreferences. var sharedPrefs = SharedPreferences.forApp(); var healthReportWrapper = { init: function () { let iframe = document.getElementById("remote-report"); iframe.addEventListener("load", healthReportWrapper.initRemotePage, false); let report = this._getReportURI(); iframe.src = report.spec; console.log("AboutHealthReport: loading content from " + report.spec); sharedPrefs.addObserver(PREF_UPLOAD_ENABLED, this, false); Services.obs.addObserver(this, EVENT_HEALTH_RESPONSE, false); }, observe: function (subject, topic, data) { if (topic == PREF_UPLOAD_ENABLED) { this.updatePrefState(); } else if (topic == EVENT_HEALTH_RESPONSE) { this.updatePayload(data); } }, uninit: function () { sharedPrefs.removeObserver(PREF_UPLOAD_ENABLED, this); Services.obs.removeObserver(this, EVENT_HEALTH_RESPONSE); }, _getReportURI: function () { let url = Services.urlFormatter.formatURLPref(PREF_REPORTURL); // This handles URLs that already have query parameters. let uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL); uri.query += ((uri.query != "") ? "&v=" : "v=") + WRAPPER_VERSION; return uri; }, onOptIn: function () { console.log("AboutHealthReport: page sent opt-in command."); sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, true); this.updatePrefState(); }, onOptOut: function () { console.log("AboutHealthReport: page sent opt-out command."); sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, false); this.updatePrefState(); }, updatePrefState: function () { console.log("AboutHealthReport: sending pref state to page."); try { let prefs = { enabled: sharedPrefs.getBoolPref(PREF_UPLOAD_ENABLED), }; this.injectData("prefs", prefs); } catch (e) { this.reportFailure(this.ERROR_PREFS_FAILED); } }, refreshPayload: function () { console.log("AboutHealthReport: page requested fresh payload."); Messaging.sendRequest({ type: EVENT_HEALTH_REQUEST, }); }, updatePayload: function (data) { healthReportWrapper.injectData("payload", data); // Data is supposed to be a string, so the length should be // defined. Just in case, we do this after injecting the data. console.log("AboutHealthReport: sending payload to page " + "(" + typeof(data) + " of length " + data.length + ")."); }, 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); }, showSettings: function () { console.log("AboutHealthReport: showing settings."); Messaging.sendRequest({ type: "Settings:Show", resource: "preferences_vendor", }); }, launchUpdater: function () { console.log("AboutHealthReport: launching updater."); Messaging.sendRequest({ type: "Updater:Launch", }); }, 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; case "ShowSettings": this.showSettings(); break; case "LaunchUpdater": this.launchUpdater(); 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.injectData("begin", null); }, // 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); }, }; window.addEventListener("load", healthReportWrapper.init.bind(healthReportWrapper), false); window.addEventListener("unload", healthReportWrapper.uninit.bind(healthReportWrapper), false);