tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

LaterRun.sys.mjs (6172B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const kEnabledPref = "browser.laterrun.enabled";
      6 const kPagePrefRoot = "browser.laterrun.pages.";
      7 // Number of sessions we've been active in
      8 const kSessionCountPref = "browser.laterrun.bookkeeping.sessionCount";
      9 // Time the profile was created at in seconds:
     10 const kProfileCreationTime = "browser.laterrun.bookkeeping.profileCreationTime";
     11 // Time the update was applied at in seconds:
     12 const kUpdateAppliedTime = "browser.laterrun.bookkeeping.updateAppliedTime";
     13 
     14 // After 50 sessions or 1 month since install, assume we will no longer be
     15 // interested in showing anything to "new" users
     16 const kSelfDestructSessionLimit = 50;
     17 const kSelfDestructHoursLimit = 31 * 24;
     18 
     19 class Page {
     20  constructor({
     21    pref,
     22    minimumHoursSinceInstall,
     23    minimumSessionCount,
     24    requireBoth,
     25    url,
     26  }) {
     27    this.pref = pref;
     28    this.minimumHoursSinceInstall = minimumHoursSinceInstall || 0;
     29    this.minimumSessionCount = minimumSessionCount || 1;
     30    this.requireBoth = requireBoth || false;
     31    this.url = url;
     32  }
     33 
     34  get hasRun() {
     35    return Services.prefs.getBoolPref(this.pref + "hasRun", false);
     36  }
     37 
     38  applies(sessionInfo) {
     39    if (this.hasRun) {
     40      return false;
     41    }
     42    if (this.requireBoth) {
     43      return (
     44        sessionInfo.sessionCount >= this.minimumSessionCount &&
     45        sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall
     46      );
     47    }
     48    return (
     49      sessionInfo.sessionCount >= this.minimumSessionCount ||
     50      sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall
     51    );
     52  }
     53 }
     54 
     55 export let LaterRun = {
     56  get ENABLE_REASON_NEW_PROFILE() {
     57    return 1;
     58  },
     59  get ENABLE_REASON_UPDATE_APPLIED() {
     60    return 2;
     61  },
     62 
     63  init(reason) {
     64    if (!this.enabled) {
     65      return;
     66    }
     67 
     68    if (reason == this.ENABLE_REASON_NEW_PROFILE) {
     69      // If this is the first run, set the time we were installed
     70      if (
     71        Services.prefs.getPrefType(kProfileCreationTime) ==
     72        Ci.nsIPrefBranch.PREF_INVALID
     73      ) {
     74        // We need to store seconds in order to fit within int prefs.
     75        Services.prefs.setIntPref(
     76          kProfileCreationTime,
     77          Math.floor(Date.now() / 1000)
     78        );
     79      }
     80      this.sessionCount++;
     81    } else if (reason == this.ENABLE_REASON_UPDATE_APPLIED) {
     82      Services.prefs.setIntPref(
     83        kUpdateAppliedTime,
     84        Math.floor(Services.startup.getStartupInfo().start.getTime() / 1000)
     85      );
     86    }
     87 
     88    if (
     89      this.hoursSinceInstall > kSelfDestructHoursLimit ||
     90      this.sessionCount > kSelfDestructSessionLimit
     91    ) {
     92      this.selfDestruct();
     93    }
     94  },
     95 
     96  // The enabled, hoursSinceInstall and sessionCount properties mirror the
     97  // preferences system, and are here for convenience.
     98  get enabled() {
     99    return Services.prefs.getBoolPref(kEnabledPref, false);
    100  },
    101 
    102  enable(reason) {
    103    if (!this.enabled) {
    104      Services.prefs.setBoolPref(kEnabledPref, true);
    105      this.init(reason);
    106    }
    107  },
    108 
    109  get hoursSinceInstall() {
    110    let installStampSec = Services.prefs.getIntPref(
    111      kProfileCreationTime,
    112      Date.now() / 1000
    113    );
    114    return Math.floor((Date.now() / 1000 - installStampSec) / 3600);
    115  },
    116 
    117  get hoursSinceUpdate() {
    118    let updateStampSec = Services.prefs.getIntPref(kUpdateAppliedTime, 0);
    119    return Math.floor((Date.now() / 1000 - updateStampSec) / 3600);
    120  },
    121 
    122  get sessionCount() {
    123    if (this._sessionCount) {
    124      return this._sessionCount;
    125    }
    126    return (this._sessionCount = Services.prefs.getIntPref(
    127      kSessionCountPref,
    128      0
    129    ));
    130  },
    131 
    132  set sessionCount(val) {
    133    this._sessionCount = val;
    134    Services.prefs.setIntPref(kSessionCountPref, val);
    135  },
    136 
    137  // Because we don't want to keep incrementing this indefinitely for no reason,
    138  // we will turn ourselves off after a set amount of time/sessions (see top of
    139  // file).
    140  selfDestruct() {
    141    Services.prefs.setBoolPref(kEnabledPref, false);
    142  },
    143 
    144  // Create an array of Page objects based on the currently set prefs
    145  readPages() {
    146    // Enumerate all the pages.
    147    let allPrefsForPages = Services.prefs.getChildList(kPagePrefRoot);
    148    let pageDataStore = new Map();
    149    for (let pref of allPrefsForPages) {
    150      let [slug, prop] = pref.substring(kPagePrefRoot.length).split(".");
    151      if (!pageDataStore.has(slug)) {
    152        pageDataStore.set(slug, {
    153          pref: pref.substring(0, pref.length - prop.length),
    154        });
    155      }
    156      if (prop == "requireBoth" || prop == "hasRun") {
    157        pageDataStore.get(slug)[prop] = Services.prefs.getBoolPref(pref, false);
    158      } else if (prop == "url") {
    159        pageDataStore.get(slug)[prop] = Services.prefs.getStringPref(pref, "");
    160      } else {
    161        pageDataStore.get(slug)[prop] = Services.prefs.getIntPref(pref, 0);
    162      }
    163    }
    164    let rv = [];
    165    for (let [, pageData] of pageDataStore) {
    166      if (pageData.url) {
    167        let urlString = Services.urlFormatter.formatURL(pageData.url.trim());
    168        let uri = URL.parse(urlString)?.URI;
    169        if (!uri) {
    170          console.error(
    171            "Invalid LaterRun page URL ",
    172            pageData.url,
    173            " ignored."
    174          );
    175          continue;
    176        }
    177        if (!uri.schemeIs("https")) {
    178          console.error("Insecure LaterRun page URL ", uri.spec, " ignored.");
    179        } else {
    180          pageData.url = uri.spec;
    181          rv.push(new Page(pageData));
    182        }
    183      }
    184    }
    185    return rv;
    186  },
    187 
    188  // Return a URL for display as a 'later run' page if its criteria are matched,
    189  // or null otherwise.
    190  // NB: will only return one page at a time; if multiple pages match, it's up
    191  // to the preference service which one gets shown first, and the next one
    192  // will be shown next startup instead.
    193  getURL() {
    194    if (!this.enabled) {
    195      return null;
    196    }
    197    let pages = this.readPages();
    198    let page = pages.find(p => p.applies(this));
    199    if (page) {
    200      Services.prefs.setBoolPref(page.pref + "hasRun", true);
    201      return page.url;
    202    }
    203    return null;
    204  },
    205 };
    206 
    207 LaterRun.init();