tor-browser

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

AboutNewTab.sys.mjs (8934B)


      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 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 
      8 const lazy = {};
      9 
     10 ChromeUtils.defineESModuleGetters(lazy, {
     11  AboutNewTabResourceMapping:
     12    "resource:///modules/AboutNewTabResourceMapping.sys.mjs",
     13  ActivityStream: "resource://newtab/lib/ActivityStream.sys.mjs",
     14  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
     15  TelemetryReportingPolicy:
     16    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
     17 });
     18 
     19 const ABOUT_URL = "about:newtab";
     20 const PREF_ACTIVITY_STREAM_DEBUG = "browser.newtabpage.activity-stream.debug";
     21 // AboutHomeStartupCache needs us in "quit-application", so stay alive longer.
     22 // TODO: We could better have a shared async shutdown blocker?
     23 const TOPIC_APP_QUIT = "profile-before-change";
     24 
     25 export const AboutNewTab = {
     26  QueryInterface: ChromeUtils.generateQI([
     27    "nsIObserver",
     28    "nsISupportsWeakReference",
     29  ]),
     30 
     31  // AboutNewTab
     32  initialized: false,
     33 
     34  willNotifyUser: false,
     35 
     36  _activityStreamEnabled: false,
     37  activityStream: null,
     38  activityStreamDebug: false,
     39 
     40  _cachedTopSites: null,
     41 
     42  _newTabURL: ABOUT_URL,
     43  _newTabURLOverridden: false,
     44 
     45  /**
     46   * init - Initializes an instance of Activity Stream if one doesn't exist already.
     47   */
     48  init() {
     49    if (this.initialized) {
     50      return;
     51    }
     52 
     53    Services.obs.addObserver(this, TOPIC_APP_QUIT);
     54    if (!AppConstants.RELEASE_OR_BETA) {
     55      XPCOMUtils.defineLazyPreferenceGetter(
     56        this,
     57        "activityStreamDebug",
     58        PREF_ACTIVITY_STREAM_DEBUG,
     59        false,
     60        () => {
     61          this.notifyChange();
     62        }
     63      );
     64    }
     65 
     66    XPCOMUtils.defineLazyPreferenceGetter(
     67      this,
     68      "privilegedAboutProcessEnabled",
     69      "browser.tabs.remote.separatePrivilegedContentProcess",
     70      false,
     71      () => {
     72        this.notifyChange();
     73      }
     74    );
     75 
     76    // Make sure to register newtab resource mapping as early as possible
     77    // on startup.
     78    if (AppConstants.BROWSER_NEWTAB_AS_ADDON) {
     79      lazy.AboutNewTabResourceMapping.init();
     80    }
     81 
     82    // More initialization happens here
     83    this.toggleActivityStream(true);
     84    this.initialized = true;
     85 
     86    Services.obs.addObserver(
     87      this,
     88      lazy.TelemetryReportingPolicy.TELEMETRY_TOU_ACCEPTED_OR_INELIGIBLE
     89    );
     90  },
     91 
     92  /**
     93   * React to changes to the activity stream being enabled or not.
     94   *
     95   * This will only act if there is a change of state and if not overridden.
     96   *
     97   * @returns {boolean} Returns if there has been a state change
     98   *
     99   * @param {boolean}   stateEnabled    activity stream enabled state to set to
    100   * @param {boolean}   forceState      force state change
    101   */
    102  toggleActivityStream(stateEnabled, forceState = false) {
    103    if (
    104      !forceState &&
    105      (this._newTabURLOverridden ||
    106        stateEnabled === this._activityStreamEnabled)
    107    ) {
    108      // exit there is no change of state
    109      return false;
    110    }
    111    if (stateEnabled) {
    112      this._activityStreamEnabled = true;
    113    } else {
    114      this._activityStreamEnabled = false;
    115    }
    116 
    117    this._newTabURL = ABOUT_URL;
    118    return true;
    119  },
    120 
    121  get newTabURL() {
    122    return this._newTabURL;
    123  },
    124 
    125  set newTabURL(aNewTabURL) {
    126    let newTabURL = aNewTabURL.trim();
    127    if (newTabURL === ABOUT_URL) {
    128      // avoid infinite redirects in case one sets the URL to about:newtab
    129      this.resetNewTabURL();
    130      return;
    131    } else if (newTabURL === "") {
    132      newTabURL = "about:blank";
    133    }
    134 
    135    this.toggleActivityStream(false);
    136    this._newTabURL = newTabURL;
    137    this._newTabURLOverridden = true;
    138    this.notifyChange();
    139  },
    140 
    141  get newTabURLOverridden() {
    142    return this._newTabURLOverridden;
    143  },
    144 
    145  get activityStreamEnabled() {
    146    return this._activityStreamEnabled;
    147  },
    148 
    149  resetNewTabURL() {
    150    this._newTabURLOverridden = false;
    151    this._newTabURL = ABOUT_URL;
    152    this.toggleActivityStream(true, true);
    153    this.notifyChange();
    154  },
    155 
    156  notifyChange() {
    157    Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
    158  },
    159 
    160  /**
    161   * onBrowserReady - Continues the initialization of Activity Stream after browser is ready.
    162   */
    163  async onBrowserReady() {
    164    if (AppConstants.BASE_BROWSER_VERSION) {
    165      // Do not initialise ActivityStream, which we do not want and is not
    166      // available. tor-browser#43886.
    167      return;
    168    }
    169    if (this.activityStream && this.activityStream.initialized) {
    170      return;
    171    }
    172 
    173    if (AppConstants.BROWSER_NEWTAB_AS_ADDON) {
    174      // Wait until the built-in addon has reported that it has finished
    175      // initializing.
    176      let redirector = Cc[
    177        "@mozilla.org/network/protocol/about;1?what=newtab"
    178      ].getService(Ci.nsIAboutModule).wrappedJSObject;
    179 
    180      await redirector.promiseBuiltInAddonInitialized;
    181      lazy.AboutNewTabResourceMapping.scheduleUpdateTrainhopAddonState();
    182    } else {
    183      // We may have had the built-in addon installed in the past. Since the
    184      // flag is false, let's go ahead and remove it. We don't need to await on
    185      // this since the extension should be inert if the build flag is false.
    186      lazy.AboutNewTabResourceMapping.uninstallAddon();
    187    }
    188 
    189    try {
    190      this.activityStream = new lazy.ActivityStream();
    191      Glean.newtab.activityStreamCtorSuccess.set(true);
    192    } catch (error) {
    193      // Send Activity Stream loading failure telemetry
    194      // This probe will help to monitor if ActivityStream failure has crossed
    195      // a threshold and send alert. See Bug 1965278
    196      Glean.newtab.activityStreamCtorSuccess.set(false);
    197      console.error(error);
    198      throw error;
    199    }
    200 
    201    try {
    202      this.activityStream.init();
    203      this._subscribeToActivityStream();
    204    } catch (e) {
    205      console.error(e);
    206    }
    207  },
    208 
    209  _subscribeToActivityStream() {
    210    let unsubscribe = this.activityStream.store.subscribe(() => {
    211      // If the top sites changed, broadcast "newtab-top-sites-changed". We
    212      // ignore changes to the `screenshot` property in each site because
    213      // screenshots are generated at times that are hard to predict and it ends
    214      // up interfering with tests that rely on "newtab-top-sites-changed".
    215      // Observers likely don't care about screenshots anyway.
    216      let topSites = this.activityStream.store
    217        .getState()
    218        .TopSites.rows.map(site => {
    219          site = { ...site };
    220          delete site.screenshot;
    221          return site;
    222        });
    223      if (!lazy.ObjectUtils.deepEqual(topSites, this._cachedTopSites)) {
    224        this._cachedTopSites = topSites;
    225        Services.obs.notifyObservers(null, "newtab-top-sites-changed");
    226      }
    227    });
    228    this._unsubscribeFromActivityStream = () => {
    229      try {
    230        unsubscribe();
    231      } catch (e) {
    232        console.error(e);
    233      }
    234    };
    235  },
    236 
    237  /**
    238   * uninit - Uninitializes Activity Stream if it exists.
    239   */
    240  uninit() {
    241    if (this.activityStream) {
    242      this._unsubscribeFromActivityStream?.();
    243      this.activityStream.uninit();
    244      this.activityStream = null;
    245    }
    246    try {
    247      Services.obs.removeObserver(this, TOPIC_APP_QUIT);
    248      Services.obs.removeObserver(
    249        this,
    250        lazy.TelemetryReportingPolicy.TELEMETRY_TOU_ACCEPTED_OR_INELIGIBLE
    251      );
    252    } catch (e) {
    253      // If init failed before registering these observers, removeObserver may throw.
    254      // Safe to ignore during shutdown.
    255    }
    256 
    257    this.initialized = false;
    258  },
    259 
    260  getTopSites() {
    261    return this.activityStream
    262      ? this.activityStream.store.getState().TopSites.rows
    263      : [];
    264  },
    265 
    266  _alreadyRecordedTopsitesPainted: false,
    267  _nonDefaultStartup: false,
    268 
    269  noteNonDefaultStartup() {
    270    this._nonDefaultStartup = true;
    271  },
    272 
    273  maybeRecordTopsitesPainted(timestamp) {
    274    if (this._alreadyRecordedTopsitesPainted || this._nonDefaultStartup) {
    275      return;
    276    }
    277 
    278    let startupInfo = Services.startup.getStartupInfo();
    279    let processStartTs = startupInfo.process.getTime();
    280    let delta = Math.round(timestamp - processStartTs);
    281    Glean.timestamps.aboutHomeTopsitesFirstPaint.set(delta);
    282    ChromeUtils.addProfilerMarker("aboutHomeTopsitesFirstPaint");
    283    this._alreadyRecordedTopsitesPainted = true;
    284  },
    285 
    286  // nsIObserver implementation
    287 
    288  observe(subject, topic) {
    289    switch (topic) {
    290      case TOPIC_APP_QUIT: {
    291        this.uninit();
    292        break;
    293      }
    294      case lazy.TelemetryReportingPolicy.TELEMETRY_TOU_ACCEPTED_OR_INELIGIBLE: {
    295        Services.obs.removeObserver(
    296          this,
    297          lazy.TelemetryReportingPolicy.TELEMETRY_TOU_ACCEPTED_OR_INELIGIBLE
    298        );
    299 
    300        // Avoid running synchronously during this event that's used for timing
    301        Services.tm.dispatchToMainThread(() => this.onBrowserReady());
    302        break;
    303      }
    304    }
    305  },
    306 };