summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/lib/RecommendationProvider.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/lib/RecommendationProvider.jsm')
-rw-r--r--browser/components/newtab/lib/RecommendationProvider.jsm132
1 files changed, 132 insertions, 0 deletions
diff --git a/browser/components/newtab/lib/RecommendationProvider.jsm b/browser/components/newtab/lib/RecommendationProvider.jsm
new file mode 100644
index 0000000000..2438dfe542
--- /dev/null
+++ b/browser/components/newtab/lib/RecommendationProvider.jsm
@@ -0,0 +1,132 @@
+/* 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";
+
+// Use XPCOMUtils.defineLazyModuleGetters to make the test harness keeps working
+// after bug 1608279.
+//
+// The test harness's workaround for "lazy getter on a plain object" is to
+// set the `lazy` object's prototype to the global object, inside the lazy
+// getter API.
+//
+// ChromeUtils.defineModuleGetter is converted into a static import declaration
+// by babel-plugin-jsm-to-esmodules, and it doesn't work for the following
+// 2 reasons:
+//
+// * There's no other lazy getter API call in this file, and the workaround
+// above stops working
+// * babel-plugin-jsm-to-esmodules ignores the first parameter of the lazy
+// getter API, and the result is wrong
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const lazy = {};
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ PersonalityProvider:
+ "resource://activity-stream/lib/PersonalityProvider/PersonalityProvider.jsm",
+});
+
+const { actionTypes: at, actionCreators: ac } = ChromeUtils.importESModule(
+ "resource://activity-stream/common/Actions.sys.mjs"
+);
+const PREF_PERSONALIZATION_MODEL_KEYS =
+ "discoverystream.personalization.modelKeys";
+const PREF_PERSONALIZATION = "discoverystream.personalization.enabled";
+
+// The main purpose of this class is to handle interactions with the recommendation provider.
+// A recommendation provider scores a list of stories, currently this is a personality provider.
+// So all calls to the provider, anything involved with the setup of the provider,
+// accessing prefs for the provider, or updaing devtools with provider state, is contained in here.
+class RecommendationProvider {
+ setProvider(scores) {
+ // A provider is already set. This can happen when new stories come in
+ // and we need to update their scores.
+ // We can use the existing one, a fresh one is created after startup.
+ // Using the existing one might be a bit out of date,
+ // but it's fine for now. We can rely on restarts for updates.
+ // See bug 1629931 for improvements to this.
+ if (this.provider) {
+ return;
+ }
+ // At this point we've determined we can successfully create a v2 personalization provider.
+ this.provider = new lazy.PersonalityProvider(this.modelKeys);
+ this.provider.setScores(scores);
+ }
+
+ /*
+ * This calls any async initialization that's required,
+ * and then signals to devtools when that's done.
+ */
+ async init() {
+ if (this.provider && this.provider.init) {
+ await this.provider.init();
+ this.store.dispatch(
+ ac.BroadcastToContent({
+ type: at.DISCOVERY_STREAM_PERSONALIZATION_INIT,
+ })
+ );
+ }
+ }
+
+ get modelKeys() {
+ if (!this._modelKeys) {
+ this._modelKeys =
+ this.store.getState().Prefs.values[PREF_PERSONALIZATION_MODEL_KEYS];
+ }
+
+ return this._modelKeys;
+ }
+
+ getScores() {
+ return this.provider.getScores();
+ }
+
+ async calculateItemRelevanceScore(item) {
+ if (this.provider) {
+ const scoreResult = await this.provider.calculateItemRelevanceScore(item);
+ if (scoreResult === 0 || scoreResult) {
+ item.score = scoreResult;
+ }
+ }
+ }
+
+ teardown() {
+ if (this.provider && this.provider.teardown) {
+ // This removes any in memory listeners if available.
+ this.provider.teardown();
+ }
+ }
+
+ resetState() {
+ this._modelKeys = null;
+ this.provider = null;
+ }
+
+ onAction(action) {
+ switch (action.type) {
+ case at.DISCOVERY_STREAM_CONFIG_CHANGE:
+ this.teardown();
+ this.resetState();
+ break;
+ case at.PREF_CHANGED:
+ switch (action.data.name) {
+ case PREF_PERSONALIZATION_MODEL_KEYS:
+ this.store.dispatch(
+ ac.BroadcastToContent({
+ type: at.DISCOVERY_STREAM_CONFIG_RESET,
+ })
+ );
+ break;
+ }
+ break;
+ case at.DISCOVERY_STREAM_PERSONALIZATION_TOGGLE:
+ let enabled = this.store.getState().Prefs.values[PREF_PERSONALIZATION];
+
+ this.store.dispatch(ac.SetPref(PREF_PERSONALIZATION, !enabled));
+ break;
+ }
+ }
+}
+
+const EXPORTED_SYMBOLS = ["RecommendationProvider"];