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
5/* This is a JavaScript module (JSM) to be imported via
6   Components.utils.import() and acts as a singleton. Only the following
7   listed symbols will exposed on import, and only when and where imported.
8  */
9
10var EXPORTED_SYMBOLS = ["FormData"];
11
12const { Logger } = ChromeUtils.import("resource://tps/logger.jsm");
13
14const { FormHistory } = ChromeUtils.import(
15  "resource://gre/modules/FormHistory.jsm"
16);
17const { Log } = ChromeUtils.import("resource://gre/modules/Log.jsm");
18
19/**
20 * FormDB
21 *
22 * Helper object containing methods to interact with the FormHistory module.
23 */
24var FormDB = {
25  _update(data) {
26    return new Promise((resolve, reject) => {
27      let handlers = {
28        handleError(error) {
29          Logger.logError(
30            "Error occurred updating form history: " + Log.exceptionStr(error)
31          );
32          reject(error);
33        },
34        handleCompletion(reason) {
35          resolve();
36        },
37      };
38      FormHistory.update(data, handlers);
39    });
40  },
41
42  /**
43   * insertValue
44   *
45   * Adds the specified value for the specified fieldname into form history.
46   *
47   * @param fieldname The form fieldname to insert
48   * @param value The form value to insert
49   * @param us The time, in microseconds, to use for the lastUsed
50   *        and firstUsed columns
51   * @return Promise<undefined>
52   */
53  insertValue(fieldname, value, us) {
54    let data = {
55      op: "add",
56      fieldname,
57      value,
58      timesUsed: 1,
59      firstUsed: us,
60      lastUsed: us,
61    };
62    return this._update(data);
63  },
64
65  /**
66   * updateValue
67   *
68   * Updates a row in the moz_formhistory table with a new value.
69   *
70   * @param id The id of the row to update
71   * @param newvalue The new value to set
72   * @return Promise<undefined>
73   */
74  updateValue(id, newvalue) {
75    return this._update({ op: "update", guid: id, value: newvalue });
76  },
77
78  /**
79   * getDataForValue
80   *
81   * Retrieves a set of values for a row in the database that
82   * corresponds to the given fieldname and value.
83   *
84   * @param fieldname The fieldname of the row to query
85   * @param value The value of the row to query
86   * @return Promise<null if no row is found with the specified fieldname and value,
87   *         or an object containing the row's guid, lastUsed, and firstUsed
88   *         values>
89   */
90  getDataForValue(fieldname, value) {
91    return new Promise((resolve, reject) => {
92      let result = null;
93      let handlers = {
94        handleResult(oneResult) {
95          if (result != null) {
96            reject("more than 1 result for this query");
97            return;
98          }
99          result = oneResult;
100        },
101        handleError(error) {
102          Logger.logError(
103            "Error occurred updating form history: " + Log.exceptionStr(error)
104          );
105          reject(error);
106        },
107        handleCompletion(reason) {
108          resolve(result);
109        },
110      };
111      FormHistory.search(
112        ["guid", "lastUsed", "firstUsed"],
113        { fieldname, value },
114        handlers
115      );
116    });
117  },
118
119  /**
120   * remove
121   *
122   * Removes the specified GUID from the database.
123   *
124   * @param guid The guid of the item to delete
125   * @return Promise<>
126   */
127  remove(guid) {
128    return this._update({ op: "remove", guid });
129  },
130};
131
132/**
133 * FormData class constructor
134 *
135 * Initializes instance properties.
136 */
137function FormData(props, msSinceEpoch) {
138  this.fieldname = null;
139  this.value = null;
140  this.date = 0;
141  this.newvalue = null;
142  this.usSinceEpoch = msSinceEpoch * 1000;
143
144  for (var prop in props) {
145    if (prop in this) {
146      this[prop] = props[prop];
147    }
148  }
149}
150
151/**
152 * FormData instance methods
153 */
154FormData.prototype = {
155  /**
156   * hours_to_us
157   *
158   * Converts hours since present to microseconds since epoch.
159   *
160   * @param hours The number of hours since the present time (e.g., 0 is
161   *        'now', and -1 is 1 hour ago)
162   * @return the corresponding number of microseconds since the epoch
163   */
164  hours_to_us(hours) {
165    return this.usSinceEpoch + hours * 60 * 60 * 1000 * 1000;
166  },
167
168  /**
169   * Create
170   *
171   * If this FormData object doesn't exist in the moz_formhistory database,
172   * add it.  Throws on error.
173   *
174   * @return nothing
175   */
176  Create() {
177    Logger.AssertTrue(
178      this.fieldname != null && this.value != null,
179      "Must specify both fieldname and value"
180    );
181
182    return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => {
183      if (!formdata) {
184        // this item doesn't exist yet in the db, so we need to insert it
185        return FormDB.insertValue(
186          this.fieldname,
187          this.value,
188          this.hours_to_us(this.date)
189        );
190      }
191      /* Right now, we ignore this case.  If bug 552531 is ever fixed,
192         we might need to add code here to update the firstUsed or
193         lastUsed fields, as appropriate.
194       */
195      return null;
196    });
197  },
198
199  /**
200   * Find
201   *
202   * Attempts to locate an entry in the moz_formhistory database that
203   * matches the fieldname and value for this FormData object.
204   *
205   * @return true if this entry exists in the database, otherwise false
206   */
207  Find() {
208    return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => {
209      let status = formdata != null;
210      if (status) {
211        /*
212        //form history dates currently not synced!  bug 552531
213        let us = this.hours_to_us(this.date);
214        status = Logger.AssertTrue(
215          us >= formdata.firstUsed && us <= formdata.lastUsed,
216          "No match for with that date value");
217
218        if (status)
219        */
220        this.id = formdata.guid;
221      }
222      return status;
223    });
224  },
225
226  /**
227   * Remove
228   *
229   * Removes the row represented by this FormData instance from the
230   * moz_formhistory database.
231   *
232   * @return nothing
233   */
234  async Remove() {
235    const formdata = await FormDB.getDataForValue(this.fieldname, this.value);
236    if (!formdata) {
237      return;
238    }
239    await FormDB.remove(formdata.guid);
240  },
241};
242