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"use strict";
6
7const { XPCOMUtils } = ChromeUtils.import(
8  "resource://gre/modules/XPCOMUtils.jsm"
9);
10
11ChromeUtils.defineModuleGetter(
12  this,
13  "Preferences",
14  "resource://gre/modules/Preferences.jsm"
15);
16ChromeUtils.defineModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
17ChromeUtils.defineModuleGetter(
18  this,
19  "TelemetryController",
20  "resource://gre/modules/TelemetryController.jsm"
21);
22ChromeUtils.defineModuleGetter(
23  this,
24  "AppConstants",
25  "resource://gre/modules/AppConstants.jsm"
26);
27ChromeUtils.defineModuleGetter(
28  this,
29  "Services",
30  "resource://gre/modules/Services.jsm"
31);
32
33XPCOMUtils.defineLazyServiceGetter(
34  this,
35  "gUpdateTimerManager",
36  "@mozilla.org/updates/timer-manager;1",
37  "nsIUpdateTimerManager"
38);
39
40var EXPORTED_SYMBOLS = ["TelemetryModules"];
41
42const LOGGER_NAME = "Toolkit.Telemetry";
43const LOGGER_PREFIX = "TelemetryModules::";
44
45// The default is 1 week.
46const MODULES_PING_INTERVAL_SECONDS = 7 * 24 * 60 * 60;
47const MODULES_PING_INTERVAL_PREFERENCE =
48  "toolkit.telemetry.modulesPing.interval";
49
50const MAX_MODULES_NUM = 512;
51const MAX_NAME_LENGTH = 64;
52const TRUNCATION_DELIMITER = "\u2026";
53
54var TelemetryModules = Object.freeze({
55  _log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX),
56
57  start() {
58    // The list of loaded modules is obtainable only when the profiler is enabled.
59    // If it isn't, we don't want to send the ping at all.
60    if (!AppConstants.MOZ_GECKO_PROFILER) {
61      return;
62    }
63
64    // Use nsIUpdateTimerManager for a long-duration timer that survives across sessions.
65    let interval = Preferences.get(
66      MODULES_PING_INTERVAL_PREFERENCE,
67      MODULES_PING_INTERVAL_SECONDS
68    );
69    gUpdateTimerManager.registerTimer(
70      "telemetry_modules_ping",
71      this,
72      interval,
73      interval != 0 // only skip the first interval if the interval is non-0
74    );
75  },
76
77  /**
78   * Called when the 'telemetry_modules_ping' timer fires.
79   */
80  notify() {
81    try {
82      Services.telemetry.getLoadedModules().then(
83        modules => {
84          modules = modules.filter(module => !!module.name.length);
85
86          // Cut the list of modules to MAX_MODULES_NUM entries.
87          if (modules.length > MAX_MODULES_NUM) {
88            modules = modules.slice(0, MAX_MODULES_NUM);
89          }
90
91          // Cut the file names of the modules to MAX_NAME_LENGTH characters.
92          for (let module of modules) {
93            if (module.name.length > MAX_NAME_LENGTH) {
94              module.name =
95                module.name.substr(0, MAX_NAME_LENGTH - 1) +
96                TRUNCATION_DELIMITER;
97            }
98
99            if (
100              module.debugName !== null &&
101              module.debugName.length > MAX_NAME_LENGTH
102            ) {
103              module.debugName =
104                module.debugName.substr(0, MAX_NAME_LENGTH - 1) +
105                TRUNCATION_DELIMITER;
106            }
107
108            if (
109              module.certSubject !== undefined &&
110              module.certSubject.length > MAX_NAME_LENGTH
111            ) {
112              module.certSubject =
113                module.certSubject.substr(0, MAX_NAME_LENGTH - 1) +
114                TRUNCATION_DELIMITER;
115            }
116          }
117
118          TelemetryController.submitExternalPing(
119            "modules",
120            {
121              version: 1,
122              modules,
123            },
124            {
125              addClientId: true,
126              addEnvironment: true,
127            }
128          );
129        },
130        err => this._log.error("notify - promise failed", err)
131      );
132    } catch (ex) {
133      this._log.error("notify - caught exception", ex);
134    }
135  },
136});
137