tor-browser

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

TimerFeed.sys.mjs (5747B)


      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 
      7 // eslint-disable-next-line mozilla/use-static-import
      8 const { AppConstants } = ChromeUtils.importESModule(
      9  "resource://gre/modules/AppConstants.sys.mjs"
     10 );
     11 
     12 ChromeUtils.defineESModuleGetters(lazy, {
     13  PersistentCache: "resource://newtab/lib/PersistentCache.sys.mjs",
     14 });
     15 
     16 ChromeUtils.defineLazyGetter(lazy, "gNewTabStrings", () => {
     17  return new Localization(["browser/newtab/newtab.ftl"], true);
     18 });
     19 
     20 import {
     21  actionTypes as at,
     22  actionCreators as ac,
     23 } from "resource://newtab/common/Actions.mjs";
     24 
     25 const PREF_TIMER_ENABLED = "widgets.timer.enabled";
     26 const PREF_SYSTEM_TIMER_ENABLED = "widgets.system.timer.enabled";
     27 const PREF_TIMER_SHOW_NOTIFICATIONS =
     28  "widgets.focusTimer.showSystemNotifications";
     29 const CACHE_KEY = "timer_widget";
     30 
     31 const AlertNotification = Components.Constructor(
     32  "@mozilla.org/alert-notification;1",
     33  "nsIAlertNotification",
     34  "initWithObject"
     35 );
     36 
     37 /**
     38 * Class for the Timer widget, which manages the changes to the Timer widget
     39 * and syncs with PersistentCache
     40 */
     41 export class TimerFeed {
     42  constructor() {
     43    this.initialized = false;
     44    this.cache = this.PersistentCache(CACHE_KEY, true);
     45    this.notifiedThisCycle = false;
     46  }
     47 
     48  resetNotificationFlag() {
     49    this.notifiedThisCycle = false;
     50  }
     51 
     52  async showSystemNotification(title, body) {
     53    const prefs = this.store.getState()?.Prefs.values;
     54 
     55    if (!prefs[PREF_TIMER_SHOW_NOTIFICATIONS]) {
     56      return;
     57    }
     58 
     59    try {
     60      const alertsService = Cc["@mozilla.org/alerts-service;1"].getService(
     61        Ci.nsIAlertsService
     62      );
     63 
     64      /**
     65       * @backward-compat { version 147 }
     66       * Remove `alertsService.showAlertNotification` call once Firefox 147
     67       * makes it to the release channel.
     68       */
     69 
     70      if (Services.vc.compare(AppConstants.MOZ_APP_VERSION, "147.0a1") >= 0) {
     71        alertsService.showAlert(
     72          new AlertNotification({
     73            title,
     74            text: body,
     75          })
     76        );
     77      } else {
     78        alertsService.showAlertNotification(null, title, body, false, "", null);
     79      }
     80    } catch (err) {
     81      console.error("Failed to show system notification", err);
     82    }
     83  }
     84 
     85  get enabled() {
     86    const prefs = this.store.getState()?.Prefs.values;
     87    const nimbusTimerEnabled = prefs.widgetsConfig?.timerEnabled;
     88    const nimbusTimerTrainhopEnabled =
     89      prefs.trainhopConfig?.widgets?.timerEnabled;
     90 
     91    return (
     92      prefs?.[PREF_TIMER_ENABLED] &&
     93      (prefs?.[PREF_SYSTEM_TIMER_ENABLED] ||
     94        nimbusTimerEnabled ||
     95        nimbusTimerTrainhopEnabled)
     96    );
     97  }
     98 
     99  async init() {
    100    this.initialized = true;
    101    await this.syncTimer(true);
    102  }
    103 
    104  async syncTimer(isStartup = false) {
    105    const cachedData = (await this.cache.get()) || {};
    106    const { timer } = cachedData;
    107    if (timer) {
    108      this.update(timer, isStartup);
    109    }
    110  }
    111 
    112  update(data, isStartup = false) {
    113    this.store.dispatch(
    114      ac.BroadcastToContent({
    115        type: at.WIDGETS_TIMER_SET,
    116        data,
    117        meta: isStartup,
    118      })
    119    );
    120  }
    121 
    122  /**
    123   * @param {object} action - The action object containing pref change data
    124   * @param {string} action.data.name - The name of the pref that changed
    125   */
    126  async onPrefChangedAction(action) {
    127    switch (action.data.name) {
    128      case PREF_TIMER_ENABLED:
    129      case PREF_SYSTEM_TIMER_ENABLED:
    130      case "trainhopConfig":
    131      case "widgetsConfig": {
    132        if (this.enabled && !this.initialized) {
    133          await this.init();
    134        }
    135        break;
    136      }
    137    }
    138  }
    139 
    140  async onAction(action) {
    141    switch (action.type) {
    142      case at.INIT:
    143        if (this.enabled) {
    144          await this.init();
    145        }
    146        break;
    147      case at.PREF_CHANGED:
    148        await this.onPrefChangedAction(action);
    149        break;
    150      case at.WIDGETS_TIMER_END:
    151        {
    152          const prevState = this.store.getState().TimerWidget;
    153          await this.cache.set("timer", { ...prevState, ...action.data });
    154          this.update({ ...prevState, ...action.data });
    155          const { timerType } = action.data;
    156 
    157          const l10nId =
    158            timerType === "break"
    159              ? "newtab-widget-timer-notification-break"
    160              : "newtab-widget-timer-notification-focus";
    161          const [titleMessage, bodyMessage] =
    162            await lazy.gNewTabStrings.formatMessages([
    163              { id: "newtab-widget-timer-notification-title" },
    164              { id: l10nId },
    165            ]);
    166          const title = titleMessage?.value || "Timer";
    167          const body = bodyMessage?.value || "Timer ended";
    168 
    169          if (!this.notifiedThisCycle) {
    170            this.notifiedThisCycle = true;
    171            this.showSystemNotification(title, body);
    172          }
    173        }
    174        break;
    175      case at.WIDGETS_TIMER_SET_TYPE:
    176      case at.WIDGETS_TIMER_SET_DURATION:
    177      case at.WIDGETS_TIMER_PAUSE:
    178      case at.WIDGETS_TIMER_PLAY:
    179        {
    180          this.resetNotificationFlag();
    181          const prevState = this.store.getState().TimerWidget;
    182          await this.cache.set("timer", { ...prevState, ...action.data });
    183          this.update({ ...prevState, ...action.data });
    184        }
    185        break;
    186      case at.WIDGETS_TIMER_RESET:
    187        {
    188          this.resetNotificationFlag();
    189          const prevState = this.store.getState().TimerWidget;
    190          await this.cache.set("timer", { ...prevState, ...action.data });
    191          this.update({ ...prevState, ...action.data });
    192        }
    193        break;
    194    }
    195  }
    196 }
    197 
    198 TimerFeed.prototype.PersistentCache = (...args) => {
    199  return new lazy.PersistentCache(...args);
    200 };