1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5/* 6 * This module sends Origin Telemetry periodically: 7 * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/prio-ping.html 8 */ 9 10"use strict"; 11 12var EXPORTED_SYMBOLS = ["TelemetryPrioPing", "Policy"]; 13 14const { XPCOMUtils } = ChromeUtils.import( 15 "resource://gre/modules/XPCOMUtils.jsm" 16); 17 18XPCOMUtils.defineLazyModuleGetters(this, { 19 TelemetryController: "resource://gre/modules/TelemetryController.jsm", 20 Log: "resource://gre/modules/Log.jsm", 21}); 22 23const { TelemetryUtils } = ChromeUtils.import( 24 "resource://gre/modules/TelemetryUtils.jsm" 25); 26const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 27 28const Utils = TelemetryUtils; 29 30const LOGGER_NAME = "Toolkit.Telemetry"; 31const LOGGER_PREFIX = "TelemetryPrioPing"; 32 33// Triggered from native Origin Telemetry storage. 34const PRIO_LIMIT_REACHED_TOPIC = "origin-telemetry-storage-limit-reached"; 35 36const PRIO_PING_VERSION = "1"; 37 38var Policy = { 39 sendPing: (type, payload, options) => 40 TelemetryController.submitExternalPing(type, payload, options), 41 getEncodedOriginSnapshot: async aClear => 42 Services.telemetry.getEncodedOriginSnapshot(aClear), 43}; 44 45var TelemetryPrioPing = { 46 Reason: Object.freeze({ 47 PERIODIC: "periodic", // Sent the ping containing Origin Telemetry from the past periodic interval (default 24h). 48 MAX: "max", // Sent the ping containing at least the maximum number (default 10) of prioData elements, earlier than the periodic interval. 49 SHUTDOWN: "shutdown", // Recorded data was sent on shutdown. 50 }), 51 52 PRIO_PING_TYPE: "prio", 53 54 _logger: null, 55 _testing: false, 56 _timeoutId: null, 57 58 startup() { 59 if (!this._testing && !Services.telemetry.canRecordPrereleaseData) { 60 this._log.trace("Extended collection disabled. Prio ping disabled."); 61 return; 62 } 63 64 if ( 65 !this._testing && 66 !Services.prefs.getBoolPref(Utils.Preferences.PrioPingEnabled, true) 67 ) { 68 this._log.trace("Prio ping disabled by pref."); 69 return; 70 } 71 this._log.trace("Starting up."); 72 73 Services.obs.addObserver(this, PRIO_LIMIT_REACHED_TOPIC); 74 }, 75 76 async shutdown() { 77 this._log.trace("Shutting down."); 78 // removeObserver may throw, which could interrupt shutdown. 79 try { 80 Services.obs.removeObserver(this, PRIO_LIMIT_REACHED_TOPIC); 81 } catch (ex) {} 82 83 await this._submitPing(this.Reason.SHUTDOWN); 84 }, 85 86 observe(aSubject, aTopic, aData) { 87 switch (aTopic) { 88 case PRIO_LIMIT_REACHED_TOPIC: 89 this._log.trace("prio limit reached"); 90 this._submitPing(this.Reason.MAX); 91 break; 92 } 93 }, 94 95 periodicPing() { 96 this._log.trace("periodic ping triggered"); 97 this._submitPing(this.Reason.PERIODIC); 98 }, 99 100 /** 101 * Submits an "prio" ping and restarts the timer for the next interval. 102 * 103 * @param {String} reason The reason we're sending the ping. One of TelemetryPrioPing.Reason. 104 */ 105 async _submitPing(reason) { 106 this._log.trace("_submitPing"); 107 108 let snapshot = await Policy.getEncodedOriginSnapshot(true /* clear */); 109 110 if (!this._testing) { 111 snapshot = snapshot.filter( 112 ({ encoding }) => !encoding.startsWith("telemetry.test") 113 ); 114 } 115 116 if (snapshot.length === 0) { 117 // Don't send a ping if we haven't anything to send 118 this._log.trace("nothing to send"); 119 return; 120 } 121 122 let payload = { 123 version: PRIO_PING_VERSION, 124 reason, 125 prioData: snapshot, 126 }; 127 128 const options = { 129 addClientId: false, 130 addEnvironment: false, 131 usePingSender: reason === this.Reason.SHUTDOWN, 132 }; 133 134 Policy.sendPing(this.PRIO_PING_TYPE, payload, options); 135 }, 136 137 /** 138 * Test-only, restore to initial state. 139 */ 140 testReset() { 141 this._testing = true; 142 }, 143 144 get _log() { 145 if (!this._logger) { 146 this._logger = Log.repository.getLoggerWithMessagePrefix( 147 LOGGER_NAME, 148 LOGGER_PREFIX + "::" 149 ); 150 } 151 152 return this._logger; 153 }, 154}; 155