зеркало из https://github.com/mozilla/gecko-dev.git
288 строки
9.7 KiB
JavaScript
288 строки
9.7 KiB
JavaScript
/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
|
|
/* 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";
|
|
|
|
this.EXPORTED_SYMBOLS = ["BrowserUsageTelemetry"];
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
|
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
|
|
|
// The upper bound for the count of the visited unique domain names.
|
|
const MAX_UNIQUE_VISITED_DOMAINS = 100;
|
|
|
|
// Observed topic names.
|
|
const WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
|
|
const TAB_RESTORING_TOPIC = "SSTabRestoring";
|
|
const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split";
|
|
const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
|
|
|
|
// Probe names.
|
|
const MAX_TAB_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_tab_count";
|
|
const MAX_WINDOW_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_window_count";
|
|
const TAB_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.tab_open_event_count";
|
|
const WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.window_open_event_count";
|
|
const UNIQUE_DOMAINS_COUNT_SCALAR_NAME = "browser.engagement.unique_domains_count";
|
|
const TOTAL_URI_COUNT_SCALAR_NAME = "browser.engagement.total_uri_count";
|
|
|
|
function getOpenTabsAndWinsCounts() {
|
|
let tabCount = 0;
|
|
let winCount = 0;
|
|
|
|
let browserEnum = Services.wm.getEnumerator("navigator:browser");
|
|
while (browserEnum.hasMoreElements()) {
|
|
let win = browserEnum.getNext();
|
|
winCount++;
|
|
tabCount += win.gBrowser.tabs.length;
|
|
}
|
|
|
|
return { tabCount, winCount };
|
|
}
|
|
|
|
let URICountListener = {
|
|
// A set containing the visited domains, see bug 1271310.
|
|
_domainSet: new Set(),
|
|
// A map to keep track of the URIs loaded from the restored tabs.
|
|
_restoredURIsMap: new WeakMap(),
|
|
|
|
isValidURI(uri) {
|
|
// Only consider http(s) schemas.
|
|
return uri.schemeIs("http") || uri.schemeIs("https");
|
|
},
|
|
|
|
addRestoredURI(browser, uri) {
|
|
if (!this.isValidURI(uri)) {
|
|
return;
|
|
}
|
|
|
|
this._restoredURIsMap.set(browser, uri.spec);
|
|
},
|
|
|
|
onLocationChange(browser, webProgress, request, uri, flags) {
|
|
// Don't count this URI if it's an error page.
|
|
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
|
|
return;
|
|
}
|
|
|
|
// We only care about top level loads.
|
|
if (!webProgress.isTopLevel) {
|
|
return;
|
|
}
|
|
|
|
if (!this.isValidURI(uri)) {
|
|
return;
|
|
}
|
|
|
|
// The SessionStore sets the URI of a tab first, firing onLocationChange the
|
|
// first time, then manages content loading using its scheduler. Once content
|
|
// loads, we will hit onLocationChange again.
|
|
// We can catch the first case by checking for null requests: be advised that
|
|
// this can also happen when navigating page fragments, so account for it.
|
|
if (!request &&
|
|
!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
|
|
return;
|
|
}
|
|
|
|
// If the URI we're loading is in the _restoredURIsMap, then it comes from a
|
|
// restored tab. If so, let's skip it and remove it from the map as we want to
|
|
// count page refreshes.
|
|
if (this._restoredURIsMap.get(browser) === uri.spec) {
|
|
this._restoredURIsMap.delete(browser);
|
|
return;
|
|
}
|
|
|
|
// Update the URI counts.
|
|
Services.telemetry.scalarAdd(TOTAL_URI_COUNT_SCALAR_NAME, 1);
|
|
|
|
// We only want to count the unique domains up to MAX_UNIQUE_VISITED_DOMAINS.
|
|
if (this._domainSet.size == MAX_UNIQUE_VISITED_DOMAINS) {
|
|
return;
|
|
}
|
|
|
|
// Unique domains should be aggregated by (eTLD + 1): x.test.com and y.test.com
|
|
// are counted once as test.com.
|
|
try {
|
|
// Even if only considering http(s) URIs, |getBaseDomain| could still throw
|
|
// due to the URI containing invalid characters or the domain actually being
|
|
// an ipv4 or ipv6 address.
|
|
this._domainSet.add(Services.eTLD.getBaseDomain(uri));
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
|
|
Services.telemetry.scalarSet(UNIQUE_DOMAINS_COUNT_SCALAR_NAME, this._domainSet.size);
|
|
},
|
|
|
|
/**
|
|
* Reset the counts. This should be called when breaking a session in Telemetry.
|
|
*/
|
|
reset() {
|
|
this._domainSet.clear();
|
|
},
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
|
Ci.nsISupportsWeakReference]),
|
|
};
|
|
|
|
let BrowserUsageTelemetry = {
|
|
init() {
|
|
Services.obs.addObserver(this, WINDOWS_RESTORED_TOPIC, false);
|
|
},
|
|
|
|
/**
|
|
* Handle subsession splits in the parent process.
|
|
*/
|
|
afterSubsessionSplit() {
|
|
// Scalars just got cleared due to a subsession split. We need to set the maximum
|
|
// concurrent tab and window counts so that they reflect the correct value for the
|
|
// new subsession.
|
|
const counts = getOpenTabsAndWinsCounts();
|
|
Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
|
|
Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
|
|
|
|
// Reset the URI counter.
|
|
URICountListener.reset();
|
|
},
|
|
|
|
uninit() {
|
|
Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC, false);
|
|
Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
|
|
Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC, false);
|
|
},
|
|
|
|
observe(subject, topic, data) {
|
|
switch (topic) {
|
|
case WINDOWS_RESTORED_TOPIC:
|
|
this._setupAfterRestore();
|
|
break;
|
|
case DOMWINDOW_OPENED_TOPIC:
|
|
this._onWindowOpen(subject);
|
|
break;
|
|
case TELEMETRY_SUBSESSIONSPLIT_TOPIC:
|
|
this.afterSubsessionSplit();
|
|
break;
|
|
}
|
|
},
|
|
|
|
handleEvent(event) {
|
|
switch (event.type) {
|
|
case "TabOpen":
|
|
this._onTabOpen();
|
|
break;
|
|
case "unload":
|
|
this._unregisterWindow(event.target);
|
|
break;
|
|
case TAB_RESTORING_TOPIC:
|
|
// We're restoring a new tab from a previous or crashed session.
|
|
// We don't want to track the URIs from these tabs, so let
|
|
// |URICountListener| know about them.
|
|
let browser = event.target.linkedBrowser;
|
|
URICountListener.addRestoredURI(browser, browser.currentURI);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* This gets called shortly after the SessionStore has finished restoring
|
|
* windows and tabs. It counts the open tabs and adds listeners to all the
|
|
* windows.
|
|
*/
|
|
_setupAfterRestore() {
|
|
// Make sure to catch new chrome windows and subsession splits.
|
|
Services.obs.addObserver(this, DOMWINDOW_OPENED_TOPIC, false);
|
|
Services.obs.addObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
|
|
|
|
// Attach the tabopen handlers to the existing Windows.
|
|
let browserEnum = Services.wm.getEnumerator("navigator:browser");
|
|
while (browserEnum.hasMoreElements()) {
|
|
this._registerWindow(browserEnum.getNext());
|
|
}
|
|
|
|
// Get the initial tab and windows max counts.
|
|
const counts = getOpenTabsAndWinsCounts();
|
|
Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
|
|
Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
|
|
},
|
|
|
|
/**
|
|
* Adds listeners to a single chrome window.
|
|
*/
|
|
_registerWindow(win) {
|
|
win.addEventListener("unload", this);
|
|
win.addEventListener("TabOpen", this, true);
|
|
|
|
// Don't include URI and domain counts when in private mode.
|
|
if (PrivateBrowsingUtils.isWindowPrivate(win)) {
|
|
return;
|
|
}
|
|
win.gBrowser.tabContainer.addEventListener(TAB_RESTORING_TOPIC, this);
|
|
win.gBrowser.addTabsProgressListener(URICountListener);
|
|
},
|
|
|
|
/**
|
|
* Removes listeners from a single chrome window.
|
|
*/
|
|
_unregisterWindow(win) {
|
|
win.removeEventListener("unload", this);
|
|
win.removeEventListener("TabOpen", this, true);
|
|
|
|
// Don't include URI and domain counts when in private mode.
|
|
if (PrivateBrowsingUtils.isWindowPrivate(win.defaultView)) {
|
|
return;
|
|
}
|
|
win.defaultView.gBrowser.tabContainer.removeEventListener(TAB_RESTORING_TOPIC, this);
|
|
win.defaultView.gBrowser.removeTabsProgressListener(URICountListener);
|
|
},
|
|
|
|
/**
|
|
* Updates the tab counts.
|
|
* @param {Number} [newTabCount=0] The count of the opened tabs across all windows. This
|
|
* is computed manually if not provided.
|
|
*/
|
|
_onTabOpen(tabCount = 0) {
|
|
// Use the provided tab count if available. Otherwise, go on and compute it.
|
|
tabCount = tabCount || getOpenTabsAndWinsCounts().tabCount;
|
|
// Update the "tab opened" count and its maximum.
|
|
Services.telemetry.scalarAdd(TAB_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
|
|
Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, tabCount);
|
|
},
|
|
|
|
/**
|
|
* Tracks the window count and registers the listeners for the tab count.
|
|
* @param{Object} win The window object.
|
|
*/
|
|
_onWindowOpen(win) {
|
|
// Make sure to have a |nsIDOMWindow|.
|
|
if (!(win instanceof Ci.nsIDOMWindow)) {
|
|
return;
|
|
}
|
|
|
|
let onLoad = () => {
|
|
win.removeEventListener("load", onLoad, false);
|
|
|
|
// Ignore non browser windows.
|
|
if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
|
|
return;
|
|
}
|
|
|
|
this._registerWindow(win);
|
|
// Track the window open event and check the maximum.
|
|
const counts = getOpenTabsAndWinsCounts();
|
|
Services.telemetry.scalarAdd(WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
|
|
Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
|
|
|
|
// We won't receive the "TabOpen" event for the first tab within a new window.
|
|
// Account for that.
|
|
this._onTabOpen(counts.tabCount);
|
|
};
|
|
win.addEventListener("load", onLoad, false);
|
|
},
|
|
};
|