summaryrefslogtreecommitdiffstats
path: root/browser/modules/test/browser_UsageTelemetry.js
blob: a84f33a9751c8f9342a442da9201490808aa34af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
"use strict";

const MAX_CONCURRENT_TABS = "browser.engagement.max_concurrent_tab_count";
const TAB_EVENT_COUNT = "browser.engagement.tab_open_event_count";
const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count";
const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count";
const TOTAL_URI_COUNT = "browser.engagement.total_uri_count";
const UNIQUE_DOMAINS_COUNT = "browser.engagement.unique_domains_count";
const UNFILTERED_URI_COUNT = "browser.engagement.unfiltered_uri_count";

const TELEMETRY_SUBSESSION_TOPIC = "internal-telemetry-after-subsession-split";

/**
 * Waits for the web progress listener associated with this tab to fire an
 * onLocationChange for a non-error page.
 *
 * @param {xul:browser} browser
 *        A xul:browser.
 *
 * @return {Promise}
 * @resolves When navigating to a non-error page.
 */
function browserLocationChanged(browser) {
  return new Promise(resolve => {
    let wpl = {
      onStateChange() {},
      onSecurityChange() {},
      onStatusChange() {},
      onLocationChange(aWebProgress, aRequest, aURI, aFlags) {
        if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE)) {
          browser.webProgress.removeProgressListener(filter);
          filter.removeProgressListener(wpl);
          resolve();
        }
      },
      QueryInterface: XPCOMUtils.generateQI([
        Ci.nsIWebProgressListener,
        Ci.nsIWebProgressListener2,
      ]),
    };
    const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
                     .createInstance(Ci.nsIWebProgress);
    filter.addProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL);
    browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
  });
}

/**
 * An helper that checks the value of a scalar if it's expected to be > 0,
 * otherwise makes sure that the scalar it's not reported.
 */
let checkScalar = (scalars, scalarName, value, msg) => {
  if (value > 0) {
    is(scalars[scalarName], value, msg);
    return;
  }
  ok(!(scalarName in scalars), scalarName + " must not be reported.");
};

/**
 * Get a snapshot of the scalars and check them against the provided values.
 */
let checkScalars = (countsObject) => {
  const scalars =
    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);

  // Check the expected values. Scalars that are never set must not be reported.
  checkScalar(scalars, MAX_CONCURRENT_TABS, countsObject.maxTabs,
              "The maximum tab count must match the expected value.");
  checkScalar(scalars, TAB_EVENT_COUNT, countsObject.tabOpenCount,
              "The number of open tab event count must match the expected value.");
  checkScalar(scalars, MAX_CONCURRENT_WINDOWS, countsObject.maxWindows,
              "The maximum window count must match the expected value.");
  checkScalar(scalars, WINDOW_OPEN_COUNT, countsObject.windowsOpenCount,
              "The number of window open event count must match the expected value.");
  checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
              "The total URI count must match the expected value.");
  checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
              "The unique domains count must match the expected value.");
  checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
              "The unfiltered URI count must match the expected value.");
};

add_task(function* test_tabsAndWindows() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();

  let openedTabs = [];
  let expectedTabOpenCount = 0;
  let expectedWinOpenCount = 0;
  let expectedMaxTabs = 0;
  let expectedMaxWins = 0;

  // Add a new tab and check that the count is right.
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
  expectedTabOpenCount = 1;
  expectedMaxTabs = 2;
  // This, and all the checks below, also check that initial pages (about:newtab, about:blank, ..)
  // are not counted by the total_uri_count and the unfiltered_uri_count probes.
  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
                totalUnfilteredURIs: 0});

  // Add two new tabs in the same window.
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
  expectedTabOpenCount += 2;
  expectedMaxTabs += 2;
  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
                totalUnfilteredURIs: 0});

  // Add a new window and then some tabs in it. An empty new windows counts as a tab.
  let win = yield BrowserTestUtils.openNewBrowserWindow();
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
  // The new window started with a new tab, so account for it.
  expectedTabOpenCount += 4;
  expectedWinOpenCount += 1;
  expectedMaxWins = 2;
  expectedMaxTabs += 4;

  // Remove a tab from the first window, the max shouldn't change.
  yield BrowserTestUtils.removeTab(openedTabs.pop());
  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
                totalUnfilteredURIs: 0});

  // Remove all the extra windows and tabs.
  for (let tab of openedTabs) {
    yield BrowserTestUtils.removeTab(tab);
  }
  yield BrowserTestUtils.closeWindow(win);

  // Make sure all the scalars still have the expected values.
  checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
                windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
                totalUnfilteredURIs: 0});
});

add_task(function* test_subsessionSplit() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();

  // Add a new window (that will have 4 tabs).
  let win = yield BrowserTestUtils.openNewBrowserWindow();
  let openedTabs = [];
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:mozilla"));
  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://www.example.com"));

  // Check that the scalars have the right values. We expect 2 unfiltered URI loads
  // (about:mozilla and www.example.com, but no about:blank) and 1 URI totalURIs
  // (only www.example.com).
  checkScalars({maxTabs: 5, tabOpenCount: 4, maxWindows: 2, windowsOpenCount: 1,
                totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 2});

  // Remove a tab.
  yield BrowserTestUtils.removeTab(openedTabs.pop());

  // Simulate a subsession split by clearing the scalars (via |snapshotScalars|) and
  // notifying the subsession split topic.
  Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                     true /* clearScalars */);
  Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC, "");

  // After a subsession split, only the MAX_CONCURRENT_* scalars must be available
  // and have the correct value. No tabs, windows or URIs were opened so other scalars
  // must not be reported.
  checkScalars({maxTabs: 4, tabOpenCount: 0, maxWindows: 2, windowsOpenCount: 0,
                totalURIs: 0, domainCount: 0, totalUnfilteredURIs: 0});

  // Remove all the extra windows and tabs.
  for (let tab of openedTabs) {
    yield BrowserTestUtils.removeTab(tab);
  }
  yield BrowserTestUtils.closeWindow(win);
});

add_task(function* test_URIAndDomainCounts() {
  // Let's reset the counts.
  Services.telemetry.clearScalars();

  let checkCounts = (countsObject) => {
    // Get a snapshot of the scalars and then clear them.
    const scalars =
      Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
    checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
                "The URI scalar must contain the expected value.");
    checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
                "The unique domains scalar must contain the expected value.");
    checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
                "The unfiltered URI scalar must contain the expected value.");
  };

  // Check that about:blank doesn't get counted in the URI total.
  let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
  checkCounts({totalURIs: 0, domainCount: 0, totalUnfilteredURIs: 0});

  // Open a different page and check the counts.
  yield BrowserTestUtils.loadURI(firstTab.linkedBrowser, "http://example.com/");
  yield BrowserTestUtils.browserLoaded(firstTab.linkedBrowser);
  checkCounts({totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 1});

  // Activating a different tab must not increase the URI count.
  let secondTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
  yield BrowserTestUtils.switchTab(gBrowser, firstTab);
  checkCounts({totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 1});
  yield BrowserTestUtils.removeTab(secondTab);

  // Open a new window and set the tab to a new address.
  let newWin = yield BrowserTestUtils.openNewBrowserWindow();
  yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/");
  yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
  checkCounts({totalURIs: 2, domainCount: 1, totalUnfilteredURIs: 2});

  // We should not count AJAX requests.
  const XHR_URL = "http://example.com/r";
  yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, XHR_URL, function(url) {
    return new Promise(resolve => {
      var xhr = new content.window.XMLHttpRequest();
      xhr.open("GET", url);
      xhr.onload = () => resolve();
      xhr.send();
    });
  });
  checkCounts({totalURIs: 2, domainCount: 1, totalUnfilteredURIs: 2});

  // Check that we're counting page fragments.
  let loadingStopped = browserLocationChanged(newWin.gBrowser.selectedBrowser);
  yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/#2");
  yield loadingStopped;
  checkCounts({totalURIs: 3, domainCount: 1, totalUnfilteredURIs: 3});

  // Check that a different URI from the example.com domain doesn't increment the unique count.
  yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://test1.example.com/");
  yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
  checkCounts({totalURIs: 4, domainCount: 1, totalUnfilteredURIs: 4});

  // Make sure that the unique domains counter is incrementing for a different domain.
  yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "https://example.org/");
  yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
  checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 5});

  // Check that we only account for top level loads (e.g. we don't count URIs from
  // embedded iframes).
  yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, null, function* () {
    let doc = content.document;
    let iframe = doc.createElement("iframe");
    let promiseIframeLoaded = ContentTaskUtils.waitForEvent(iframe, "load", false);
    iframe.src = "https://example.org/test";
    doc.body.insertBefore(iframe, doc.body.firstChild);
    yield promiseIframeLoaded;
  });
  checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 5});

  // Check that uncommon protocols get counted in the unfiltered URI probe.
  const TEST_PAGE =
    "data:text/html,<a id='target' href='%23par1'>Click me</a><a name='par1'>The paragraph.</a>";
  yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, TEST_PAGE);
  yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
  checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 6});

  // Clean up.
  yield BrowserTestUtils.removeTab(firstTab);
  yield BrowserTestUtils.closeWindow(newWin);
});