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


Quelle  ext-storage.js   Sprache: JAVA

 
/* 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";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionStorage: "resource://gre/modules/ExtensionStorage.sys.mjs",
  ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.sys.mjs",
  ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.sys.mjs",
});

// Wrap a storage operation in a TelemetryStopWatch.
async function measureOp(telemetryMetric, extension, fn) {
  const stopwatchKey = {};
  telemetryMetric.stopwatchStart(extension, stopwatchKey);
  try {
    let result = await fn();
    telemetryMetric.stopwatchFinish(extension, stopwatchKey);
    return result;
  } catch (err) {
    telemetryMetric.stopwatchCancel(extension, stopwatchKey);
    throw err;
  }
}

this.storage = class extends ExtensionAPI {
  getLocalFileBackend(context, { deserialize, serialize }) {
    return {
      get(keys) {
        return context.childManager
          .callParentAsyncFunction("storage.local.JSONFileBackend.get", [
            serialize(keys),
          ])
          .then(deserialize);
      },
      set(items) {
        return context.childManager.callParentAsyncFunction(
          "storage.local.JSONFileBackend.set",
          [serialize(items)]
        );
      },
      remove(keys) {
        return context.childManager.callParentAsyncFunction(
          "storage.local.JSONFileBackend.remove",
          [serialize(keys)]
        );
      },
      clear() {
        return context.childManager.callParentAsyncFunction(
          "storage.local.JSONFileBackend.clear",
          []
        );
      },
    };
  }

  getLocalIDBBackend(context, { fireOnChanged, storagePrincipal }) {
    let dbPromise;
    async function getDB() {
      if (dbPromise) {
        return dbPromise;
      }

      const persisted = context.extension.hasPermission("unlimitedStorage");
      dbPromise = ExtensionStorageIDB.open(storagePrincipal, persisted).catch(
        err => {
          // Reset the cached promise if it has been rejected, so that the next
          // API call is going to retry to open the DB.
          dbPromise = null;
          throw err;
        }
      );

      return dbPromise;
    }

    return {
      get(keys) {
        return measureOp(
          ExtensionTelemetry.storageLocalGetIdb,
          context.extension,
          async () => {
            const db = await getDB();
            return db.get(keys);
          }
        );
      },
      set(items) {
        function serialize(name, anonymizedName, value) {
          return ExtensionStorage.serialize(
            `set/${context.extension.id}/${name}`,
            `set/${context.extension.id}/${anonymizedName}`,
            value
          );
        }

        return measureOp(
          ExtensionTelemetry.storageLocalSetIdb,
          context.extension,
          async () => {
            const db = await getDB();
            const changes = await db.set(items, {
              serialize,
            });

            if (changes) {
              fireOnChanged(changes);
            }
          }
        );
      },
      async remove(keys) {
        const db = await getDB();
        const changes = await db.remove(keys);

        if (changes) {
          fireOnChanged(changes);
        }
      },
      async clear() {
        const db = await getDB();
        const changes = await db.clear(context.extension);

        if (changes) {
          fireOnChanged(changes);
        }
      },
    };
  }

  getAPI(context) {
    const { extension } = context;
    const serialize = ExtensionStorage.serializeForContext.bind(null, context);
    const deserialize = ExtensionStorage.deserializeForContext.bind(
      null,
      context
    );

    // onChangedName is "storage.onChanged", "storage.sync.onChanged", etc.
    function makeOnChangedEventTarget(onChangedName) {
      return new EventManager({
        context,
        name: onChangedName,
        // Parent event already resets idle if needed, no need to do it here.
        resetIdleOnEvent: false,
        register: fire => {
          let onChanged = (data, area) => {
            let changes = new context.cloneScope.Object();
            for (let [key, value] of Object.entries(data)) {
              changes[key] = deserialize(value);
            }
            if (area) {
              // storage.onChanged includes the area.
              fire.raw(changes, area);
            } else {
              // StorageArea.onChanged doesn't include the area.
              fire.raw(changes);
            }
          };

          let parent = context.childManager.getParentEvent(onChangedName);
          parent.addListener(onChanged);
          return () => {
            parent.removeListener(onChanged);
          };
        },
      }).api();
    }

    function sanitize(items) {
      // The schema validator already takes care of arrays (which are only allowed
      // to contain strings). Strings and null are safe values.
      if (typeof items != "object" || items === null || Array.isArray(items)) {
        return items;
      }
      // If we got here, then `items` is an object generated by `ObjectType`'s
      // `normalize` method from Schemas.sys.mjs. The object returned by `normalize`
      // lives in this compartment, while the values live in compartment of
      // `context.contentWindow`. The `sanitize` method runs with the principal
      // of `context`, so we cannot just use `ExtensionStorage.sanitize` because
      // it is not allowed to access properties of `items`.
      // So we enumerate all properties and sanitize each value individually.
      let sanitized = {};
      for (let [key, value] of Object.entries(items)) {
        sanitized[key] = ExtensionStorage.sanitize(value, context);
      }
      return sanitized;
    }

    function fireOnChanged(changes) {
      // This call is used (by the storage.local API methods for the IndexedDB backend) to fire a storage.onChanged event,
      // it uses the underlying message manager since the child context (or its ProxyContentParent counterpart
      // running in the main process) may be gone by the time we call this, and so we can't use the childManager
      // abstractions (e.g. callParentAsyncFunction or callParentFunctionNoReturn).
      Services.cpmm.sendAsyncMessage(
        `Extension:StorageLocalOnChanged:${extension.uuid}`,
        changes
      );
    }

    // If the selected backend for the extension is not known yet, we have to lazily detect it
    // by asking to the main process (as soon as the storage.local API has been accessed for
    // the first time).
    const getStorageLocalBackend = async () => {
      const { backendEnabled, storagePrincipal } =
        await ExtensionStorageIDB.selectBackend(context);

      if (!backendEnabled) {
        return this.getLocalFileBackend(context, { deserialize, serialize });
      }

      return this.getLocalIDBBackend(context, {
        storagePrincipal,
        fireOnChanged,
        serialize,
      });
    };

    // Synchronously select the backend if it is already known.
    let selectedBackend;

    const useStorageIDBBackend = extension.getSharedData("storageIDBBackend");
    if (useStorageIDBBackend === false) {
      selectedBackend = this.getLocalFileBackend(context, {
        deserialize,
        serialize,
      });
    } else if (useStorageIDBBackend === true) {
      selectedBackend = this.getLocalIDBBackend(context, {
        storagePrincipal: extension.getSharedData("storageIDBPrincipal"),
        fireOnChanged,
        serialize,
      });
    }

    let promiseStorageLocalBackend;

    // Generate the backend-agnostic local API wrapped methods.
    const local = {
      onChanged: makeOnChangedEventTarget("storage.local.onChanged"),
    };
    for (let method of ["get""set""remove""clear"]) {
      local[method] = async function (...args) {
        try {
          // Discover the selected backend if it is not known yet.
          if (!selectedBackend) {
            if (!promiseStorageLocalBackend) {
              promiseStorageLocalBackend = getStorageLocalBackend().catch(
                err => {
                  // Clear the cached promise if it has been rejected.
                  promiseStorageLocalBackend = null;
                  throw err;
                }
              );
            }

            // If the storage.local method is not 'get' (which doesn't change any of the stored data),
            // fall back to call the method in the parent process, so that it can be completed even
            // if this context has been destroyed in the meantime.
            if (method !== "get") {
              // Let the outer try to catch rejections returned by the backend methods.
              try {
                const result =
                  await context.childManager.callParentAsyncFunction(
                    "storage.local.callMethodInParentProcess",
                    [method, args]
                  );
                return result;
              } catch (err) {
                // Just return the rejection as is, the error has been normalized in the
                // parent process by callMethodInParentProcess and the original error
                // logged in the browser console.
                return Promise.reject(err);
              }
            }

            // Get the selected backend and cache it for the next API calls from this context.
            selectedBackend = await promiseStorageLocalBackend;
          }

          // Let the outer try to catch rejections returned by the backend methods.
          const result = await selectedBackend[method](...args);
          return result;
        } catch (err) {
          throw ExtensionStorageIDB.normalizeStorageError({
            error: err,
            extensionId: extension.id,
            storageMethod: method,
          });
        }
      };
    }

    return {
      storage: {
        local,

        session: {
          async get(keys) {
            return deserialize(
              await context.childManager.callParentAsyncFunction(
                "storage.session.get",
                [serialize(keys)]
              )
            );
          },
          set(items) {
            return context.childManager.callParentAsyncFunction(
              "storage.session.set",
              [serialize(items), { callerLocation: context.getCaller() }]
            );
          },
          onChanged: makeOnChangedEventTarget("storage.session.onChanged"),
        },

        sync: {
          get(keys) {
            keys = sanitize(keys);
            return context.childManager.callParentAsyncFunction(
              "storage.sync.get",
              [keys]
            );
          },
          set(items) {
            items = sanitize(items);
            return context.childManager.callParentAsyncFunction(
              "storage.sync.set",
              [items]
            );
          },
          onChanged: makeOnChangedEventTarget("storage.sync.onChanged"),
        },

        managed: {
          get(keys) {
            return context.childManager
              .callParentAsyncFunction("storage.managed.get", [serialize(keys)])
              .then(deserialize);
          },
          set() {
            return Promise.reject({ message: "storage.managed is read-only" });
          },
          remove() {
            return Promise.reject({ message: "storage.managed is read-only" });
          },
          clear() {
            return Promise.reject({ message: "storage.managed is read-only" });
          },

          onChanged: makeOnChangedEventTarget("storage.managed.onChanged"),
        },

        onChanged: makeOnChangedEventTarget("storage.onChanged"),
      },
    };
  }
};

Messung V0.5
C=94 H=100 G=96

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


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