/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* 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/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const lazy = {}; // The maximum number of concurrently-loaded origins allowed in order to // qualify for the Fission rollout experiment. XPCOMUtils.defineLazyPreferenceGetter( lazy, "fissionExperimentMaxOrigins", "fission.experiment.max-origins.origin-cap", 30 ); // The length of the sliding window during which a user must stay below // the max origin cap. If the last time a user passed the max origin cap // fell outside of this window, they will requalify for the experiment. XPCOMUtils.defineLazyPreferenceGetter( lazy, "fissionExperimentSlidingWindowMS", "fission.experiment.max-origins.sliding-window-ms", 7 * 24 * 60 * 60 * 1000 ); // The pref holding the current qaualification state of the user. If // true, the user is currently qualified from the experiment. const FISSION_EXPERIMENT_PREF_QUALIFIED = "fission.experiment.max-origins.qualified"; XPCOMUtils.defineLazyPreferenceGetter( lazy, "fissionExperimentQualified", FISSION_EXPERIMENT_PREF_QUALIFIED, false ); // The pref holding the timestamp of the last time we saw an origin // count below the cap while the user was not currently marked as // qualified. const FISSION_EXPERIMENT_PREF_LAST_QUALIFIED = "fission.experiment.max-origins.last-qualified"; XPCOMUtils.defineLazyPreferenceGetter( lazy, "fissionExperimentLastQualified", FISSION_EXPERIMENT_PREF_LAST_QUALIFIED, 0 ); // The pref holding the timestamp of the last time we saw an origin // count exceeding the cap. const FISSION_EXPERIMENT_PREF_LAST_DISQUALIFIED = "fission.experiment.max-origins.last-disqualified"; XPCOMUtils.defineLazyPreferenceGetter( lazy, "fissionExperimentLastDisqualified", FISSION_EXPERIMENT_PREF_LAST_DISQUALIFIED, 0 ); export var BrowserTelemetryUtils = { recordSiteOriginTelemetry(aWindows, aIsGeckoView) { Services.tm.idleDispatchToMainThread(() => { this._recordSiteOriginTelemetry(aWindows, aIsGeckoView); }); }, computeSiteOriginCount(aWindows, aIsGeckoView) { // Geckoview and Desktop work differently. On desktop, aBrowser objects // holds an array of tabs which we can use to get the objects. // In Geckoview, it is apps' responsibility to keep track of the tabs, so // there isn't an easy way for us to get the tabs. let tabs = []; if (aIsGeckoView) { // To get all active windows; Each tab has its own window tabs = aWindows; } else { for (const win of aWindows) { tabs = tabs.concat(win.gBrowser.tabs); } } let topLevelBCs = []; for (const tab of tabs) { let browser; if (aIsGeckoView) { browser = tab.browser; } else { browser = tab.linkedBrowser; } if (browser.browsingContext) { // This is the top level browsingContext topLevelBCs.push(browser.browsingContext); } } return CanonicalBrowsingContext.countSiteOrigins(topLevelBCs); }, _recordSiteOriginTelemetry(aWindows, aIsGeckoView) { let currentTime = Date.now(); // default is 5 minutes if (!this.min_interval) { this.min_interval = Services.prefs.getIntPref( "telemetry.number_of_site_origin.min_interval", 300000 ); } let originCount = this.computeSiteOriginCount(aWindows, aIsGeckoView); let histogram = Services.telemetry.getHistogramById( "FX_NUMBER_OF_UNIQUE_SITE_ORIGINS_ALL_TABS" ); // Discard the first load because most of the time the first load only has 1 // tab and 1 window open, so it is useless to report it. if (!this._lastRecordSiteOrigin) { this._lastRecordSiteOrigin = currentTime; } else if (currentTime >= this._lastRecordSiteOrigin + this.min_interval) { this._lastRecordSiteOrigin = currentTime; histogram.add(originCount); } // Update the Fission experiment qualification state based on the // current origin count: // If we don't already have a last disqualification timestamp, look // through the existing histogram values, and use the existing // maximum value as the initial count. This will prevent us from // enrolling users in the experiment if they have a history of // exceeding our origin cap. if (!this._checkedInitialExperimentQualification) { this._checkedInitialExperimentQualification = true; if ( !Services.prefs.prefHasUserValue( FISSION_EXPERIMENT_PREF_LAST_DISQUALIFIED ) ) { for (let [bucketStr, entryCount] of Object.entries( histogram.snapshot().values )) { let bucket = Number(bucketStr); if (bucket > originCount && entryCount > 0) { originCount = bucket; } } Services.prefs.setIntPref(FISSION_EXPERIMENT_PREF_LAST_DISQUALIFIED, 0); } } let currentTimeSec = currentTime / 1000; if (originCount < lazy.fissionExperimentMaxOrigins) { let lastDisqualified = lazy.fissionExperimentLastDisqualified; let lastQualified = lazy.fissionExperimentLastQualified; // If the last time we saw a qualifying origin count was earlier // than the last time we say a disqualifying count, update any // existing last disqualified timestamp to just before now, on the // basis that our origin count has probably just fallen below the // cap. if (lastDisqualified > 0 && lastQualified <= lastDisqualified) { Services.prefs.setIntPref( FISSION_EXPERIMENT_PREF_LAST_DISQUALIFIED, currentTimeSec - 1 ); } if (!lazy.fissionExperimentQualified) { Services.prefs.setIntPref( FISSION_EXPERIMENT_PREF_LAST_QUALIFIED, currentTimeSec ); // We have a qualifying origin count now. If the last time we were // disqualified was prior to the start of our current sliding // window, re-qualify the user. if ( currentTimeSec - lastDisqualified >= lazy.fissionExperimentSlidingWindowMS / 1000 ) { Services.prefs.setBoolPref(FISSION_EXPERIMENT_PREF_QUALIFIED, true); } } } else { Services.prefs.setIntPref( FISSION_EXPERIMENT_PREF_LAST_DISQUALIFIED, currentTimeSec ); Services.prefs.setBoolPref(FISSION_EXPERIMENT_PREF_QUALIFIED, false); } }, };