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/. */
5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
10 ChromeUtils.defineESModuleGetters(lazy, {
11 ActivityStream: "resource://activity-stream/lib/ActivityStream.sys.mjs",
12 ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
15 const ABOUT_URL = "about:newtab";
16 const PREF_ACTIVITY_STREAM_DEBUG = "browser.newtabpage.activity-stream.debug";
17 const TOPIC_APP_QUIT = "quit-application-granted";
18 const BROWSER_READY_NOTIFICATION = "sessionstore-windows-restored";
20 export const AboutNewTab = {
21 QueryInterface: ChromeUtils.generateQI([
23 "nsISupportsWeakReference",
29 willNotifyUser: false,
31 _activityStreamEnabled: false,
33 activityStreamDebug: false,
35 _cachedTopSites: null,
37 _newTabURL: ABOUT_URL,
38 _newTabURLOverridden: false,
41 * init - Initializes an instance of Activity Stream if one doesn't exist already.
44 Services.obs.addObserver(this, TOPIC_APP_QUIT);
45 if (!AppConstants.RELEASE_OR_BETA) {
46 XPCOMUtils.defineLazyPreferenceGetter(
48 "activityStreamDebug",
49 PREF_ACTIVITY_STREAM_DEBUG,
57 XPCOMUtils.defineLazyPreferenceGetter(
59 "privilegedAboutProcessEnabled",
60 "browser.tabs.remote.separatePrivilegedContentProcess",
67 // More initialization happens here
68 this.toggleActivityStream(true);
69 this.initialized = true;
71 Services.obs.addObserver(this, BROWSER_READY_NOTIFICATION);
75 * React to changes to the activity stream being enabled or not.
77 * This will only act if there is a change of state and if not overridden.
79 * @returns {Boolean} Returns if there has been a state change
81 * @param {Boolean} stateEnabled activity stream enabled state to set to
82 * @param {Boolean} forceState force state change
84 toggleActivityStream(stateEnabled, forceState = false) {
87 (this._newTabURLOverridden ||
88 stateEnabled === this._activityStreamEnabled)
90 // exit there is no change of state
94 this._activityStreamEnabled = true;
96 this._activityStreamEnabled = false;
99 this._newTabURL = ABOUT_URL;
104 return this._newTabURL;
107 set newTabURL(aNewTabURL) {
108 let newTabURL = aNewTabURL.trim();
109 if (newTabURL === ABOUT_URL) {
110 // avoid infinite redirects in case one sets the URL to about:newtab
111 this.resetNewTabURL();
113 } else if (newTabURL === "") {
114 newTabURL = "about:blank";
117 this.toggleActivityStream(false);
118 this._newTabURL = newTabURL;
119 this._newTabURLOverridden = true;
123 get newTabURLOverridden() {
124 return this._newTabURLOverridden;
127 get activityStreamEnabled() {
128 return this._activityStreamEnabled;
132 this._newTabURLOverridden = false;
133 this._newTabURL = ABOUT_URL;
134 this.toggleActivityStream(true, true);
139 Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
143 * onBrowserReady - Continues the initialization of Activity Stream after browser is ready.
146 if (this.activityStream && this.activityStream.initialized) {
150 this.activityStream = new lazy.ActivityStream();
152 this.activityStream.init();
153 this._subscribeToActivityStream();
159 _subscribeToActivityStream() {
160 let unsubscribe = this.activityStream.store.subscribe(() => {
161 // If the top sites changed, broadcast "newtab-top-sites-changed". We
162 // ignore changes to the `screenshot` property in each site because
163 // screenshots are generated at times that are hard to predict and it ends
164 // up interfering with tests that rely on "newtab-top-sites-changed".
165 // Observers likely don't care about screenshots anyway.
166 let topSites = this.activityStream.store
168 .TopSites.rows.map(site => {
170 delete site.screenshot;
173 if (!lazy.ObjectUtils.deepEqual(topSites, this._cachedTopSites)) {
174 this._cachedTopSites = topSites;
175 Services.obs.notifyObservers(null, "newtab-top-sites-changed");
178 this._unsubscribeFromActivityStream = () => {
188 * uninit - Uninitializes Activity Stream if it exists.
191 if (this.activityStream) {
192 this._unsubscribeFromActivityStream?.();
193 this.activityStream.uninit();
194 this.activityStream = null;
197 this.initialized = false;
201 return this.activityStream
202 ? this.activityStream.store.getState().TopSites.rows
206 _alreadyRecordedTopsitesPainted: false,
207 _nonDefaultStartup: false,
209 noteNonDefaultStartup() {
210 this._nonDefaultStartup = true;
213 maybeRecordTopsitesPainted(timestamp) {
214 if (this._alreadyRecordedTopsitesPainted || this._nonDefaultStartup) {
218 const SCALAR_KEY = "timestamps.about_home_topsites_first_paint";
220 let startupInfo = Services.startup.getStartupInfo();
221 let processStartTs = startupInfo.process.getTime();
222 let delta = Math.round(timestamp - processStartTs);
223 Services.telemetry.scalarSet(SCALAR_KEY, delta);
224 ChromeUtils.addProfilerMarker("aboutHomeTopsitesFirstPaint");
225 this._alreadyRecordedTopsitesPainted = true;
228 // nsIObserver implementation
230 observe(subject, topic) {
232 case TOPIC_APP_QUIT: {
233 // We defer to this to the next tick of the event loop since the
234 // AboutHomeStartupCache might want to read from the ActivityStream
235 // store during TOPIC_APP_QUIT.
236 Services.tm.dispatchToMainThread(() => this.uninit());
239 case BROWSER_READY_NOTIFICATION: {
240 Services.obs.removeObserver(this, BROWSER_READY_NOTIFICATION);
241 // Avoid running synchronously during this event that's used for timing
242 Services.tm.dispatchToMainThread(() => this.onBrowserReady());