Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
5 this.EXPORTED_SYMBOLS = ['FormEngine', 'FormRec'];
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12 Cu.import("resource://services-sync/engines.js");
13 Cu.import("resource://services-sync/record.js");
14 Cu.import("resource://services-common/async.js");
15 Cu.import("resource://services-sync/util.js");
16 Cu.import("resource://services-sync/constants.js");
17 Cu.import("resource://gre/modules/Log.jsm");
19 const FORMS_TTL = 5184000; // 60 days
21 this.FormRec = function FormRec(collection, id) {
22 CryptoWrapper.call(this, collection, id);
23 }
24 FormRec.prototype = {
25 __proto__: CryptoWrapper.prototype,
26 _logName: "Sync.Record.Form",
27 ttl: FORMS_TTL
28 };
30 Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);
33 let FormWrapper = {
34 _log: Log.repository.getLogger("Sync.Engine.Forms"),
36 _getEntryCols: ["fieldname", "value"],
37 _guidCols: ["guid"],
39 // Do a "sync" search by spinning the event loop until it completes.
40 _searchSpinningly: function(terms, searchData) {
41 let results = [];
42 let cb = Async.makeSpinningCallback();
43 let callbacks = {
44 handleResult: function(result) {
45 results.push(result);
46 },
47 handleCompletion: function(reason) {
48 cb(null, results);
49 }
50 };
51 Svc.FormHistory.search(terms, searchData, callbacks);
52 return cb.wait();
53 },
55 _updateSpinningly: function(changes) {
56 if (!Svc.FormHistory.enabled) {
57 return; // update isn't going to do anything.
58 }
59 let cb = Async.makeSpinningCallback();
60 let callbacks = {
61 handleCompletion: function(reason) {
62 cb();
63 }
64 };
65 Svc.FormHistory.update(changes, callbacks);
66 return cb.wait();
67 },
69 getEntry: function (guid) {
70 let results = this._searchSpinningly(this._getEntryCols, {guid: guid});
71 if (!results.length) {
72 return null;
73 }
74 return {name: results[0].fieldname, value: results[0].value};
75 },
77 getGUID: function (name, value) {
78 // Query for the provided entry.
79 let query = { fieldname: name, value: value };
80 let results = this._searchSpinningly(this._guidCols, query);
81 return results.length ? results[0].guid : null;
82 },
84 hasGUID: function (guid) {
85 // We could probably use a count function here, but searchSpinningly exists...
86 return this._searchSpinningly(this._guidCols, {guid: guid}).length != 0;
87 },
89 replaceGUID: function (oldGUID, newGUID) {
90 let changes = {
91 op: "update",
92 guid: oldGUID,
93 newGuid: newGUID,
94 }
95 this._updateSpinningly(changes);
96 }
98 };
100 this.FormEngine = function FormEngine(service) {
101 SyncEngine.call(this, "Forms", service);
102 }
103 FormEngine.prototype = {
104 __proto__: SyncEngine.prototype,
105 _storeObj: FormStore,
106 _trackerObj: FormTracker,
107 _recordObj: FormRec,
108 applyIncomingBatchSize: FORMS_STORE_BATCH_SIZE,
110 get prefName() "history",
112 _findDupe: function _findDupe(item) {
113 return FormWrapper.getGUID(item.name, item.value);
114 }
115 };
117 function FormStore(name, engine) {
118 Store.call(this, name, engine);
119 }
120 FormStore.prototype = {
121 __proto__: Store.prototype,
123 _processChange: function (change) {
124 // If this._changes is defined, then we are applying a batch, so we
125 // can defer it.
126 if (this._changes) {
127 this._changes.push(change);
128 return;
129 }
131 // Otherwise we must handle the change synchronously, right now.
132 FormWrapper._updateSpinningly(change);
133 },
135 applyIncomingBatch: function (records) {
136 // We collect all the changes to be made then apply them all at once.
137 this._changes = [];
138 let failures = Store.prototype.applyIncomingBatch.call(this, records);
139 if (this._changes.length) {
140 FormWrapper._updateSpinningly(this._changes);
141 }
142 delete this._changes;
143 return failures;
144 },
146 getAllIDs: function () {
147 let results = FormWrapper._searchSpinningly(["guid"], [])
148 let guids = {};
149 for (let result of results) {
150 guids[result.guid] = true;
151 }
152 return guids;
153 },
155 changeItemID: function (oldID, newID) {
156 FormWrapper.replaceGUID(oldID, newID);
157 },
159 itemExists: function (id) {
160 return FormWrapper.hasGUID(id);
161 },
163 createRecord: function (id, collection) {
164 let record = new FormRec(collection, id);
165 let entry = FormWrapper.getEntry(id);
166 if (entry != null) {
167 record.name = entry.name;
168 record.value = entry.value;
169 } else {
170 record.deleted = true;
171 }
172 return record;
173 },
175 create: function (record) {
176 this._log.trace("Adding form record for " + record.name);
177 let change = {
178 op: "add",
179 fieldname: record.name,
180 value: record.value
181 };
182 this._processChange(change);
183 },
185 remove: function (record) {
186 this._log.trace("Removing form record: " + record.id);
187 let change = {
188 op: "remove",
189 guid: record.id
190 };
191 this._processChange(change);
192 },
194 update: function (record) {
195 this._log.trace("Ignoring form record update request!");
196 },
198 wipe: function () {
199 let change = {
200 op: "remove"
201 };
202 FormWrapper._updateSpinningly(change);
203 }
204 };
206 function FormTracker(name, engine) {
207 Tracker.call(this, name, engine);
208 }
209 FormTracker.prototype = {
210 __proto__: Tracker.prototype,
212 QueryInterface: XPCOMUtils.generateQI([
213 Ci.nsIObserver,
214 Ci.nsISupportsWeakReference]),
216 startTracking: function() {
217 Svc.Obs.add("satchel-storage-changed", this);
218 },
220 stopTracking: function() {
221 Svc.Obs.remove("satchel-storage-changed", this);
222 },
224 observe: function (subject, topic, data) {
225 Tracker.prototype.observe.call(this, subject, topic, data);
227 switch (topic) {
228 case "satchel-storage-changed":
229 if (data == "formhistory-add" || data == "formhistory-remove") {
230 let guid = subject.QueryInterface(Ci.nsISupportsString).toString();
231 this.trackEntry(guid);
232 }
233 break;
234 }
235 },
237 trackEntry: function (guid) {
238 this.addChangedID(guid);
239 this.score += SCORE_INCREMENT_MEDIUM;
240 },
241 };