summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/queries/head_queries.js
blob: 3e686e4f1626b034de9c9fd6826314f6488b67c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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 common head.
{
  /* import-globals-from ../head_common.js */
  let commonFile = do_get_file("../head_common.js", false);
  let uri = Services.io.newFileURI(commonFile);
  Services.scriptloader.loadSubScript(uri.spec, this);
}

// Put any other stuff relative to this test folder below.

// Some Useful Date constants - PRTime uses microseconds, so convert
const DAY_MICROSEC = 86400000000;
const today = PlacesUtils.toPRTime(Date.now());
const yesterday = today - DAY_MICROSEC;
const lastweek = today - DAY_MICROSEC * 7;
const daybefore = today - DAY_MICROSEC * 2;
const old = today - DAY_MICROSEC * 3;
const futureday = today + DAY_MICROSEC * 3;
const olderthansixmonths = today - DAY_MICROSEC * 31 * 7;

/**
 * Generalized function to pull in an array of objects of data and push it into
 * the database.  It does NOT do any checking to see that the input is
 * appropriate.  This function is an asynchronous task, it can be called using
 * "Task.spawn" or using the "yield" function inside another task.
 */
async function task_populateDB(aArray) {
  // Iterate over aArray and execute all instructions.
  for (let arrayItem of aArray) {
    try {
      // make the data object into a query data object in order to create proper
      // default values for anything left unspecified
      var qdata = new queryData(arrayItem);
      if (qdata.isVisit) {
        // Then we should add a visit for this node
        await PlacesTestUtils.addVisits({
          uri: uri(qdata.uri),
          transition: qdata.transType,
          visitDate: qdata.lastVisit,
          referrer: qdata.referrer ? uri(qdata.referrer) : null,
          title: qdata.title,
        });
        if (qdata.visitCount && !qdata.isDetails) {
          // Set a fake visit_count, this is not a real count but can be used
          // to test sorting by visit_count.
          let stmt = DBConn().createAsyncStatement(
            "UPDATE moz_places SET visit_count = :vc WHERE url_hash = hash(:url) AND url = :url"
          );
          stmt.params.vc = qdata.visitCount;
          stmt.params.url = qdata.uri;
          try {
            stmt.executeAsync();
          } catch (ex) {
            print("Error while setting visit_count.");
          } finally {
            stmt.finalize();
          }
        }
      }

      if (qdata.isRedirect) {
        // This must be async to properly enqueue after the updateFrecency call
        // done by the visit addition.
        let stmt = DBConn().createAsyncStatement(
          "UPDATE moz_places SET hidden = 1 WHERE url_hash = hash(:url) AND url = :url"
        );
        stmt.params.url = qdata.uri;
        try {
          stmt.executeAsync();
        } catch (ex) {
          print("Error while setting hidden.");
        } finally {
          stmt.finalize();
        }
      }

      if (qdata.isDetails) {
        // Then we add extraneous page details for testing
        await PlacesTestUtils.addVisits({
          uri: uri(qdata.uri),
          visitDate: qdata.lastVisit,
          title: qdata.title,
        });
      }

      if (qdata.markPageAsTyped) {
        PlacesUtils.history.markPageAsTyped(uri(qdata.uri));
      }

      if (qdata.isPageAnnotation) {
        await PlacesUtils.history.update({
          url: qdata.uri,
          annotations: new Map([
            [qdata.annoName, qdata.removeAnnotation ? null : qdata.annoVal],
          ]),
        });
      }

      if (qdata.isFolder) {
        await PlacesUtils.bookmarks.insert({
          parentGuid: qdata.parentGuid,
          type: PlacesUtils.bookmarks.TYPE_FOLDER,
          title: qdata.title,
          index: qdata.index,
        });
      }

      if (qdata.isBookmark) {
        let data = {
          parentGuid: qdata.parentGuid,
          index: qdata.index,
          title: qdata.title,
          url: qdata.uri,
        };

        if (qdata.dateAdded) {
          data.dateAdded = new Date(qdata.dateAdded / 1000);
        }

        if (qdata.lastModified) {
          data.lastModified = new Date(qdata.lastModified / 1000);
        }

        await PlacesUtils.bookmarks.insert(data);

        if (qdata.keyword) {
          await PlacesUtils.keywords.insert({
            url: qdata.uri,
            keyword: qdata.keyword,
          });
        }
      }

      if (qdata.isTag) {
        PlacesUtils.tagging.tagURI(uri(qdata.uri), qdata.tagArray);
      }

      if (qdata.isSeparator) {
        await PlacesUtils.bookmarks.insert({
          parentGuid: qdata.parentGuid,
          type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
          index: qdata.index,
        });
      }
    } catch (ex) {
      // use the arrayItem object here in case instantiation of qdata failed
      info("Problem with this URI: " + arrayItem.uri);
      do_throw("Error creating database: " + ex + "\n");
    }
  }
}

/**
 * The Query Data Object - this object encapsulates data for our queries and is
 * used to parameterize our calls to the Places APIs to put data into the
 * database. It also has some interesting meta functions to determine which APIs
 * should be called, and to determine if this object should show up in the
 * resulting query.
 * Its parameter is an object specifying which attributes you want to set.
 * For ex:
 * var myobj = new queryData({isVisit: true, uri:"http://mozilla.com", title="foo"});
 * Note that it doesn't do any input checking on that object.
 */
function queryData(obj) {
  this.isVisit = obj.isVisit ? obj.isVisit : false;
  this.isBookmark = obj.isBookmark ? obj.isBookmark : false;
  this.uri = obj.uri ? obj.uri : "";
  this.lastVisit = obj.lastVisit ? obj.lastVisit : today;
  this.referrer = obj.referrer ? obj.referrer : null;
  this.transType = obj.transType
    ? obj.transType
    : Ci.nsINavHistoryService.TRANSITION_TYPED;
  this.isRedirect = obj.isRedirect ? obj.isRedirect : false;
  this.isDetails = obj.isDetails ? obj.isDetails : false;
  this.title = obj.title ? obj.title : "";
  this.markPageAsTyped = obj.markPageAsTyped ? obj.markPageAsTyped : false;
  this.isPageAnnotation = obj.isPageAnnotation ? obj.isPageAnnotation : false;
  this.removeAnnotation = !!obj.removeAnnotation;
  this.annoName = obj.annoName ? obj.annoName : "";
  this.annoVal = obj.annoVal ? obj.annoVal : "";
  this.itemId = obj.itemId ? obj.itemId : 0;
  this.annoMimeType = obj.annoMimeType ? obj.annoMimeType : "";
  this.isTag = obj.isTag ? obj.isTag : false;
  this.tagArray = obj.tagArray ? obj.tagArray : null;
  this.parentGuid = obj.parentGuid || PlacesUtils.bookmarks.unfiledGuid;
  this.feedURI = obj.feedURI ? obj.feedURI : "";
  this.index = obj.index ? obj.index : PlacesUtils.bookmarks.DEFAULT_INDEX;
  this.isFolder = obj.isFolder ? obj.isFolder : false;
  this.contractId = obj.contractId ? obj.contractId : "";
  this.lastModified = obj.lastModified ? obj.lastModified : null;
  this.dateAdded = obj.dateAdded ? obj.dateAdded : null;
  this.keyword = obj.keyword ? obj.keyword : "";
  this.visitCount = obj.visitCount ? obj.visitCount : 0;
  this.isSeparator = obj.hasOwnProperty("isSeparator") && obj.isSeparator;

  // And now, the attribute for whether or not this object should appear in the
  // resulting query
  this.isInQuery = obj.isInQuery ? obj.isInQuery : false;
}

// All attributes are set in the constructor above
queryData.prototype = {};

/**
 * Helper function to compare an array of query objects with a result set.
 * It assumes the array of query objects contains the SAME SORT as the result
 * set.  It checks the the uri, title, time, and bookmarkIndex properties of
 * the results, where appropriate.
 */
function compareArrayToResult(aArray, aRoot) {
  info("Comparing Array to Results");

  var wasOpen = aRoot.containerOpen;
  if (!wasOpen) {
    aRoot.containerOpen = true;
  }

  // check expected number of results against actual
  var expectedResultCount = aArray.filter(function(aEl) {
    return aEl.isInQuery;
  }).length;
  if (expectedResultCount != aRoot.childCount) {
    // Debugging code for failures.
    dump_table("moz_places");
    dump_table("moz_historyvisits");
    info("Found children:");
    for (let i = 0; i < aRoot.childCount; i++) {
      info(aRoot.getChild(i).uri);
    }
    info("Expected:");
    for (let i = 0; i < aArray.length; i++) {
      if (aArray[i].isInQuery) {
        info(aArray[i].uri);
      }
    }
  }
  Assert.equal(expectedResultCount, aRoot.childCount);

  var inQueryIndex = 0;
  for (var i = 0; i < aArray.length; i++) {
    if (aArray[i].isInQuery) {
      var child = aRoot.getChild(inQueryIndex);
      // do_print("testing testData[" + i + "] vs result[" + inQueryIndex + "]");
      if (!aArray[i].isFolder && !aArray[i].isSeparator) {
        info(
          "testing testData[" + aArray[i].uri + "] vs result[" + child.uri + "]"
        );
        if (aArray[i].uri != child.uri) {
          dump_table("moz_places");
          do_throw("Expected " + aArray[i].uri + " found " + child.uri);
        }
      }
      if (!aArray[i].isSeparator && aArray[i].title != child.title) {
        do_throw("Expected " + aArray[i].title + " found " + child.title);
      }
      if (
        aArray[i].hasOwnProperty("lastVisit") &&
        aArray[i].lastVisit != child.time
      ) {
        do_throw("Expected " + aArray[i].lastVisit + " found " + child.time);
      }
      if (
        aArray[i].hasOwnProperty("index") &&
        aArray[i].index != PlacesUtils.bookmarks.DEFAULT_INDEX &&
        aArray[i].index != child.bookmarkIndex
      ) {
        do_throw(
          "Expected " + aArray[i].index + " found " + child.bookmarkIndex
        );
      }

      inQueryIndex++;
    }
  }

  if (!wasOpen) {
    aRoot.containerOpen = false;
  }
  info("Comparing Array to Results passes");
}

/**
 * Helper function to check to see if one object either is or is not in the
 * result set.  It can accept either a queryData object or an array of queryData
 * objects.  If it gets an array, it only compares the first object in the array
 * to see if it is in the result set.
 * Returns: True if item is in query set, and false if item is not in query set
 *          If input is an array, returns True if FIRST object in array is in
 *          query set.  To compare entire array, use the function above.
 */
function isInResult(aQueryData, aRoot) {
  var rv = false;
  var uri;
  var wasOpen = aRoot.containerOpen;
  if (!wasOpen) {
    aRoot.containerOpen = true;
  }

  // If we have an array, pluck out the first item. If an object, pluc out the
  // URI, we just compare URI's here.
  if ("uri" in aQueryData) {
    uri = aQueryData.uri;
  } else {
    uri = aQueryData[0].uri;
  }

  for (var i = 0; i < aRoot.childCount; i++) {
    if (uri == aRoot.getChild(i).uri) {
      rv = true;
      break;
    }
  }
  if (!wasOpen) {
    aRoot.containerOpen = false;
  }
  return rv;
}

/**
 * A nice helper function for debugging things. It prints the contents of a
 * result set.
 */
function displayResultSet(aRoot) {
  var wasOpen = aRoot.containerOpen;
  if (!wasOpen) {
    aRoot.containerOpen = true;
  }

  if (!aRoot.hasChildren) {
    // Something wrong? Empty result set?
    info("Result Set Empty");
    return;
  }

  for (var i = 0; i < aRoot.childCount; ++i) {
    info(
      "Result Set URI: " +
        aRoot.getChild(i).uri +
        "   Title: " +
        aRoot.getChild(i).title +
        "   Visit Time: " +
        aRoot.getChild(i).time
    );
  }
  if (!wasOpen) {
    aRoot.containerOpen = false;
  }
}