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 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const Ci = Components.interfaces;
8 const Cc = Components.classes;
9 const Cr = Components.results;
10 const Cu = Components.utils;
12 Cu.import("resource://gre/modules/Services.jsm");
14 // Import common head.
15 let (commonFile = do_get_file("../head_common.js", false)) {
16 let uri = Services.io.newFileURI(commonFile);
17 Services.scriptloader.loadSubScript(uri.spec, this);
18 }
20 // Put any other stuff relative to this test folder below.
23 // Some Useful Date constants - PRTime uses microseconds, so convert
24 const DAY_MICROSEC = 86400000000;
25 const today = Date.now() * 1000;
26 const yesterday = today - DAY_MICROSEC;
27 const lastweek = today - (DAY_MICROSEC * 7);
28 const daybefore = today - (DAY_MICROSEC * 2);
29 const tomorrow = today + DAY_MICROSEC;
30 const old = today - (DAY_MICROSEC * 3);
31 const futureday = today + (DAY_MICROSEC * 3);
32 const olderthansixmonths = today - (DAY_MICROSEC * 31 * 7);
35 /**
36 * Generalized function to pull in an array of objects of data and push it into
37 * the database. It does NOT do any checking to see that the input is
38 * appropriate. This function is an asynchronous task, it can be called using
39 * "Task.spawn" or using the "yield" function inside another task.
40 */
41 function task_populateDB(aArray)
42 {
43 // Iterate over aArray and execute all the instructions that can be done with
44 // asynchronous APIs, excluding those that will be done in batch mode later.
45 for ([, data] in Iterator(aArray)) {
46 try {
47 // make the data object into a query data object in order to create proper
48 // default values for anything left unspecified
49 var qdata = new queryData(data);
50 if (qdata.isVisit) {
51 // Then we should add a visit for this node
52 yield promiseAddVisits({
53 uri: uri(qdata.uri),
54 transition: qdata.transType,
55 visitDate: qdata.lastVisit,
56 referrer: qdata.referrer ? uri(qdata.referrer) : null,
57 title: qdata.title
58 });
59 if (qdata.visitCount && !qdata.isDetails) {
60 // Set a fake visit_count, this is not a real count but can be used
61 // to test sorting by visit_count.
62 let stmt = DBConn().createAsyncStatement(
63 "UPDATE moz_places SET visit_count = :vc WHERE url = :url");
64 stmt.params.vc = qdata.visitCount;
65 stmt.params.url = qdata.uri;
66 try {
67 stmt.executeAsync();
68 }
69 catch (ex) {
70 print("Error while setting visit_count.");
71 }
72 finally {
73 stmt.finalize();
74 }
75 }
76 }
78 if (qdata.isRedirect) {
79 // This must be async to properly enqueue after the updateFrecency call
80 // done by the visit addition.
81 let stmt = DBConn().createAsyncStatement(
82 "UPDATE moz_places SET hidden = 1 WHERE url = :url");
83 stmt.params.url = qdata.uri;
84 try {
85 stmt.executeAsync();
86 }
87 catch (ex) {
88 print("Error while setting hidden.");
89 }
90 finally {
91 stmt.finalize();
92 }
93 }
95 if (qdata.isDetails) {
96 // Then we add extraneous page details for testing
97 yield promiseAddVisits({
98 uri: uri(qdata.uri),
99 visitDate: qdata.lastVisit,
100 title: qdata.title
101 });
102 }
103 } catch (ex) {
104 // use the data object here in case instantiation of qdata failed
105 LOG("Problem with this URI: " + data.uri);
106 do_throw("Error creating database: " + ex + "\n");
107 }
108 }
110 // Now execute the part of the instructions made with synchronous APIs.
111 PlacesUtils.history.runInBatchMode({
112 runBatched: function (aUserData)
113 {
114 aArray.forEach(function (data)
115 {
116 try {
117 // make the data object into a query data object in order to create proper
118 // default values for anything left unspecified
119 var qdata = new queryData(data);
121 if (qdata.markPageAsTyped) {
122 PlacesUtils.history.markPageAsTyped(uri(qdata.uri));
123 }
125 if (qdata.isPageAnnotation) {
126 if (qdata.removeAnnotation)
127 PlacesUtils.annotations.removePageAnnotation(uri(qdata.uri),
128 qdata.annoName);
129 else {
130 PlacesUtils.annotations.setPageAnnotation(uri(qdata.uri),
131 qdata.annoName,
132 qdata.annoVal,
133 qdata.annoFlags,
134 qdata.annoExpiration);
135 }
136 }
138 if (qdata.isItemAnnotation) {
139 if (qdata.removeAnnotation)
140 PlacesUtils.annotations.removeItemAnnotation(qdata.itemId,
141 qdata.annoName);
142 else {
143 PlacesUtils.annotations.setItemAnnotation(qdata.itemId,
144 qdata.annoName,
145 qdata.annoVal,
146 qdata.annoFlags,
147 qdata.annoExpiration);
148 }
149 }
151 if (qdata.isFolder) {
152 let folderId = PlacesUtils.bookmarks.createFolder(qdata.parentFolder,
153 qdata.title,
154 qdata.index);
155 if (qdata.readOnly)
156 PlacesUtils.bookmarks.setFolderReadonly(folderId, true);
157 }
159 if (qdata.isLivemark) {
160 PlacesUtils.livemarks.addLivemark({ title: qdata.title
161 , parentId: qdata.parentFolder
162 , index: qdata.index
163 , feedURI: uri(qdata.feedURI)
164 , siteURI: uri(qdata.uri)
165 }).then(null, do_throw);
166 }
168 if (qdata.isBookmark) {
169 let itemId = PlacesUtils.bookmarks.insertBookmark(qdata.parentFolder,
170 uri(qdata.uri),
171 qdata.index,
172 qdata.title);
173 if (qdata.keyword)
174 PlacesUtils.bookmarks.setKeywordForBookmark(itemId, qdata.keyword);
175 if (qdata.dateAdded)
176 PlacesUtils.bookmarks.setItemDateAdded(itemId, qdata.dateAdded);
177 if (qdata.lastModified)
178 PlacesUtils.bookmarks.setItemLastModified(itemId, qdata.lastModified);
179 }
181 if (qdata.isTag) {
182 PlacesUtils.tagging.tagURI(uri(qdata.uri), qdata.tagArray);
183 }
185 if (qdata.isDynContainer) {
186 PlacesUtils.bookmarks.createDynamicContainer(qdata.parentFolder,
187 qdata.title,
188 qdata.contractId,
189 qdata.index);
190 }
192 if (qdata.isSeparator)
193 PlacesUtils.bookmarks.insertSeparator(qdata.parentFolder, qdata.index);
194 } catch (ex) {
195 // use the data object here in case instantiation of qdata failed
196 LOG("Problem with this URI: " + data.uri);
197 do_throw("Error creating database: " + ex + "\n");
198 }
199 }); // End of function and array
200 }
201 }, null);
202 }
205 /**
206 * The Query Data Object - this object encapsulates data for our queries and is
207 * used to parameterize our calls to the Places APIs to put data into the
208 * database. It also has some interesting meta functions to determine which APIs
209 * should be called, and to determine if this object should show up in the
210 * resulting query.
211 * Its parameter is an object specifying which attributes you want to set.
212 * For ex:
213 * var myobj = new queryData({isVisit: true, uri:"http://mozilla.com", title="foo"});
214 * Note that it doesn't do any input checking on that object.
215 */
216 function queryData(obj) {
217 this.isVisit = obj.isVisit ? obj.isVisit : false;
218 this.isBookmark = obj.isBookmark ? obj.isBookmark: false;
219 this.uri = obj.uri ? obj.uri : "";
220 this.lastVisit = obj.lastVisit ? obj.lastVisit : today;
221 this.referrer = obj.referrer ? obj.referrer : null;
222 this.transType = obj.transType ? obj.transType : Ci.nsINavHistoryService.TRANSITION_TYPED;
223 this.isRedirect = obj.isRedirect ? obj.isRedirect : false;
224 this.isDetails = obj.isDetails ? obj.isDetails : false;
225 this.title = obj.title ? obj.title : "";
226 this.markPageAsTyped = obj.markPageAsTyped ? obj.markPageAsTyped : false;
227 this.isPageAnnotation = obj.isPageAnnotation ? obj.isPageAnnotation : false;
228 this.removeAnnotation= obj.removeAnnotation ? true : false;
229 this.annoName = obj.annoName ? obj.annoName : "";
230 this.annoVal = obj.annoVal ? obj.annoVal : "";
231 this.annoFlags = obj.annoFlags ? obj.annoFlags : 0;
232 this.annoExpiration = obj.annoExpiration ? obj.annoExpiration : 0;
233 this.isItemAnnotation = obj.isItemAnnotation ? obj.isItemAnnotation : false;
234 this.itemId = obj.itemId ? obj.itemId : 0;
235 this.annoMimeType = obj.annoMimeType ? obj.annoMimeType : "";
236 this.isTag = obj.isTag ? obj.isTag : false;
237 this.tagArray = obj.tagArray ? obj.tagArray : null;
238 this.isLivemark = obj.isLivemark ? obj.isLivemark : false;
239 this.parentFolder = obj.parentFolder ? obj.parentFolder
240 : PlacesUtils.placesRootId;
241 this.feedURI = obj.feedURI ? obj.feedURI : "";
242 this.index = obj.index ? obj.index : PlacesUtils.bookmarks.DEFAULT_INDEX;
243 this.isFolder = obj.isFolder ? obj.isFolder : false;
244 this.contractId = obj.contractId ? obj.contractId : "";
245 this.lastModified = obj.lastModified ? obj.lastModified : today;
246 this.dateAdded = obj.dateAdded ? obj.dateAdded : today;
247 this.keyword = obj.keyword ? obj.keyword : "";
248 this.visitCount = obj.visitCount ? obj.visitCount : 0;
249 this.readOnly = obj.readOnly ? obj.readOnly : false;
250 this.isSeparator = obj.hasOwnProperty("isSeparator") && obj.isSeparator;
252 // And now, the attribute for whether or not this object should appear in the
253 // resulting query
254 this.isInQuery = obj.isInQuery ? obj.isInQuery : false;
255 }
257 // All attributes are set in the constructor above
258 queryData.prototype = { }
261 /**
262 * Helper function to compare an array of query objects with a result set.
263 * It assumes the array of query objects contains the SAME SORT as the result
264 * set. It checks the the uri, title, time, and bookmarkIndex properties of
265 * the results, where appropriate.
266 */
267 function compareArrayToResult(aArray, aRoot) {
268 LOG("Comparing Array to Results");
270 var wasOpen = aRoot.containerOpen;
271 if (!wasOpen)
272 aRoot.containerOpen = true;
274 // check expected number of results against actual
275 var expectedResultCount = aArray.filter(function(aEl) { return aEl.isInQuery; }).length;
276 if (expectedResultCount != aRoot.childCount) {
277 // Debugging code for failures.
278 dump_table("moz_places");
279 dump_table("moz_historyvisits");
280 LOG("Found children:");
281 for (let i = 0; i < aRoot.childCount; i++) {
282 LOG(aRoot.getChild(i).uri);
283 }
284 LOG("Expected:");
285 for (let i = 0; i < aArray.length; i++) {
286 if (aArray[i].isInQuery)
287 LOG(aArray[i].uri);
288 }
289 }
290 do_check_eq(expectedResultCount, aRoot.childCount);
292 var inQueryIndex = 0;
293 for (var i = 0; i < aArray.length; i++) {
294 if (aArray[i].isInQuery) {
295 var child = aRoot.getChild(inQueryIndex);
296 //LOG("testing testData[" + i + "] vs result[" + inQueryIndex + "]");
297 if (!aArray[i].isFolder && !aArray[i].isSeparator) {
298 LOG("testing testData[" + aArray[i].uri + "] vs result[" + child.uri + "]");
299 if (aArray[i].uri != child.uri) {
300 dump_table("moz_places");
301 do_throw("Expected " + aArray[i].uri + " found " + child.uri);
302 }
303 }
304 if (!aArray[i].isSeparator && aArray[i].title != child.title)
305 do_throw("Expected " + aArray[i].title + " found " + child.title);
306 if (aArray[i].hasOwnProperty("lastVisit") &&
307 aArray[i].lastVisit != child.time)
308 do_throw("Expected " + aArray[i].lastVisit + " found " + child.time);
309 if (aArray[i].hasOwnProperty("index") &&
310 aArray[i].index != PlacesUtils.bookmarks.DEFAULT_INDEX &&
311 aArray[i].index != child.bookmarkIndex)
312 do_throw("Expected " + aArray[i].index + " found " + child.bookmarkIndex);
314 inQueryIndex++;
315 }
316 }
318 if (!wasOpen)
319 aRoot.containerOpen = false;
320 LOG("Comparing Array to Results passes");
321 }
324 /**
325 * Helper function to check to see if one object either is or is not in the
326 * result set. It can accept either a queryData object or an array of queryData
327 * objects. If it gets an array, it only compares the first object in the array
328 * to see if it is in the result set.
329 * Returns: True if item is in query set, and false if item is not in query set
330 * If input is an array, returns True if FIRST object in array is in
331 * query set. To compare entire array, use the function above.
332 */
333 function isInResult(aQueryData, aRoot) {
334 var rv = false;
335 var uri;
336 var wasOpen = aRoot.containerOpen;
337 if (!wasOpen)
338 aRoot.containerOpen = true;
340 // If we have an array, pluck out the first item. If an object, pluc out the
341 // URI, we just compare URI's here.
342 if ("uri" in aQueryData) {
343 uri = aQueryData.uri;
344 } else {
345 uri = aQueryData[0].uri;
346 }
348 for (var i=0; i < aRoot.childCount; i++) {
349 if (uri == aRoot.getChild(i).uri) {
350 rv = true;
351 break;
352 }
353 }
354 if (!wasOpen)
355 aRoot.containerOpen = false;
356 return rv;
357 }
360 /**
361 * A nice helper function for debugging things. It LOGs the contents of a
362 * result set.
363 */
364 function displayResultSet(aRoot) {
366 var wasOpen = aRoot.containerOpen;
367 if (!wasOpen)
368 aRoot.containerOpen = true;
370 if (!aRoot.hasChildren) {
371 // Something wrong? Empty result set?
372 LOG("Result Set Empty");
373 return;
374 }
376 for (var i=0; i < aRoot.childCount; ++i) {
377 LOG("Result Set URI: " + aRoot.getChild(i).uri + " Title: " +
378 aRoot.getChild(i).title + " Visit Time: " + aRoot.getChild(i).time);
379 }
380 if (!wasOpen)
381 aRoot.containerOpen = false;
382 }