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 ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
14 XPCOMUtils.defineLazyModuleGetters(lazy, {
15 ActivityStream: "resource://activity-stream/lib/ActivityStream.jsm",
18 const ABOUT_URL = "about:newtab";
19 const PREF_ACTIVITY_STREAM_DEBUG = "browser.newtabpage.activity-stream.debug";
20 const TOPIC_APP_QUIT = "quit-application-granted";
21 const BROWSER_READY_NOTIFICATION = "sessionstore-windows-restored";
23 export const AboutNewTab = {
24 QueryInterface: ChromeUtils.generateQI([
26 "nsISupportsWeakReference",
32 willNotifyUser: false,
34 _activityStreamEnabled: false,
36 activityStreamDebug: false,
38 _cachedTopSites: null,
40 _newTabURL: ABOUT_URL,
41 _newTabURLOverridden: false,
44 * init - Initializes an instance of Activity Stream if one doesn't exist already.
47 Services.obs.addObserver(this, TOPIC_APP_QUIT);
48 if (!AppConstants.RELEASE_OR_BETA) {
49 XPCOMUtils.defineLazyPreferenceGetter(
51 "activityStreamDebug",
52 PREF_ACTIVITY_STREAM_DEBUG,
60 XPCOMUtils.defineLazyPreferenceGetter(
62 "privilegedAboutProcessEnabled",
63 "browser.tabs.remote.separatePrivilegedContentProcess",
70 // More initialization happens here
71 this.toggleActivityStream(true);
72 this.initialized = true;
74 Services.obs.addObserver(this, BROWSER_READY_NOTIFICATION);
78 * React to changes to the activity stream being enabled or not.
80 * This will only act if there is a change of state and if not overridden.
82 * @returns {Boolean} Returns if there has been a state change
84 * @param {Boolean} stateEnabled activity stream enabled state to set to
85 * @param {Boolean} forceState force state change
87 toggleActivityStream(stateEnabled, forceState = false) {
90 (this._newTabURLOverridden ||
91 stateEnabled === this._activityStreamEnabled)
93 // exit there is no change of state
97 this._activityStreamEnabled = true;
99 this._activityStreamEnabled = false;
102 this._newTabURL = ABOUT_URL;
107 return this._newTabURL;
110 set newTabURL(aNewTabURL) {
111 let newTabURL = aNewTabURL.trim();
112 if (newTabURL === ABOUT_URL) {
113 // avoid infinite redirects in case one sets the URL to about:newtab
114 this.resetNewTabURL();
116 } else if (newTabURL === "") {
117 newTabURL = "about:blank";
120 this.toggleActivityStream(false);
121 this._newTabURL = newTabURL;
122 this._newTabURLOverridden = true;
126 get newTabURLOverridden() {
127 return this._newTabURLOverridden;
130 get activityStreamEnabled() {
131 return this._activityStreamEnabled;
135 this._newTabURLOverridden = false;
136 this._newTabURL = ABOUT_URL;
137 this.toggleActivityStream(true, true);
142 Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
146 * onBrowserReady - Continues the initialization of Activity Stream after browser is ready.
149 if (this.activityStream && this.activityStream.initialized) {
153 this.activityStream = new lazy.ActivityStream();
155 this.activityStream.init();
156 this._subscribeToActivityStream();
162 _subscribeToActivityStream() {
163 let unsubscribe = this.activityStream.store.subscribe(() => {
164 // If the top sites changed, broadcast "newtab-top-sites-changed". We
165 // ignore changes to the `screenshot` property in each site because
166 // screenshots are generated at times that are hard to predict and it ends
167 // up interfering with tests that rely on "newtab-top-sites-changed".
168 // Observers likely don't care about screenshots anyway.
169 let topSites = this.activityStream.store
171 .TopSites.rows.map(site => {
173 delete site.screenshot;
176 if (!lazy.ObjectUtils.deepEqual(topSites, this._cachedTopSites)) {
177 this._cachedTopSites = topSites;
178 Services.obs.notifyObservers(null, "newtab-top-sites-changed");
181 this._unsubscribeFromActivityStream = () => {
191 * uninit - Uninitializes Activity Stream if it exists.
194 if (this.activityStream) {
195 this._unsubscribeFromActivityStream?.();
196 this.activityStream.uninit();
197 this.activityStream = null;
200 this.initialized = false;
204 return this.activityStream
205 ? this.activityStream.store.getState().TopSites.rows
209 _alreadyRecordedTopsitesPainted: false,
210 _nonDefaultStartup: false,
212 noteNonDefaultStartup() {
213 this._nonDefaultStartup = true;
216 maybeRecordTopsitesPainted(timestamp) {
217 if (this._alreadyRecordedTopsitesPainted || this._nonDefaultStartup) {
221 const SCALAR_KEY = "timestamps.about_home_topsites_first_paint";
223 let startupInfo = Services.startup.getStartupInfo();
224 let processStartTs = startupInfo.process.getTime();
225 let delta = Math.round(timestamp - processStartTs);
226 Services.telemetry.scalarSet(SCALAR_KEY, delta);
227 ChromeUtils.addProfilerMarker("aboutHomeTopsitesFirstPaint");
228 this._alreadyRecordedTopsitesPainted = true;
231 // nsIObserver implementation
233 observe(subject, topic, data) {
235 case TOPIC_APP_QUIT: {
236 // We defer to this to the next tick of the event loop since the
237 // AboutHomeStartupCache might want to read from the ActivityStream
238 // store during TOPIC_APP_QUIT.
239 Services.tm.dispatchToMainThread(() => this.uninit());
242 case BROWSER_READY_NOTIFICATION: {
243 Services.obs.removeObserver(this, BROWSER_READY_NOTIFICATION);
244 // Avoid running synchronously during this event that's used for timing
245 Services.tm.dispatchToMainThread(() => this.onBrowserReady());