Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


SSL NotificationDB.sys.mjs   Sprache: unbekannt

 
/* 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/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "NotificationDB",
    maxLogLevelPref: "dom.webnotifications.loglevel",
  });
});

const NOTIFICATION_STORE_DIR = PathUtils.profileDir;
const NOTIFICATION_STORE_PATH = PathUtils.join(
  NOTIFICATION_STORE_DIR,
  "notificationstore.json"
);

export class NotificationDB {
  // Ensure we won't call init() while xpcom-shutdown is performed
  #shutdownInProgress = false;

  // A promise that resolves once the ongoing task queue has been drained.
  // The value will be reset when the queue starts again.
  #queueDrainedPromise = null;
  #queueDrainedPromiseResolve = null;

  #byTag = Object.create(null);
  #notifications = Object.create(null);
  #loaded = false;
  #tasks = [];
  #runningTask = null;

  storageQualifier() {
    return "Notification";
  }

  prefixStorageQualifier(message) {
    return `${this.storageQualifier()}:${message}`;
  }

  formatMessageType(message) {
    return this.prefixStorageQualifier(message);
  }

  supportedMessageTypes() {
    return [
      this.formatMessageType("Save"),
      this.formatMessageType("Delete"),
      this.formatMessageType("GetAll"),
    ];
  }

  constructor() {
    if (this.#shutdownInProgress) {
      return;
    }

    this.#notifications = Object.create(null);
    this.#byTag = Object.create(null);
    this.#loaded = false;

    this.#tasks = []; // read/write operation queue
    this.#runningTask = null;

    Services.obs.addObserver(this, "xpcom-shutdown");
    this.registerListeners();

    // This assumes that nothing will queue a new task at profile-change-teardown phase,
    // potentially replacing the #queueDrainedPromise if there was no existing task run.
    lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
      "NotificationDB: Need to make sure that all notification messages are processed",
      () => this.#queueDrainedPromise
    );
  }

  registerListeners() {
    for (let message of this.supportedMessageTypes()) {
      Services.ppmm.addMessageListener(message, this);
    }
  }

  unregisterListeners() {
    for (let message of this.supportedMessageTypes()) {
      Services.ppmm.removeMessageListener(message, this);
    }
  }

  observe(aSubject, aTopic) {
    lazy.console.debug(`Topic: ${aTopic}`);
    if (aTopic == "xpcom-shutdown") {
      this.#shutdownInProgress = true;
      Services.obs.removeObserver(this, "xpcom-shutdown");
      this.unregisterListeners();
    }
  }

  filterNonAppNotifications(notifications) {
    let result = Object.create(null);
    for (let origin in notifications) {
      result[origin] = Object.create(null);
      let persistentNotificationCount = 0;
      for (let id in notifications[origin]) {
        if (notifications[origin][id].serviceWorkerRegistrationScope) {
          persistentNotificationCount++;
          result[origin][id] = notifications[origin][id];
        }
      }
      if (persistentNotificationCount == 0) {
        lazy.console.debug(
          `Origin ${origin} is not linked to an app manifest, deleting.`
        );
        delete result[origin];
      }
    }

    return result;
  }

  // Attempt to read notification file, if it's not there we will create it.
  load() {
    var promise = IOUtils.readUTF8(NOTIFICATION_STORE_PATH);
    return promise.then(
      data => {
        if (data.length) {
          // Preprocessing phase intends to cleanly separate any migration-related
          // tasks.
          this.#notifications = this.filterNonAppNotifications(
            JSON.parse(data)
          );
        }

        // populate the list of notifications by tag
        if (this.#notifications) {
          for (var origin in this.#notifications) {
            this.#byTag[origin] = Object.create(null);
            for (var id in this.#notifications[origin]) {
              var curNotification = this.#notifications[origin][id];
              if (curNotification.tag) {
                this.#byTag[origin][curNotification.tag] = curNotification;
              }
            }
          }
        }

        this.#loaded = true;
      },

      // If read failed, we assume we have no notifications to load.
      () => {
        this.#loaded = true;
        return this.createStore();
      }
    );
  }

  // Creates the notification directory.
  createStore() {
    var promise = IOUtils.makeDirectory(NOTIFICATION_STORE_DIR, {
      ignoreExisting: true,
    });
    return promise.then(this.createFile.bind(this));
  }

  // Creates the notification file once the directory is created.
  createFile() {
    return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, "", {
      tmpPath: NOTIFICATION_STORE_PATH + ".tmp",
    });
  }

  // Save current notifications to the file.
  save() {
    var data = JSON.stringify(this.#notifications);
    return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, data, {
      tmpPath: NOTIFICATION_STORE_PATH + ".tmp",
    });
  }

  // Helper function: promise will be resolved once file exists and/or is loaded.
  #ensureLoaded() {
    if (!this.#loaded) {
      return this.load();
    }
    return Promise.resolve();
  }

  receiveMessage(message) {
    lazy.console.debug(`Received message: ${message.name}`);

    // sendAsyncMessage can fail if the child process exits during a
    // notification storage operation, so always wrap it in a try/catch.
    function returnMessage(name, data) {
      try {
        message.target.sendAsyncMessage(name, data);
      } catch (e) {
        lazy.console.debug(`Return message failed, ${name}`);
      }
    }

    switch (message.name) {
      case this.formatMessageType("GetAll"):
        this.queueTask("getall", message.data)
          .then(notifications => {
            returnMessage(this.formatMessageType("GetAll:Return:OK"), {
              requestID: message.data.requestID,
              origin: message.data.origin,
              notifications,
            });
          })
          .catch(error => {
            returnMessage(this.formatMessageType("GetAll:Return:KO"), {
              requestID: message.data.requestID,
              origin: message.data.origin,
              errorMsg: error,
            });
          });
        break;

      case this.formatMessageType("Save"):
        this.queueTask("save", message.data)
          .then(() => {
            returnMessage(this.formatMessageType("Save:Return:OK"), {
              requestID: message.data.requestID,
            });
          })
          .catch(error => {
            returnMessage(this.formatMessageType("Save:Return:KO"), {
              requestID: message.data.requestID,
              errorMsg: error,
            });
          });
        break;

      case this.formatMessageType("Delete"):
        this.queueTask("delete", message.data)
          .then(() => {
            returnMessage(this.formatMessageType("Delete:Return:OK"), {
              requestID: message.data.requestID,
            });
          })
          .catch(error => {
            returnMessage(this.formatMessageType("Delete:Return:KO"), {
              requestID: message.data.requestID,
              errorMsg: error,
            });
          });
        break;

      default:
        lazy.console.debug(`Invalid message name ${message.name}`);
    }
  }

  // We need to make sure any read/write operations are atomic,
  // so use a queue to run each operation sequentially.
  queueTask(operation, data) {
    lazy.console.debug(`Queueing task: ${operation}`);

    var defer = {};

    this.#tasks.push({
      operation,
      data,
      defer,
    });

    var promise = new Promise((resolve, reject) => {
      defer.resolve = resolve;
      defer.reject = reject;
    });

    // Only run immediately if we aren't currently running another task.
    if (!this.#runningTask) {
      lazy.console.debug("Task queue was not running, starting now...");
      this.runNextTask();
      this.#queueDrainedPromise = new Promise(resolve => {
        this.#queueDrainedPromiseResolve = resolve;
      });
    }

    return promise;
  }

  runNextTask() {
    if (this.#tasks.length === 0) {
      lazy.console.debug("No more tasks to run, queue depleted");
      this.#runningTask = null;
      if (this.#queueDrainedPromiseResolve) {
        this.#queueDrainedPromiseResolve();
      } else {
        lazy.console.debug(
          "#queueDrainedPromiseResolve was null somehow, no promise to resolve"
        );
      }
      return;
    }
    this.#runningTask = this.#tasks.shift();

    // Always make sure we are loaded before performing any read/write tasks.
    this.#ensureLoaded()
      .then(() => {
        var task = this.#runningTask;

        switch (task.operation) {
          case "getall":
            return this.taskGetAll(task.data);

          case "save":
            return this.taskSave(task.data);

          case "delete":
            return this.taskDelete(task.data);

          default:
            return Promise.reject(
              new Error(`Found a task with unknown operation ${task.operation}`)
            );
        }
      })
      .then(payload => {
        lazy.console.debug(`Finishing task: ${this.#runningTask.operation}`);
        this.#runningTask.defer.resolve(payload);
      })
      .catch(err => {
        lazy.console.debug(
          `Error while running ${this.#runningTask.operation}: ${err}`
        );
        this.#runningTask.defer.reject(err);
      })
      .then(() => {
        this.runNextTask();
      });
  }

  taskGetAll(data) {
    lazy.console.debug("Task, getting all");
    var origin = data.origin;
    var notifications = [];
    // Grab only the notifications for specified origin.
    if (this.#notifications[origin]) {
      if (data.tag) {
        let n;
        if ((n = this.#byTag[origin][data.tag])) {
          notifications.push(n);
        }
      } else {
        for (var i in this.#notifications[origin]) {
          notifications.push(this.#notifications[origin][i]);
        }
      }
    }
    return Promise.resolve(notifications);
  }

  taskSave(data) {
    lazy.console.debug("Task, saving");
    var origin = data.origin;
    var notification = data.notification;
    if (!this.#notifications[origin]) {
      this.#notifications[origin] = Object.create(null);
      this.#byTag[origin] = Object.create(null);
    }

    // We might have existing notification with this tag,
    // if so we need to remove it before saving the new one.
    if (notification.tag) {
      var oldNotification = this.#byTag[origin][notification.tag];
      if (oldNotification) {
        delete this.#notifications[origin][oldNotification.id];
      }
      this.#byTag[origin][notification.tag] = notification;
    }

    this.#notifications[origin][notification.id] = notification;
    return this.save();
  }

  taskDelete(data) {
    lazy.console.debug("Task, deleting");
    var origin = data.origin;
    var id = data.id;
    if (!this.#notifications[origin]) {
      lazy.console.debug(`No notifications found for origin: ${origin}`);
      return Promise.resolve();
    }

    // Make sure we can find the notification to delete.
    var oldNotification = this.#notifications[origin][id];
    if (!oldNotification) {
      lazy.console.debug(`No notification found with id: ${id}`);
      return Promise.resolve();
    }

    if (oldNotification.tag) {
      delete this.#byTag[origin][oldNotification.tag];
    }
    delete this.#notifications[origin][id];
    return this.save();
  }
}

new NotificationDB();

[ Verzeichnis aufwärts0.37unsichere Verbindung  Übersetzung europäischer Sprachen durch Browser  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge