summaryrefslogtreecommitdiffstats
path: root/browser/modules/AboutNewTab.jsm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/modules/AboutNewTab.jsm256
1 files changed, 256 insertions, 0 deletions
diff --git a/browser/modules/AboutNewTab.jsm b/browser/modules/AboutNewTab.jsm
new file mode 100644
index 0000000000..07d3d9b1bd
--- /dev/null
+++ b/browser/modules/AboutNewTab.jsm
@@ -0,0 +1,256 @@
+/* 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";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ ActivityStream: "resource://activity-stream/lib/ActivityStream.jsm",
+ ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
+});
+
+const ABOUT_URL = "about:newtab";
+const PREF_ACTIVITY_STREAM_DEBUG = "browser.newtabpage.activity-stream.debug";
+const TOPIC_APP_QUIT = "quit-application-granted";
+const BROWSER_READY_NOTIFICATION = "sessionstore-windows-restored";
+
+const AboutNewTab = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ // AboutNewTab
+ initialized: false,
+
+ willNotifyUser: false,
+
+ _activityStreamEnabled: false,
+ activityStream: null,
+ activityStreamDebug: false,
+
+ _cachedTopSites: null,
+
+ _newTabURL: ABOUT_URL,
+ _newTabURLOverridden: false,
+
+ /**
+ * init - Initializes an instance of Activity Stream if one doesn't exist already.
+ */
+ init() {
+ Services.obs.addObserver(this, TOPIC_APP_QUIT);
+ if (!AppConstants.RELEASE_OR_BETA) {
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "activityStreamDebug",
+ PREF_ACTIVITY_STREAM_DEBUG,
+ false,
+ () => {
+ this.notifyChange();
+ }
+ );
+ }
+
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "privilegedAboutProcessEnabled",
+ "browser.tabs.remote.separatePrivilegedContentProcess",
+ false,
+ () => {
+ this.notifyChange();
+ }
+ );
+
+ // More initialization happens here
+ this.toggleActivityStream(true);
+ this.initialized = true;
+
+ Services.obs.addObserver(this, BROWSER_READY_NOTIFICATION);
+ },
+
+ /**
+ * React to changes to the activity stream being enabled or not.
+ *
+ * This will only act if there is a change of state and if not overridden.
+ *
+ * @returns {Boolean} Returns if there has been a state change
+ *
+ * @param {Boolean} stateEnabled activity stream enabled state to set to
+ * @param {Boolean} forceState force state change
+ */
+ toggleActivityStream(stateEnabled, forceState = false) {
+ if (
+ !forceState &&
+ (this._newTabURLOverridden ||
+ stateEnabled === this._activityStreamEnabled)
+ ) {
+ // exit there is no change of state
+ return false;
+ }
+ if (stateEnabled) {
+ this._activityStreamEnabled = true;
+ } else {
+ this._activityStreamEnabled = false;
+ }
+
+ this._newTabURL = ABOUT_URL;
+ return true;
+ },
+
+ get newTabURL() {
+ return this._newTabURL;
+ },
+
+ set newTabURL(aNewTabURL) {
+ let newTabURL = aNewTabURL.trim();
+ if (newTabURL === ABOUT_URL) {
+ // avoid infinite redirects in case one sets the URL to about:newtab
+ this.resetNewTabURL();
+ return;
+ } else if (newTabURL === "") {
+ newTabURL = "about:blank";
+ }
+
+ this.toggleActivityStream(false);
+ this._newTabURL = newTabURL;
+ this._newTabURLOverridden = true;
+ this.notifyChange();
+ },
+
+ get newTabURLOverridden() {
+ return this._newTabURLOverridden;
+ },
+
+ get activityStreamEnabled() {
+ return this._activityStreamEnabled;
+ },
+
+ resetNewTabURL() {
+ this._newTabURLOverridden = false;
+ this._newTabURL = ABOUT_URL;
+ this.toggleActivityStream(true, true);
+ this.notifyChange();
+ },
+
+ notifyChange() {
+ Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
+ },
+
+ /**
+ * onBrowserReady - Continues the initialization of Activity Stream after browser is ready.
+ */
+ onBrowserReady() {
+ if (this.activityStream && this.activityStream.initialized) {
+ return;
+ }
+
+ this.activityStream = new lazy.ActivityStream();
+ try {
+ this.activityStream.init();
+ this._subscribeToActivityStream();
+ } catch (e) {
+ console.error(e);
+ }
+ },
+
+ _subscribeToActivityStream() {
+ let unsubscribe = this.activityStream.store.subscribe(() => {
+ // If the top sites changed, broadcast "newtab-top-sites-changed". We
+ // ignore changes to the `screenshot` property in each site because
+ // screenshots are generated at times that are hard to predict and it ends
+ // up interfering with tests that rely on "newtab-top-sites-changed".
+ // Observers likely don't care about screenshots anyway.
+ let topSites = this.activityStream.store
+ .getState()
+ .TopSites.rows.map(site => {
+ site = { ...site };
+ delete site.screenshot;
+ return site;
+ });
+ if (!lazy.ObjectUtils.deepEqual(topSites, this._cachedTopSites)) {
+ this._cachedTopSites = topSites;
+ Services.obs.notifyObservers(null, "newtab-top-sites-changed");
+ }
+ });
+ this._unsubscribeFromActivityStream = () => {
+ try {
+ unsubscribe();
+ } catch (e) {
+ console.error(e);
+ }
+ };
+ },
+
+ /**
+ * uninit - Uninitializes Activity Stream if it exists.
+ */
+ uninit() {
+ if (this.activityStream) {
+ this._unsubscribeFromActivityStream?.();
+ this.activityStream.uninit();
+ this.activityStream = null;
+ }
+
+ this.initialized = false;
+ },
+
+ getTopSites() {
+ return this.activityStream
+ ? this.activityStream.store.getState().TopSites.rows
+ : [];
+ },
+
+ _alreadyRecordedTopsitesPainted: false,
+ _nonDefaultStartup: false,
+
+ noteNonDefaultStartup() {
+ this._nonDefaultStartup = true;
+ },
+
+ maybeRecordTopsitesPainted(timestamp) {
+ if (this._alreadyRecordedTopsitesPainted || this._nonDefaultStartup) {
+ return;
+ }
+
+ const SCALAR_KEY = "timestamps.about_home_topsites_first_paint";
+
+ let startupInfo = Services.startup.getStartupInfo();
+ let processStartTs = startupInfo.process.getTime();
+ let delta = Math.round(timestamp - processStartTs);
+ Services.telemetry.scalarSet(SCALAR_KEY, delta);
+ ChromeUtils.addProfilerMarker("aboutHomeTopsitesFirstPaint");
+ this._alreadyRecordedTopsitesPainted = true;
+ },
+
+ // nsIObserver implementation
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case TOPIC_APP_QUIT: {
+ // We defer to this to the next tick of the event loop since the
+ // AboutHomeStartupCache might want to read from the ActivityStream
+ // store during TOPIC_APP_QUIT.
+ Services.tm.dispatchToMainThread(() => this.uninit());
+ break;
+ }
+ case BROWSER_READY_NOTIFICATION: {
+ Services.obs.removeObserver(this, BROWSER_READY_NOTIFICATION);
+ // Avoid running synchronously during this event that's used for timing
+ Services.tm.dispatchToMainThread(() => this.onBrowserReady());
+ break;
+ }
+ }
+ },
+};
+
+var EXPORTED_SYMBOLS = ["AboutNewTab"];