tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ListsFeed.sys.mjs (4511B)


      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/. */
      4 
      5 const lazy = {};
      6 ChromeUtils.defineESModuleGetters(lazy, {
      7  PersistentCache: "resource://newtab/lib/PersistentCache.sys.mjs",
      8 });
      9 
     10 import {
     11  actionTypes as at,
     12  actionCreators as ac,
     13 } from "resource://newtab/common/Actions.mjs";
     14 
     15 const PREF_LISTS_ENABLED = "widgets.lists.enabled";
     16 const PREF_SYSTEM_LISTS_ENABLED = "widgets.system.lists.enabled";
     17 const PREF_WIDGETS_LISTS_MAX_LISTS = "widgets.lists.maxLists";
     18 const CACHE_KEY = "lists_widget";
     19 
     20 /**
     21 * Class for the Lists widget, which manages the updates to the lists widget
     22 * and syncs with PersistentCache
     23 */
     24 export class ListsFeed {
     25  constructor() {
     26    this.initialized = false;
     27    this.cache = this.PersistentCache(CACHE_KEY, true);
     28  }
     29 
     30  get enabled() {
     31    const prefs = this.store.getState()?.Prefs.values;
     32    const nimbusListsEnabled = prefs.widgetsConfig?.listsEnabled;
     33    const nimbusListsTrainhopEnabled =
     34      prefs.trainhopConfig?.widgets?.listsEnabled;
     35 
     36    return (
     37      prefs?.[PREF_LISTS_ENABLED] &&
     38      (prefs?.[PREF_SYSTEM_LISTS_ENABLED] ||
     39        nimbusListsEnabled ||
     40        nimbusListsTrainhopEnabled)
     41    );
     42  }
     43 
     44  async init() {
     45    this.initialized = true;
     46    await this.syncLists(true);
     47  }
     48 
     49  isOverMaximumListCount(lists) {
     50    const prefs = this.store.getState()?.Prefs.values;
     51    const maxListsCount = prefs?.[PREF_WIDGETS_LISTS_MAX_LISTS];
     52    const currentListsCount = Object.keys(lists).length;
     53    return currentListsCount > maxListsCount;
     54  }
     55 
     56  async syncLists(isStartup = false) {
     57    const cachedData = (await this.cache.get()) || {};
     58    const { lists, selected } = cachedData;
     59    // only update lists if this has been set before
     60    if (lists) {
     61      // Ensure all lists have a `completed` array
     62      for (const listId in lists) {
     63        if (lists[listId]) {
     64          const list = lists[listId];
     65          if (!Array.isArray(list.completed)) {
     66            list.completed = [];
     67          }
     68          // move any completed tasks to the completed array
     69          const activeTasks = [];
     70          const completedTasks = [];
     71          for (const task of list.tasks) {
     72            if (task.completed === true) {
     73              completedTasks.push(task);
     74            } else {
     75              activeTasks.push(task);
     76            }
     77          }
     78 
     79          list.tasks = activeTasks;
     80          list.completed = list.completed.concat(completedTasks);
     81        }
     82      }
     83 
     84      // Bug 1981722 — Only trigger if the user has manually created more lists
     85      // than allowed. Throwing here prevents lists from syncing.
     86      if (this.isOverMaximumListCount(lists)) {
     87        throw new Error(`Over the maximum list count`);
     88      }
     89 
     90      this.update({ lists }, isStartup);
     91    }
     92 
     93    if (selected) {
     94      this.updateSelected(selected, isStartup);
     95    }
     96  }
     97 
     98  update(data, isStartup = false) {
     99    this.store.dispatch(
    100      ac.BroadcastToContent({
    101        type: at.WIDGETS_LISTS_SET,
    102        data: data.lists,
    103        meta: isStartup,
    104      })
    105    );
    106  }
    107 
    108  updateSelected(data, isStartup = false) {
    109    this.store.dispatch(
    110      ac.BroadcastToContent({
    111        type: at.WIDGETS_LISTS_SET_SELECTED,
    112        data,
    113        meta: isStartup,
    114      })
    115    );
    116  }
    117 
    118  /**
    119   * @param {object} action - The action object containing pref change data
    120   * @param {string} action.data.name - The name of the pref that changed
    121   */
    122  async onPrefChangedAction(action) {
    123    switch (action.data.name) {
    124      case PREF_LISTS_ENABLED:
    125      case PREF_SYSTEM_LISTS_ENABLED:
    126      case "trainhopConfig":
    127      case "widgetsConfig": {
    128        if (this.enabled && !this.initialized) {
    129          await this.init();
    130        }
    131        break;
    132      }
    133    }
    134  }
    135 
    136  async onAction(action) {
    137    switch (action.type) {
    138      case at.INIT:
    139        if (this.enabled) {
    140          await this.init();
    141        }
    142        break;
    143      case at.PREF_CHANGED:
    144        await this.onPrefChangedAction(action);
    145        break;
    146      case at.WIDGETS_LISTS_UPDATE:
    147        await this.cache.set("lists", action.data.lists);
    148        this.update(action.data);
    149        break;
    150      case at.WIDGETS_LISTS_CHANGE_SELECTED:
    151        await this.cache.set("selected", action.data);
    152        this.updateSelected(action.data);
    153        break;
    154    }
    155  }
    156 }
    157 
    158 ListsFeed.prototype.PersistentCache = (...args) => {
    159  return new lazy.PersistentCache(...args);
    160 };