ate(changes).catch(console.error); }, async getEntry(guid) { let results = await this._search(this._getEntryCols, { guid }); if (!results.length) { return null; } return { name: results[0].fieldname, value: results[0].value }; }, async getGUID(name, value) { // Query for the provided entry. let query = { fieldname: name, value }; let results = await this._search(this._guidCols, query); return results.length ? results[0].guid : null; }, async hasGUID(guid) { // We could probably use a count function here, but search exists... let results = await this._search(this._guidCols, { guid }); return !!results.length; }, async replaceGUID(oldGUID, newGUID) { let changes = { op: "update", guid: oldGUID, newGuid: newGUID, }; await this._update(changes); }, }; export function FormEngine(service) { SyncEngine.call(this, "Forms", service); } FormEngine.prototype = { _storeObj: FormStore, _trackerObj: FormTracker, _recordObj: FormRec, syncPriority: 6, get prefName() { return "history"; }, async _findDupe(item) { return FormWrapper.getGUID(item.name, item.value); }, }; Object.setPrototypeOf(FormEngine.prototype, SyncEngine.prototype); function FormStore(name, engine) { Store.call(this, name, engine); } FormStore.prototype = { async _processChange(change) { // If this._changes is defined, then we are applying a batch, so we // can defer it. if (this._changes) { this._changes.push(change); return; } // Otherwise we must handle the change right now. await FormWrapper._update(change); }, async applyIncomingBatch(records, countTelemetry) { Async.checkAppReady(); // We collect all the changes to be made then apply them all at once. this._changes = []; let failures = await Store.prototype.applyIncomingBatch.call( this, records, countTelemetry ); if (this._changes.length) { await FormWrapper._update(this._changes); } delete this._changes; return failures; }, async getAllIDs() { let results = await FormWrapper._search(["guid"], []); let guids = {}; for (let result of results) { guids[result.guid] = true; } return guids; }, async changeItemID(oldID, newID) { await FormWrapper.replaceGUID(oldID, newID); }, async itemExists(id) { return FormWrapper.hasGUID(id); }, async createRecord(id, collection) { let record = new FormRec(collection, id); let entry = await FormWrapper.getEntry(id); if (entry != null) { record.name = entry.name; record.value = entry.value; } else { record.deleted = true; } return record; }, async create(record) { this._log.trace("Adding form record for " + record.name); let change = { op: "add", guid: record.id, fieldname: record.name, value: record.value, }; await this._processChange(change); }, async remove(record) { this._log.trace("Removing form record: " + record.id); let change = { op: "remove", guid: record.id, }; await this._processChange(change); }, async update() { this._log.trace("Ignoring form record update request!"); }, async wipe() { let change = { op: "remove", }; await FormWrapper._update(change); }, }; Object.setPrototypeOf(FormStore.prototype, Store.prototype); function FormTracker(name, engine) { LegacyTracker.call(this, name, engine); } FormTracker.prototype = { QueryInterface: ChromeUtils.generateQI([ "nsIObserver", "nsISupportsWeakReference", ]), onStart() { Svc.Obs.add("satchel-storage-changed", this.asyncObserver); }, onStop() { Svc.Obs.remove("satchel-storage-changed", this.asyncObserver); }, async observe(subject, topic, data) { if (this.ignoreAll) { return; } switch (topic) { case "satchel-storage-changed": if (data == "formhistory-add" || data == "formhistory-remove") { let guid = subject.QueryInterface(Ci.nsISupportsString).toString(); await this.trackEntry(guid); } break; } }, async trackEntry(guid) { const added = await this.addChangedID(guid); if (added) { this.score += SCORE_INCREMENT_MEDIUM; } }, }; Object.setPrototypeOf(FormTracker.prototype, LegacyTracker.prototype); class FormsProblemData extends CollectionProblemData { getSummary() { // We don't support syncing deleted form data, so "clientMissing" isn't a problem return super.getSummary().filter(entry => entry.name !== "clientMissing"); } } export class FormValidator extends CollectionValidator { constructor() { super("forms", "id", ["name", "value"]); this.ignoresMissingClients = true; } emptyProblemData() { return new FormsProblemData(); } async getClientItems() { return FormWrapper._search(["guid", "fieldname", "value"], {}); } normalizeClientItem(item) { return { id: item.guid, guid: item.guid, name: item.fieldname, fieldname: item.fieldname, value: item.value, original: item, }; } async normalizeServerItem(item) { let res = Object.assign( { guid: item.id, fieldname: item.name, original: item, }, item ); // Missing `name` or `value` causes the getGUID call to throw if (item.name !== undefined && item.value !== undefined) { let guid = await FormWrapper.getGUID(item.name, item.value); if (guid) { res.guid = guid; res.id = guid; res.duped = true; } } return res; } } PK