/* 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 { actionCreators: ac, actionTypes: at } = ChromeUtils.import( "resource://activity-stream/common/Actions.jsm" ); const { Prefs } = ChromeUtils.import( "resource://activity-stream/lib/ActivityStreamPrefs.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.defineModuleGetter( this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "AppConstants", "resource://gre/modules/AppConstants.jsm" ); ChromeUtils.defineModuleGetter( this, "ExperimentAPI", "resource://messaging-system/experiments/ExperimentAPI.jsm" ); ChromeUtils.defineModuleGetter( this, "Region", "resource://gre/modules/Region.jsm" ); this.PrefsFeed = class PrefsFeed { constructor(prefMap) { this._prefMap = prefMap; this._prefs = new Prefs(); this.onExperimentUpdated = this.onExperimentUpdated.bind(this); } onPrefChanged(name, value) { const prefItem = this._prefMap.get(name); if (prefItem) { this.store.dispatch( ac[prefItem.skipBroadcast ? "OnlyToMain" : "BroadcastToContent"]({ type: at.PREF_CHANGED, data: { name, value }, }) ); } } _setStringPref(values, key, defaultValue) { this._setPref(values, key, defaultValue, Services.prefs.getStringPref); } _setBoolPref(values, key, defaultValue) { this._setPref(values, key, defaultValue, Services.prefs.getBoolPref); } _setIntPref(values, key, defaultValue) { this._setPref(values, key, defaultValue, Services.prefs.getIntPref); } _setPref(values, key, defaultValue, getPrefFunction) { let value = getPrefFunction( `browser.newtabpage.activity-stream.${key}`, defaultValue ); values[key] = value; this._prefMap.set(key, { value }); } /** * Combine default values with experiment values for * the feature config. * */ getFeatureConfigFromExperimentData(experimentData) { return { // Icon that shows up in the corner to link to preferences prefsButtonIcon: "icon-settings", // Override defaults with any experiment values, if any exist. ...(experimentData?.branch?.feature?.value || {}), }; } /** * Helper for initializing experiment and feature config data in .init() * */ addExperimentDataToValues(values) { let experimentData; try { experimentData = ExperimentAPI.getExperiment({ featureId: "newtab", }); } catch (e) { Cu.reportError(e); } values.experimentData = experimentData; values.featureConfig = this.getFeatureConfigFromExperimentData( experimentData ); } /** * Handler for when experiment data updates. */ onExperimentUpdated(event, experimentData) { this.store.dispatch( ac.BroadcastToContent({ type: at.PREF_CHANGED, data: { name: "experimentData", value: experimentData }, }) ); this.store.dispatch( ac.BroadcastToContent({ type: at.PREF_CHANGED, data: { name: "featureConfig", value: this.getFeatureConfigFromExperimentData(experimentData), }, }) ); } init() { this._prefs.observeBranch(this); ExperimentAPI.on( "update", { featureId: "newtab" }, this.onExperimentUpdated ); this._storage = this.store.dbStorage.getDbTable("sectionPrefs"); // Get the initial value of each activity stream pref const values = {}; for (const name of this._prefMap.keys()) { values[name] = this._prefs.get(name); } // These are not prefs, but are needed to determine stuff in content that can only be // computed in main process values.isPrivateBrowsingEnabled = PrivateBrowsingUtils.enabled; values.platform = AppConstants.platform; // Save the geo pref if we have it if (Region.home) { values.region = Region.home; this.geo = values.region; } else if (this.geo !== "") { // Watch for geo changes and use a dummy value for now Services.obs.addObserver(this, Region.REGION_TOPIC); this.geo = ""; } // Get the firefox accounts url for links and to send firstrun metrics to. values.fxa_endpoint = Services.prefs.getStringPref( "browser.newtabpage.activity-stream.fxaccounts.endpoint", "https://accounts.firefox.com" ); // Get the firefox update channel with values as default, nightly, beta or release values.appUpdateChannel = Services.prefs.getStringPref( "app.update.channel", "" ); // Read the pref for search shortcuts top sites experiment from firefox.js and store it // in our interal list of prefs to watch let searchTopSiteExperimentPrefValue = Services.prefs.getBoolPref( "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts" ); values[ "improvesearch.topSiteSearchShortcuts" ] = searchTopSiteExperimentPrefValue; this._prefMap.set("improvesearch.topSiteSearchShortcuts", { value: searchTopSiteExperimentPrefValue, }); values.mayHaveSponsoredTopSites = Services.prefs.getBoolPref( "browser.topsites.useRemoteSetting" ); // Read the pref for search hand-off from firefox.js and store it // in our interal list of prefs to watch let handoffToAwesomebarPrefValue = Services.prefs.getBoolPref( "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar" ); values["improvesearch.handoffToAwesomebar"] = handoffToAwesomebarPrefValue; this._prefMap.set("improvesearch.handoffToAwesomebar", { value: handoffToAwesomebarPrefValue, }); this.addExperimentDataToValues(values); this._setBoolPref(values, "newNewtabExperience.enabled", false); this._setBoolPref(values, "customizationMenu.enabled", false); this._setBoolPref(values, "logowordmark.alwaysVisible", false); this._setBoolPref(values, "feeds.section.topstories", false); this._setBoolPref(values, "discoverystream.enabled", false); this._setBoolPref(values, "discoverystream.isCollectionDismissible", false); this._setBoolPref(values, "discoverystream.hardcoded-basic-layout", false); this._setBoolPref(values, "discoverystream.recs.personalized", false); this._setBoolPref(values, "discoverystream.spocs.personalized", false); this._setStringPref( values, "discoverystream.personalization.modelKeys", "" ); this._setIntPref(values, "discoverystream.personalization.version", 1); this._setIntPref(values, "discoverystream.personalization.overrideVersion"); this._setStringPref(values, "discoverystream.spocs-endpoint", ""); this._setStringPref(values, "discoverystream.spocs-endpoint-query", ""); this._setStringPref(values, "newNewtabExperience.colors", ""); // Set the initial state of all prefs in redux this.store.dispatch( ac.BroadcastToContent({ type: at.PREFS_INITIAL_VALUES, data: values, meta: { isStartup: true, }, }) ); } uninit() { this.removeListeners(); } removeListeners() { this._prefs.ignoreBranch(this); ExperimentAPI.off(this.onExperimentUpdated); if (this.geo === "") { Services.obs.removeObserver(this, Region.REGION_TOPIC); } } async _setIndexedDBPref(id, value) { const name = id === "topsites" ? id : `feeds.section.${id}`; try { await this._storage.set(name, value); } catch (e) { Cu.reportError("Could not set section preferences."); } } observe(subject, topic, data) { switch (topic) { case Region.REGION_TOPIC: if (data === Region.REGION_UPDATED) { this.store.dispatch( ac.BroadcastToContent({ type: at.PREF_CHANGED, data: { name: "region", value: Region.home }, }) ); } break; } } onAction(action) { switch (action.type) { case at.INIT: this.init(); break; case at.UNINIT: this.uninit(); break; case at.CLEAR_PREF: Services.prefs.clearUserPref(this._prefs._branchStr + action.data.name); break; case at.SET_PREF: this._prefs.set(action.data.name, action.data.value); break; case at.UPDATE_SECTION_PREFS: this._setIndexedDBPref(action.data.id, action.data.value); break; } } }; const EXPORTED_SYMBOLS = ["PrefsFeed"];