|
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/. */ |
|
6 |
|
7 const Ci = Components.interfaces; |
|
8 const Cc = Components.classes; |
|
9 const Cr = Components.results; |
|
10 const Cu = Components.utils; |
|
11 |
|
12 Cu.import("resource://gre/modules/Services.jsm"); |
|
13 |
|
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 } |
|
19 |
|
20 // Put any other stuff relative to this test folder below. |
|
21 |
|
22 |
|
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); |
|
33 |
|
34 |
|
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 } |
|
77 |
|
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 } |
|
94 |
|
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 } |
|
109 |
|
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); |
|
120 |
|
121 if (qdata.markPageAsTyped) { |
|
122 PlacesUtils.history.markPageAsTyped(uri(qdata.uri)); |
|
123 } |
|
124 |
|
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 } |
|
137 |
|
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 } |
|
150 |
|
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 } |
|
158 |
|
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 } |
|
167 |
|
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 } |
|
180 |
|
181 if (qdata.isTag) { |
|
182 PlacesUtils.tagging.tagURI(uri(qdata.uri), qdata.tagArray); |
|
183 } |
|
184 |
|
185 if (qdata.isDynContainer) { |
|
186 PlacesUtils.bookmarks.createDynamicContainer(qdata.parentFolder, |
|
187 qdata.title, |
|
188 qdata.contractId, |
|
189 qdata.index); |
|
190 } |
|
191 |
|
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 } |
|
203 |
|
204 |
|
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; |
|
251 |
|
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 } |
|
256 |
|
257 // All attributes are set in the constructor above |
|
258 queryData.prototype = { } |
|
259 |
|
260 |
|
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"); |
|
269 |
|
270 var wasOpen = aRoot.containerOpen; |
|
271 if (!wasOpen) |
|
272 aRoot.containerOpen = true; |
|
273 |
|
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); |
|
291 |
|
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); |
|
313 |
|
314 inQueryIndex++; |
|
315 } |
|
316 } |
|
317 |
|
318 if (!wasOpen) |
|
319 aRoot.containerOpen = false; |
|
320 LOG("Comparing Array to Results passes"); |
|
321 } |
|
322 |
|
323 |
|
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; |
|
339 |
|
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 } |
|
347 |
|
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 } |
|
358 |
|
359 |
|
360 /** |
|
361 * A nice helper function for debugging things. It LOGs the contents of a |
|
362 * result set. |
|
363 */ |
|
364 function displayResultSet(aRoot) { |
|
365 |
|
366 var wasOpen = aRoot.containerOpen; |
|
367 if (!wasOpen) |
|
368 aRoot.containerOpen = true; |
|
369 |
|
370 if (!aRoot.hasChildren) { |
|
371 // Something wrong? Empty result set? |
|
372 LOG("Result Set Empty"); |
|
373 return; |
|
374 } |
|
375 |
|
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 } |