forms.js 6.2 KB

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