Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / browser / modules / AboutNewTab.sys.mjs
blob0ad7f4f6ca57b6f6a983fd86152415a85cf77505
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";
8 const lazy = {};
10 ChromeUtils.defineESModuleGetters(lazy, {
11   ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
12 });
14 XPCOMUtils.defineLazyModuleGetters(lazy, {
15   ActivityStream: "resource://activity-stream/lib/ActivityStream.jsm",
16 });
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([
25     "nsIObserver",
26     "nsISupportsWeakReference",
27   ]),
29   // AboutNewTab
30   initialized: false,
32   willNotifyUser: false,
34   _activityStreamEnabled: false,
35   activityStream: null,
36   activityStreamDebug: false,
38   _cachedTopSites: null,
40   _newTabURL: ABOUT_URL,
41   _newTabURLOverridden: false,
43   /**
44    * init - Initializes an instance of Activity Stream if one doesn't exist already.
45    */
46   init() {
47     Services.obs.addObserver(this, TOPIC_APP_QUIT);
48     if (!AppConstants.RELEASE_OR_BETA) {
49       XPCOMUtils.defineLazyPreferenceGetter(
50         this,
51         "activityStreamDebug",
52         PREF_ACTIVITY_STREAM_DEBUG,
53         false,
54         () => {
55           this.notifyChange();
56         }
57       );
58     }
60     XPCOMUtils.defineLazyPreferenceGetter(
61       this,
62       "privilegedAboutProcessEnabled",
63       "browser.tabs.remote.separatePrivilegedContentProcess",
64       false,
65       () => {
66         this.notifyChange();
67       }
68     );
70     // More initialization happens here
71     this.toggleActivityStream(true);
72     this.initialized = true;
74     Services.obs.addObserver(this, BROWSER_READY_NOTIFICATION);
75   },
77   /**
78    * React to changes to the activity stream being enabled or not.
79    *
80    * This will only act if there is a change of state and if not overridden.
81    *
82    * @returns {Boolean} Returns if there has been a state change
83    *
84    * @param {Boolean}   stateEnabled    activity stream enabled state to set to
85    * @param {Boolean}   forceState      force state change
86    */
87   toggleActivityStream(stateEnabled, forceState = false) {
88     if (
89       !forceState &&
90       (this._newTabURLOverridden ||
91         stateEnabled === this._activityStreamEnabled)
92     ) {
93       // exit there is no change of state
94       return false;
95     }
96     if (stateEnabled) {
97       this._activityStreamEnabled = true;
98     } else {
99       this._activityStreamEnabled = false;
100     }
102     this._newTabURL = ABOUT_URL;
103     return true;
104   },
106   get newTabURL() {
107     return this._newTabURL;
108   },
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();
115       return;
116     } else if (newTabURL === "") {
117       newTabURL = "about:blank";
118     }
120     this.toggleActivityStream(false);
121     this._newTabURL = newTabURL;
122     this._newTabURLOverridden = true;
123     this.notifyChange();
124   },
126   get newTabURLOverridden() {
127     return this._newTabURLOverridden;
128   },
130   get activityStreamEnabled() {
131     return this._activityStreamEnabled;
132   },
134   resetNewTabURL() {
135     this._newTabURLOverridden = false;
136     this._newTabURL = ABOUT_URL;
137     this.toggleActivityStream(true, true);
138     this.notifyChange();
139   },
141   notifyChange() {
142     Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
143   },
145   /**
146    * onBrowserReady - Continues the initialization of Activity Stream after browser is ready.
147    */
148   onBrowserReady() {
149     if (this.activityStream && this.activityStream.initialized) {
150       return;
151     }
153     this.activityStream = new lazy.ActivityStream();
154     try {
155       this.activityStream.init();
156       this._subscribeToActivityStream();
157     } catch (e) {
158       console.error(e);
159     }
160   },
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
170         .getState()
171         .TopSites.rows.map(site => {
172           site = { ...site };
173           delete site.screenshot;
174           return site;
175         });
176       if (!lazy.ObjectUtils.deepEqual(topSites, this._cachedTopSites)) {
177         this._cachedTopSites = topSites;
178         Services.obs.notifyObservers(null, "newtab-top-sites-changed");
179       }
180     });
181     this._unsubscribeFromActivityStream = () => {
182       try {
183         unsubscribe();
184       } catch (e) {
185         console.error(e);
186       }
187     };
188   },
190   /**
191    * uninit - Uninitializes Activity Stream if it exists.
192    */
193   uninit() {
194     if (this.activityStream) {
195       this._unsubscribeFromActivityStream?.();
196       this.activityStream.uninit();
197       this.activityStream = null;
198     }
200     this.initialized = false;
201   },
203   getTopSites() {
204     return this.activityStream
205       ? this.activityStream.store.getState().TopSites.rows
206       : [];
207   },
209   _alreadyRecordedTopsitesPainted: false,
210   _nonDefaultStartup: false,
212   noteNonDefaultStartup() {
213     this._nonDefaultStartup = true;
214   },
216   maybeRecordTopsitesPainted(timestamp) {
217     if (this._alreadyRecordedTopsitesPainted || this._nonDefaultStartup) {
218       return;
219     }
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;
229   },
231   // nsIObserver implementation
233   observe(subject, topic, data) {
234     switch (topic) {
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());
240         break;
241       }
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());
246         break;
247       }
248     }
249   },