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


Quelle  bookmarks.sys.mjs   Sprache: unbekannt

 
Spracherkennung für: .mjs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

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

import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
import {
  Changeset,
  Store,
  SyncEngine,
  Tracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Async: "resource://services-common/async.sys.mjs",
  Observers: "resource://services-common/observers.sys.mjs",
  PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs",
  PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
  PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  Resource: "resource://services-sync/resource.sys.mjs",
  SyncedBookmarksMirror: "resource://gre/modules/SyncedBookmarksMirror.sys.mjs",
});

const PLACES_MAINTENANCE_INTERVAL_SECONDS = 4 * 60 * 60; // 4 hours.

const FOLDER_SORTINDEX = 1000000;

// Roots that should be deleted from the server, instead of applied locally.
// This matches `AndroidBrowserBookmarksRepositorySession::forbiddenGUID`,
// but allows tags because we don't want to reparent tag folders or tag items
// to "unfiled".
const FORBIDDEN_INCOMING_IDS = ["pinned", "places", "readinglist"];

// Items with these parents should be deleted from the server. We allow
// children of the Places root, to avoid orphaning left pane queries and other
// descendants of custom roots.
const FORBIDDEN_INCOMING_PARENT_IDS = ["pinned", "readinglist"];

// The tracker ignores changes made by import and restore, to avoid bumping the
// score and triggering syncs during the process, as well as changes made by
// Sync.
ChromeUtils.defineLazyGetter(lazy, "IGNORED_SOURCES", () => [
  lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
  lazy.PlacesUtils.bookmarks.SOURCES.IMPORT,
  lazy.PlacesUtils.bookmarks.SOURCES.RESTORE,
  lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
  lazy.PlacesUtils.bookmarks.SOURCES.SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
]);

// The validation telemetry version for the engine. Version 1 is collected
// by `bookmark_validator.js`, and checks value as well as structure
// differences. Version 2 is collected by the engine as part of building the
// remote tree, and checks structure differences only.
const BOOKMARK_VALIDATOR_VERSION = 2;

// The maximum time that the engine should wait before aborting a bookmark
// merge.
const BOOKMARK_APPLY_TIMEOUT_MS = 5 * 60 * 60 * 1000; // 5 minutes

// The default frecency value to use when not known.
const FRECENCY_UNKNOWN = -1;

// Returns the constructor for a bookmark record type.
function getTypeObject(type) {
  switch (type) {
    case "bookmark":
      return Bookmark;
    case "query":
      return BookmarkQuery;
    case "folder":
      return BookmarkFolder;
    case "livemark":
      return Livemark;
    case "separator":
      return BookmarkSeparator;
    case "item":
      return PlacesItem;
  }
  return null;
}

export function PlacesItem(collection, id, type) {
  CryptoWrapper.call(this, collection, id);
  this.type = type || "item";
}

PlacesItem.prototype = {
  async decrypt(keyBundle) {
    // Do the normal CryptoWrapper decrypt, but change types before returning
    let clear = await CryptoWrapper.prototype.decrypt.call(this, keyBundle);

    // Convert the abstract places item to the actual object type
    if (!this.deleted) {
      Object.setPrototypeOf(this, this.getTypeObject(this.type).prototype);
    }

    return clear;
  },

  getTypeObject: function PlacesItem_getTypeObject(type) {
    let recordObj = getTypeObject(type);
    if (!recordObj) {
      throw new Error("Unknown places item object type: " + type);
    }
    return recordObj;
  },

  _logName: "Sync.Record.PlacesItem",

  // Converts the record to a Sync bookmark object that can be passed to
  // `PlacesSyncUtils.bookmarks.{insert, update}`.
  toSyncBookmark() {
    let result = {
      kind: this.type,
      recordId: this.id,
      parentRecordId: this.parentid,
    };
    let dateAdded = lazy.PlacesSyncUtils.bookmarks.ratchetTimestampBackwards(
      this.dateAdded,
      +this.modified * 1000
    );
    if (dateAdded > 0) {
      result.dateAdded = dateAdded;
    }
    return result;
  },

  // Populates the record from a Sync bookmark object returned from
  // `PlacesSyncUtils.bookmarks.fetch`.
  fromSyncBookmark(item) {
    this.parentid = item.parentRecordId;
    this.parentName = item.parentTitle;
    if (item.dateAdded) {
      this.dateAdded = item.dateAdded;
    }
  },
};

Object.setPrototypeOf(PlacesItem.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(PlacesItem, "cleartext", [
  "hasDupe",
  "parentid",
  "parentName",
  "type",
  "dateAdded",
]);

export function Bookmark(collection, id, type) {
  PlacesItem.call(this, collection, id, type || "bookmark");
}

Bookmark.prototype = {
  _logName: "Sync.Record.Bookmark",

  toSyncBookmark() {
    let info = PlacesItem.prototype.toSyncBookmark.call(this);
    info.title = this.title;
    info.url = this.bmkUri;
    info.description = this.description;
    info.tags = this.tags;
    info.keyword = this.keyword;
    return info;
  },

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.title = item.title;
    this.bmkUri = item.url.href;
    this.description = item.description;
    this.tags = item.tags;
    this.keyword = item.keyword;
  },
};

Object.setPrototypeOf(Bookmark.prototype, PlacesItem.prototype);

Utils.deferGetSet(Bookmark, "cleartext", [
  "title",
  "bmkUri",
  "description",
  "tags",
  "keyword",
]);

export function BookmarkQuery(collection, id) {
  Bookmark.call(this, collection, id, "query");
}

BookmarkQuery.prototype = {
  _logName: "Sync.Record.BookmarkQuery",

  toSyncBookmark() {
    let info = Bookmark.prototype.toSyncBookmark.call(this);
    info.folder = this.folderName || undefined; // empty string -> undefined
    info.query = this.queryId;
    return info;
  },

  fromSyncBookmark(item) {
    Bookmark.prototype.fromSyncBookmark.call(this, item);
    this.folderName = item.folder || undefined; // empty string -> undefined
    this.queryId = item.query;
  },
};

Object.setPrototypeOf(BookmarkQuery.prototype, Bookmark.prototype);

Utils.deferGetSet(BookmarkQuery, "cleartext", ["folderName", "queryId"]);

export function BookmarkFolder(collection, id, type) {
  PlacesItem.call(this, collection, id, type || "folder");
}

BookmarkFolder.prototype = {
  _logName: "Sync.Record.Folder",

  toSyncBookmark() {
    let info = PlacesItem.prototype.toSyncBookmark.call(this);
    info.description = this.description;
    info.title = this.title;
    return info;
  },

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.title = item.title;
    this.description = item.description;
    this.children = item.childRecordIds;
  },
};

Object.setPrototypeOf(BookmarkFolder.prototype, PlacesItem.prototype);

Utils.deferGetSet(BookmarkFolder, "cleartext", [
  "description",
  "title",
  "children",
]);

export function Livemark(collection, id) {
  BookmarkFolder.call(this, collection, id, "livemark");
}

Livemark.prototype = {
  _logName: "Sync.Record.Livemark",

  toSyncBookmark() {
    let info = BookmarkFolder.prototype.toSyncBookmark.call(this);
    info.feed = this.feedUri;
    info.site = this.siteUri;
    return info;
  },

  fromSyncBookmark(item) {
    BookmarkFolder.prototype.fromSyncBookmark.call(this, item);
    this.feedUri = item.feed.href;
    if (item.site) {
      this.siteUri = item.site.href;
    }
  },
};

Object.setPrototypeOf(Livemark.prototype, BookmarkFolder.prototype);

Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]);

export function BookmarkSeparator(collection, id) {
  PlacesItem.call(this, collection, id, "separator");
}

BookmarkSeparator.prototype = {
  _logName: "Sync.Record.Separator",

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.pos = item.index;
  },
};

Object.setPrototypeOf(BookmarkSeparator.prototype, PlacesItem.prototype);

Utils.deferGetSet(BookmarkSeparator, "cleartext", "pos");

/**
 * The bookmarks engine uses a different store that stages downloaded bookmarks
 * in a separate database, instead of writing directly to Places. The buffer
 * handles reconciliation, so we stub out `_reconcile`, and wait to pull changes
 * until we're ready to upload.
 */
export function BookmarksEngine(service) {
  SyncEngine.call(this, "Bookmarks", service);
}

BookmarksEngine.prototype = {
  _recordObj: PlacesItem,
  _trackerObj: BookmarksTracker,
  _storeObj: BookmarksStore,
  version: 2,
  // Used to override the engine name in telemetry, so that we can distinguish
  // this engine from the old, now removed non-buffered engine.
  overrideTelemetryName: "bookmarks-buffered",

  // Needed to ensure we don't miss items when resuming a sync that failed or
  // aborted early.
  _defaultSort: "oldest",

  syncPriority: 4,
  allowSkippedRecord: false,

  async _ensureCurrentSyncID(newSyncID) {
    await lazy.PlacesSyncUtils.bookmarks.ensureCurrentSyncId(newSyncID);
    let buf = await this._store.ensureOpenMirror();
    await buf.ensureCurrentSyncId(newSyncID);
  },

  async ensureCurrentSyncID(newSyncID) {
    let shouldWipeRemote =
      await lazy.PlacesSyncUtils.bookmarks.shouldWipeRemote();
    if (!shouldWipeRemote) {
      this._log.debug(
        "Checking if server sync ID ${newSyncID} matches existing",
        { newSyncID }
      );
      await this._ensureCurrentSyncID(newSyncID);
      return newSyncID;
    }
    // We didn't take the new sync ID because we need to wipe the server
    // and other clients after a restore. Send the command, wipe the
    // server, and reset our sync ID to reupload everything.
    this._log.debug(
      "Ignoring server sync ID ${newSyncID} after restore; " +
        "wiping server and resetting sync ID",
      { newSyncID }
    );
    await this.service.clientsEngine.sendCommand(
      "wipeEngine",
      [this.name],
      null,
      { reason: "bookmark-restore" }
    );
    let assignedSyncID = await this.resetSyncID();
    return assignedSyncID;
  },

  async getSyncID() {
    return lazy.PlacesSyncUtils.bookmarks.getSyncId();
  },

  async resetSyncID() {
    await this._deleteServerCollection();
    return this.resetLocalSyncID();
  },

  async resetLocalSyncID() {
    let newSyncID = await lazy.PlacesSyncUtils.bookmarks.resetSyncId();
    this._log.debug("Assigned new sync ID ${newSyncID}", { newSyncID });
    let buf = await this._store.ensureOpenMirror();
    await buf.ensureCurrentSyncId(newSyncID);
    return newSyncID;
  },

  async getLastSync() {
    let mirror = await this._store.ensureOpenMirror();
    return mirror.getCollectionHighWaterMark();
  },

  async setLastSync(lastSync) {
    let mirror = await this._store.ensureOpenMirror();
    await mirror.setCollectionLastModified(lastSync);
    // Update the last sync time in Places so that reverting to the original
    // bookmarks engine doesn't download records we've already applied.
    await lazy.PlacesSyncUtils.bookmarks.setLastSync(lastSync);
  },

  async _syncStartup() {
    await super._syncStartup();

    try {
      // For first syncs, back up the user's bookmarks.
      let lastSync = await this.getLastSync();
      if (!lastSync) {
        this._log.debug("Bookmarks backup starting");
        await lazy.PlacesBackups.create(null, true);
        this._log.debug("Bookmarks backup done");
      }
    } catch (ex) {
      // Failure to create a backup is somewhat bad, but probably not bad
      // enough to prevent syncing of bookmarks - so just log the error and
      // continue.
      this._log.warn(
        "Error while backing up bookmarks, but continuing with sync",
        ex
      );
    }
  },

  async _sync() {
    try {
      await super._sync();
      if (this._ranMaintenanceOnLastSync) {
        // If the last sync failed, we ran maintenance, and this sync succeeded,
        // maintenance likely fixed the issue.
        this._ranMaintenanceOnLastSync = false;
        this.service.recordTelemetryEvent("maintenance", "fix", "bookmarks");
      }
    } catch (ex) {
      if (
        lazy.Async.isShutdownException(ex) ||
        ex.status > 0 ||
        ex.name == "InterruptedError"
      ) {
        // Don't run maintenance on shutdown or HTTP errors, or if we aborted
        // the sync because the user changed their bookmarks during merging.
        throw ex;
      }
      if (ex.name == "MergeConflictError") {
        this._log.warn(
          "Bookmark syncing ran into a merge conflict error...will retry later"
        );
        return;
      }
      // Run Places maintenance periodically to try to recover from corruption
      // that might have caused the sync to fail. We cap the interval because
      // persistent failures likely indicate a problem that won't be fixed by
      // running maintenance after every failed sync.
      let elapsedSinceMaintenance =
        Date.now() / 1000 -
        Services.prefs.getIntPref("places.database.lastMaintenance", 0);
      if (elapsedSinceMaintenance >= PLACES_MAINTENANCE_INTERVAL_SECONDS) {
        this._log.error(
          "Bookmark sync failed, ${elapsedSinceMaintenance}s " +
            "elapsed since last run; running Places maintenance",
          { elapsedSinceMaintenance }
        );
        await lazy.PlacesDBUtils.maintenanceOnIdle();
        this._ranMaintenanceOnLastSync = true;
        this.service.recordTelemetryEvent("maintenance", "run", "bookmarks");
      } else {
        this._ranMaintenanceOnLastSync = false;
      }
      throw ex;
    }
  },

  async _syncFinish() {
    await SyncEngine.prototype._syncFinish.call(this);
    await lazy.PlacesSyncUtils.bookmarks.ensureMobileQuery();
  },

  async pullAllChanges() {
    return this.pullNewChanges();
  },

  async trackRemainingChanges() {
    let changes = this._modified.changes;
    await lazy.PlacesSyncUtils.bookmarks.pushChanges(changes);
  },

  _deleteId(id) {
    this._noteDeletedId(id);
  },

  // The bookmarks engine rarely calls this method directly, except in tests or
  // when handling a `reset{All, Engine}` command from another client. We
  // usually reset local Sync metadata on a sync ID mismatch, which both engines
  // override with logic that lives in Places and the mirror.
  async _resetClient() {
    await super._resetClient();
    await lazy.PlacesSyncUtils.bookmarks.reset();
    let buf = await this._store.ensureOpenMirror();
    await buf.reset();
  },

  // Cleans up the Places root, reading list items (ignored in bug 762118,
  // removed in bug 1155684), and pinned sites.
  _shouldDeleteRemotely(incomingItem) {
    return (
      FORBIDDEN_INCOMING_IDS.includes(incomingItem.id) ||
      FORBIDDEN_INCOMING_PARENT_IDS.includes(incomingItem.parentid)
    );
  },

  emptyChangeset() {
    return new BookmarksChangeset();
  },

  async _apply() {
    let buf = await this._store.ensureOpenMirror();
    let watchdog = this._newWatchdog();
    watchdog.start(BOOKMARK_APPLY_TIMEOUT_MS);

    try {
      let recordsToUpload = await buf.apply({
        remoteTimeSeconds: lazy.Resource.serverTime,
        signal: watchdog.signal,
      });
      this._modified.replace(recordsToUpload);
    } finally {
      watchdog.stop();
      if (watchdog.abortReason) {
        this._log.warn(`Aborting bookmark merge: ${watchdog.abortReason}`);
      }
    }
  },

  async _processIncoming(newitems) {
    await super._processIncoming(newitems);
    await this._apply();
  },

  async _reconcile() {
    return true;
  },

  async _createRecord(id) {
    let record = await this._doCreateRecord(id);
    if (!record.deleted) {
      // Set hasDupe on all (non-deleted) records since we don't use it and we
      // want to minimize the risk of older clients corrupting records. Note
      // that the SyncedBookmarksMirror sets it for all records that it created,
      // but we would like to ensure that weakly uploaded records are marked as
      // hasDupe as well.
      record.hasDupe = true;
    }
    return record;
  },

  async _doCreateRecord(id) {
    let change = this._modified.changes[id];
    if (!change) {
      this._log.error(
        "Creating record for item ${id} not in strong changeset",
        { id }
      );
      throw new TypeError("Can't create record for unchanged item");
    }
    let record = this._recordFromCleartext(id, change.cleartext);
    record.sortindex = await this._store._calculateIndex(record);
    return record;
  },

  _recordFromCleartext(id, cleartext) {
    let recordObj = getTypeObject(cleartext.type);
    if (!recordObj) {
      this._log.warn(
        "Creating record for item ${id} with unknown type ${type}",
        { id, type: cleartext.type }
      );
      recordObj = PlacesItem;
    }
    let record = new recordObj(this.name, id);
    record.cleartext = cleartext;
    return record;
  },

  async pullChanges() {
    return {};
  },

  /**
   * Writes successfully uploaded records back to the mirror, so that the
   * mirror matches the server. We update the mirror before updating Places,
   * which has implications for interrupted syncs.
   *
   * 1. Sync interrupted during upload; server doesn't support atomic uploads.
   *    We'll download and reapply everything that we uploaded before the
   *    interruption. All locally changed items retain their change counters.
   * 2. Sync interrupted during upload; atomic uploads enabled. The server
   *    discards the batch. All changed local items retain their change
   *    counters, so the next sync resumes cleanly.
   * 3. Sync interrupted during upload; outgoing records can't fit in a single
   *    batch. We'll download and reapply all records through the most recent
   *    committed batch. This is a variation of (1).
   * 4. Sync interrupted after we update the mirror, but before cleanup. The
   *    mirror matches the server, but locally changed items retain their change
   *    counters. Reuploading them on the next sync should be idempotent, though
   *    unnecessary. If another client makes a conflicting remote change before
   *    we sync again, we may incorrectly prefer the local state.
   * 5. Sync completes successfully. We'll update the mirror, and reset the
   *    change counters for all items.
   */
  async _onRecordsWritten(succeeded, failed, serverModifiedTime) {
    let records = [];
    for (let id of succeeded) {
      let change = this._modified.changes[id];
      if (!change) {
        // TODO (Bug 1433178): Write weakly uploaded records back to the mirror.
        this._log.info("Uploaded record not in strong changeset", id);
        continue;
      }
      if (!change.synced) {
        this._log.info("Record in strong changeset not uploaded", id);
        continue;
      }
      let cleartext = change.cleartext;
      if (!cleartext) {
        this._log.error(
          "Missing Sync record cleartext for ${id} in ${change}",
          { id, change }
        );
        throw new TypeError("Missing cleartext for uploaded Sync record");
      }
      let record = this._recordFromCleartext(id, cleartext);
      record.modified = serverModifiedTime;
      records.push(record);
    }
    let buf = await this._store.ensureOpenMirror();
    await buf.store(records, { needsMerge: false });
  },

  async finalize() {
    await super.finalize();
    await this._store.finalize();
  },
};

Object.setPrototypeOf(BookmarksEngine.prototype, SyncEngine.prototype);

/**
 * The bookmarks store delegates to the mirror for staging and applying
 * records. Most `Store` methods intentionally remain abstract, so you can't use
 * this store to create or update bookmarks in Places. All changes must go
 * through the mirror, which takes care of merging and producing a valid tree.
 */
function BookmarksStore(name, engine) {
  Store.call(this, name, engine);
}

BookmarksStore.prototype = {
  _openMirrorPromise: null,

  // For tests.
  _batchChunkSize: 500,

  // Create a record starting from the weave id (places guid)
  async createRecord(id, collection) {
    let item = await lazy.PlacesSyncUtils.bookmarks.fetch(id);
    if (!item) {
      // deleted item
      let record = new PlacesItem(collection, id);
      record.deleted = true;
      return record;
    }

    let recordObj = getTypeObject(item.kind);
    if (!recordObj) {
      this._log.warn("Unknown item type, cannot serialize: " + item.kind);
      recordObj = PlacesItem;
    }
    let record = new recordObj(collection, id);
    record.fromSyncBookmark(item);

    record.sortindex = await this._calculateIndex(record);

    return record;
  },

  async _calculateIndex(record) {
    // Ensure folders have a very high sort index so they're not synced last.
    if (record.type == "folder") {
      return FOLDER_SORTINDEX;
    }

    // For anything directly under the toolbar, give it a boost of more than an
    // unvisited bookmark
    let index = 0;
    if (record.parentid == "toolbar") {
      index += 150;
    }

    // Add in the bookmark's frecency if we have something.
    if (record.bmkUri != null) {
      let frecency = FRECENCY_UNKNOWN;
      try {
        frecency = await lazy.PlacesSyncUtils.history.fetchURLFrecency(
          record.bmkUri
        );
      } catch (ex) {
        this._log.warn(
          `Failed to fetch frecency for ${record.id}; assuming default`,
          ex
        );
        this._log.trace("Record {id} has invalid URL ${bmkUri}", record);
      }
      if (frecency != FRECENCY_UNKNOWN) {
        index += frecency;
      }
    }

    return index;
  },

  async wipe() {
    // Save a backup before clearing out all bookmarks.
    await lazy.PlacesBackups.create(null, true);
    await lazy.PlacesSyncUtils.bookmarks.wipe();
  },

  ensureOpenMirror() {
    if (!this._openMirrorPromise) {
      this._openMirrorPromise = this._openMirror().catch(err => {
        // We may have failed to open the mirror temporarily; for example, if
        // the database is locked. Clear the promise so that subsequent
        // `ensureOpenMirror` calls can try to open the mirror again.
        this._openMirrorPromise = null;
        throw err;
      });
    }
    return this._openMirrorPromise;
  },

  async _openMirror() {
    let mirrorPath = PathUtils.join(
      PathUtils.profileDir,
      "weave",
      "bookmarks.sqlite"
    );
    await IOUtils.makeDirectory(PathUtils.parent(mirrorPath), {
      createAncestors: true,
    });

    return lazy.SyncedBookmarksMirror.open({
      path: mirrorPath,
      recordStepTelemetry: (name, took, counts) => {
        lazy.Observers.notify(
          "weave:engine:sync:step",
          {
            name,
            took,
            counts,
          },
          this.name
        );
      },
      recordValidationTelemetry: (took, checked, problems) => {
        lazy.Observers.notify(
          "weave:engine:validate:finish",
          {
            version: BOOKMARK_VALIDATOR_VERSION,
            took,
            checked,
            problems,
          },
          this.name
        );
      },
    });
  },

  async applyIncomingBatch(records) {
    let buf = await this.ensureOpenMirror();
    for (let chunk of lazy.PlacesUtils.chunkArray(
      records,
      this._batchChunkSize
    )) {
      await buf.store(chunk);
    }
    // Array of failed records.
    return [];
  },

  async applyIncoming(record) {
    let buf = await this.ensureOpenMirror();
    await buf.store([record]);
  },

  async finalize() {
    if (!this._openMirrorPromise) {
      return;
    }
    let buf = await this._openMirrorPromise;
    await buf.finalize();
  },
};

Object.setPrototypeOf(BookmarksStore.prototype, Store.prototype);

// The bookmarks tracker is a special flower. Instead of listening for changes
// via observer notifications, it queries Places for the set of items that have
// changed since the last sync. Because it's a "pull-based" tracker, it ignores
// all concepts of "add a changed ID." However, it still registers an observer
// to bump the score, so that changed bookmarks are synced immediately.
function BookmarksTracker(name, engine) {
  Tracker.call(this, name, engine);
}
BookmarksTracker.prototype = {
  onStart() {
    this._placesListener = new PlacesWeakCallbackWrapper(
      this.handlePlacesEvents.bind(this)
    );
    lazy.PlacesUtils.observers.addListener(
      [
        "bookmark-added",
        "bookmark-removed",
        "bookmark-moved",
        "bookmark-guid-changed",
        "bookmark-keyword-changed",
        "bookmark-tags-changed",
        "bookmark-time-changed",
        "bookmark-title-changed",
        "bookmark-url-changed",
      ],
      this._placesListener
    );
    Svc.Obs.add("bookmarks-restore-begin", this);
    Svc.Obs.add("bookmarks-restore-success", this);
    Svc.Obs.add("bookmarks-restore-failed", this);
  },

  onStop() {
    lazy.PlacesUtils.observers.removeListener(
      [
        "bookmark-added",
        "bookmark-removed",
        "bookmark-moved",
        "bookmark-guid-changed",
        "bookmark-keyword-changed",
        "bookmark-tags-changed",
        "bookmark-time-changed",
        "bookmark-title-changed",
        "bookmark-url-changed",
      ],
      this._placesListener
    );
    Svc.Obs.remove("bookmarks-restore-begin", this);
    Svc.Obs.remove("bookmarks-restore-success", this);
    Svc.Obs.remove("bookmarks-restore-failed", this);
  },

  async getChangedIDs() {
    return lazy.PlacesSyncUtils.bookmarks.pullChanges();
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "bookmarks-restore-begin":
        this._log.debug("Ignoring changes from importing bookmarks.");
        break;
      case "bookmarks-restore-success":
        this._log.debug("Tracking all items on successful import.");

        if (data == "json") {
          this._log.debug(
            "Restore succeeded: wiping server and other clients."
          );
          // Trigger an immediate sync. `ensureCurrentSyncID` will notice we
          // restored, wipe the server and other clients, reset the sync ID, and
          // upload the restored tree.
          this.score += SCORE_INCREMENT_XLARGE;
        } else {
          // "html", "html-initial", or "json-append"
          this._log.debug("Import succeeded.");
        }
        break;
      case "bookmarks-restore-failed":
        this._log.debug("Tracking all items on failed import.");
        break;
    }
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),

  /* Every add/remove/change will trigger a sync for MULTI_DEVICE */
  _upScore: function BMT__upScore() {
    this.score += SCORE_INCREMENT_XLARGE;
  },

  handlePlacesEvents(events) {
    for (let event of events) {
      switch (event.type) {
        case "bookmark-added":
        case "bookmark-removed":
        case "bookmark-moved":
        case "bookmark-keyword-changed":
        case "bookmark-tags-changed":
        case "bookmark-time-changed":
        case "bookmark-title-changed":
        case "bookmark-url-changed":
          if (lazy.IGNORED_SOURCES.includes(event.source)) {
            continue;
          }

          this._log.trace(`'${event.type}': ${event.id}`);
          this._upScore();
          break;
        case "bookmark-guid-changed":
          if (event.source !== lazy.PlacesUtils.bookmarks.SOURCES.SYNC) {
            this._log.warn(
              "The source of bookmark-guid-changed event shoud be sync."
            );
            continue;
          }

          this._log.trace(`'${event.type}': ${event.id}`);
          this._upScore();
          break;
        case "purge-caches":
          this._log.trace("purge-caches");
          this._upScore();
          break;
      }
    }
  },
};

Object.setPrototypeOf(BookmarksTracker.prototype, Tracker.prototype);

/**
 * A changeset that stores extra metadata in a change record for each ID. The
 * engine updates this metadata when uploading Sync records, and writes it back
 * to Places in `BookmarksEngine#trackRemainingChanges`.
 *
 * The `synced` property on a change record means its corresponding item has
 * been uploaded, and we should pretend it doesn't exist in the changeset.
 */
class BookmarksChangeset extends Changeset {
  // Only `_reconcile` calls `getModifiedTimestamp` and `has`, and the engine
  // does its own reconciliation.
  getModifiedTimestamp() {
    throw new Error("Don't use timestamps to resolve bookmark conflicts");
  }

  has() {
    throw new Error("Don't use the changeset to resolve bookmark conflicts");
  }

  delete(id) {
    let change = this.changes[id];
    if (change) {
      // Mark the change as synced without removing it from the set. We do this
      // so that we can update Places in `trackRemainingChanges`.
      change.synced = true;
    }
  }

  ids() {
    let results = new Set();
    for (let id in this.changes) {
      if (!this.changes[id].synced) {
        results.add(id);
      }
    }
    return [...results];
  }
}

[ Dauer der Verarbeitung: 0.43 Sekunden  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


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