1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/places/tests/queries/head_queries.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,382 @@ 1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +const Ci = Components.interfaces; 1.11 +const Cc = Components.classes; 1.12 +const Cr = Components.results; 1.13 +const Cu = Components.utils; 1.14 + 1.15 +Cu.import("resource://gre/modules/Services.jsm"); 1.16 + 1.17 +// Import common head. 1.18 +let (commonFile = do_get_file("../head_common.js", false)) { 1.19 + let uri = Services.io.newFileURI(commonFile); 1.20 + Services.scriptloader.loadSubScript(uri.spec, this); 1.21 +} 1.22 + 1.23 +// Put any other stuff relative to this test folder below. 1.24 + 1.25 + 1.26 +// Some Useful Date constants - PRTime uses microseconds, so convert 1.27 +const DAY_MICROSEC = 86400000000; 1.28 +const today = Date.now() * 1000; 1.29 +const yesterday = today - DAY_MICROSEC; 1.30 +const lastweek = today - (DAY_MICROSEC * 7); 1.31 +const daybefore = today - (DAY_MICROSEC * 2); 1.32 +const tomorrow = today + DAY_MICROSEC; 1.33 +const old = today - (DAY_MICROSEC * 3); 1.34 +const futureday = today + (DAY_MICROSEC * 3); 1.35 +const olderthansixmonths = today - (DAY_MICROSEC * 31 * 7); 1.36 + 1.37 + 1.38 +/** 1.39 + * Generalized function to pull in an array of objects of data and push it into 1.40 + * the database. It does NOT do any checking to see that the input is 1.41 + * appropriate. This function is an asynchronous task, it can be called using 1.42 + * "Task.spawn" or using the "yield" function inside another task. 1.43 + */ 1.44 +function task_populateDB(aArray) 1.45 +{ 1.46 + // Iterate over aArray and execute all the instructions that can be done with 1.47 + // asynchronous APIs, excluding those that will be done in batch mode later. 1.48 + for ([, data] in Iterator(aArray)) { 1.49 + try { 1.50 + // make the data object into a query data object in order to create proper 1.51 + // default values for anything left unspecified 1.52 + var qdata = new queryData(data); 1.53 + if (qdata.isVisit) { 1.54 + // Then we should add a visit for this node 1.55 + yield promiseAddVisits({ 1.56 + uri: uri(qdata.uri), 1.57 + transition: qdata.transType, 1.58 + visitDate: qdata.lastVisit, 1.59 + referrer: qdata.referrer ? uri(qdata.referrer) : null, 1.60 + title: qdata.title 1.61 + }); 1.62 + if (qdata.visitCount && !qdata.isDetails) { 1.63 + // Set a fake visit_count, this is not a real count but can be used 1.64 + // to test sorting by visit_count. 1.65 + let stmt = DBConn().createAsyncStatement( 1.66 + "UPDATE moz_places SET visit_count = :vc WHERE url = :url"); 1.67 + stmt.params.vc = qdata.visitCount; 1.68 + stmt.params.url = qdata.uri; 1.69 + try { 1.70 + stmt.executeAsync(); 1.71 + } 1.72 + catch (ex) { 1.73 + print("Error while setting visit_count."); 1.74 + } 1.75 + finally { 1.76 + stmt.finalize(); 1.77 + } 1.78 + } 1.79 + } 1.80 + 1.81 + if (qdata.isRedirect) { 1.82 + // This must be async to properly enqueue after the updateFrecency call 1.83 + // done by the visit addition. 1.84 + let stmt = DBConn().createAsyncStatement( 1.85 + "UPDATE moz_places SET hidden = 1 WHERE url = :url"); 1.86 + stmt.params.url = qdata.uri; 1.87 + try { 1.88 + stmt.executeAsync(); 1.89 + } 1.90 + catch (ex) { 1.91 + print("Error while setting hidden."); 1.92 + } 1.93 + finally { 1.94 + stmt.finalize(); 1.95 + } 1.96 + } 1.97 + 1.98 + if (qdata.isDetails) { 1.99 + // Then we add extraneous page details for testing 1.100 + yield promiseAddVisits({ 1.101 + uri: uri(qdata.uri), 1.102 + visitDate: qdata.lastVisit, 1.103 + title: qdata.title 1.104 + }); 1.105 + } 1.106 + } catch (ex) { 1.107 + // use the data object here in case instantiation of qdata failed 1.108 + LOG("Problem with this URI: " + data.uri); 1.109 + do_throw("Error creating database: " + ex + "\n"); 1.110 + } 1.111 + } 1.112 + 1.113 + // Now execute the part of the instructions made with synchronous APIs. 1.114 + PlacesUtils.history.runInBatchMode({ 1.115 + runBatched: function (aUserData) 1.116 + { 1.117 + aArray.forEach(function (data) 1.118 + { 1.119 + try { 1.120 + // make the data object into a query data object in order to create proper 1.121 + // default values for anything left unspecified 1.122 + var qdata = new queryData(data); 1.123 + 1.124 + if (qdata.markPageAsTyped) { 1.125 + PlacesUtils.history.markPageAsTyped(uri(qdata.uri)); 1.126 + } 1.127 + 1.128 + if (qdata.isPageAnnotation) { 1.129 + if (qdata.removeAnnotation) 1.130 + PlacesUtils.annotations.removePageAnnotation(uri(qdata.uri), 1.131 + qdata.annoName); 1.132 + else { 1.133 + PlacesUtils.annotations.setPageAnnotation(uri(qdata.uri), 1.134 + qdata.annoName, 1.135 + qdata.annoVal, 1.136 + qdata.annoFlags, 1.137 + qdata.annoExpiration); 1.138 + } 1.139 + } 1.140 + 1.141 + if (qdata.isItemAnnotation) { 1.142 + if (qdata.removeAnnotation) 1.143 + PlacesUtils.annotations.removeItemAnnotation(qdata.itemId, 1.144 + qdata.annoName); 1.145 + else { 1.146 + PlacesUtils.annotations.setItemAnnotation(qdata.itemId, 1.147 + qdata.annoName, 1.148 + qdata.annoVal, 1.149 + qdata.annoFlags, 1.150 + qdata.annoExpiration); 1.151 + } 1.152 + } 1.153 + 1.154 + if (qdata.isFolder) { 1.155 + let folderId = PlacesUtils.bookmarks.createFolder(qdata.parentFolder, 1.156 + qdata.title, 1.157 + qdata.index); 1.158 + if (qdata.readOnly) 1.159 + PlacesUtils.bookmarks.setFolderReadonly(folderId, true); 1.160 + } 1.161 + 1.162 + if (qdata.isLivemark) { 1.163 + PlacesUtils.livemarks.addLivemark({ title: qdata.title 1.164 + , parentId: qdata.parentFolder 1.165 + , index: qdata.index 1.166 + , feedURI: uri(qdata.feedURI) 1.167 + , siteURI: uri(qdata.uri) 1.168 + }).then(null, do_throw); 1.169 + } 1.170 + 1.171 + if (qdata.isBookmark) { 1.172 + let itemId = PlacesUtils.bookmarks.insertBookmark(qdata.parentFolder, 1.173 + uri(qdata.uri), 1.174 + qdata.index, 1.175 + qdata.title); 1.176 + if (qdata.keyword) 1.177 + PlacesUtils.bookmarks.setKeywordForBookmark(itemId, qdata.keyword); 1.178 + if (qdata.dateAdded) 1.179 + PlacesUtils.bookmarks.setItemDateAdded(itemId, qdata.dateAdded); 1.180 + if (qdata.lastModified) 1.181 + PlacesUtils.bookmarks.setItemLastModified(itemId, qdata.lastModified); 1.182 + } 1.183 + 1.184 + if (qdata.isTag) { 1.185 + PlacesUtils.tagging.tagURI(uri(qdata.uri), qdata.tagArray); 1.186 + } 1.187 + 1.188 + if (qdata.isDynContainer) { 1.189 + PlacesUtils.bookmarks.createDynamicContainer(qdata.parentFolder, 1.190 + qdata.title, 1.191 + qdata.contractId, 1.192 + qdata.index); 1.193 + } 1.194 + 1.195 + if (qdata.isSeparator) 1.196 + PlacesUtils.bookmarks.insertSeparator(qdata.parentFolder, qdata.index); 1.197 + } catch (ex) { 1.198 + // use the data object here in case instantiation of qdata failed 1.199 + LOG("Problem with this URI: " + data.uri); 1.200 + do_throw("Error creating database: " + ex + "\n"); 1.201 + } 1.202 + }); // End of function and array 1.203 + } 1.204 + }, null); 1.205 +} 1.206 + 1.207 + 1.208 +/** 1.209 + * The Query Data Object - this object encapsulates data for our queries and is 1.210 + * used to parameterize our calls to the Places APIs to put data into the 1.211 + * database. It also has some interesting meta functions to determine which APIs 1.212 + * should be called, and to determine if this object should show up in the 1.213 + * resulting query. 1.214 + * Its parameter is an object specifying which attributes you want to set. 1.215 + * For ex: 1.216 + * var myobj = new queryData({isVisit: true, uri:"http://mozilla.com", title="foo"}); 1.217 + * Note that it doesn't do any input checking on that object. 1.218 + */ 1.219 +function queryData(obj) { 1.220 + this.isVisit = obj.isVisit ? obj.isVisit : false; 1.221 + this.isBookmark = obj.isBookmark ? obj.isBookmark: false; 1.222 + this.uri = obj.uri ? obj.uri : ""; 1.223 + this.lastVisit = obj.lastVisit ? obj.lastVisit : today; 1.224 + this.referrer = obj.referrer ? obj.referrer : null; 1.225 + this.transType = obj.transType ? obj.transType : Ci.nsINavHistoryService.TRANSITION_TYPED; 1.226 + this.isRedirect = obj.isRedirect ? obj.isRedirect : false; 1.227 + this.isDetails = obj.isDetails ? obj.isDetails : false; 1.228 + this.title = obj.title ? obj.title : ""; 1.229 + this.markPageAsTyped = obj.markPageAsTyped ? obj.markPageAsTyped : false; 1.230 + this.isPageAnnotation = obj.isPageAnnotation ? obj.isPageAnnotation : false; 1.231 + this.removeAnnotation= obj.removeAnnotation ? true : false; 1.232 + this.annoName = obj.annoName ? obj.annoName : ""; 1.233 + this.annoVal = obj.annoVal ? obj.annoVal : ""; 1.234 + this.annoFlags = obj.annoFlags ? obj.annoFlags : 0; 1.235 + this.annoExpiration = obj.annoExpiration ? obj.annoExpiration : 0; 1.236 + this.isItemAnnotation = obj.isItemAnnotation ? obj.isItemAnnotation : false; 1.237 + this.itemId = obj.itemId ? obj.itemId : 0; 1.238 + this.annoMimeType = obj.annoMimeType ? obj.annoMimeType : ""; 1.239 + this.isTag = obj.isTag ? obj.isTag : false; 1.240 + this.tagArray = obj.tagArray ? obj.tagArray : null; 1.241 + this.isLivemark = obj.isLivemark ? obj.isLivemark : false; 1.242 + this.parentFolder = obj.parentFolder ? obj.parentFolder 1.243 + : PlacesUtils.placesRootId; 1.244 + this.feedURI = obj.feedURI ? obj.feedURI : ""; 1.245 + this.index = obj.index ? obj.index : PlacesUtils.bookmarks.DEFAULT_INDEX; 1.246 + this.isFolder = obj.isFolder ? obj.isFolder : false; 1.247 + this.contractId = obj.contractId ? obj.contractId : ""; 1.248 + this.lastModified = obj.lastModified ? obj.lastModified : today; 1.249 + this.dateAdded = obj.dateAdded ? obj.dateAdded : today; 1.250 + this.keyword = obj.keyword ? obj.keyword : ""; 1.251 + this.visitCount = obj.visitCount ? obj.visitCount : 0; 1.252 + this.readOnly = obj.readOnly ? obj.readOnly : false; 1.253 + this.isSeparator = obj.hasOwnProperty("isSeparator") && obj.isSeparator; 1.254 + 1.255 + // And now, the attribute for whether or not this object should appear in the 1.256 + // resulting query 1.257 + this.isInQuery = obj.isInQuery ? obj.isInQuery : false; 1.258 +} 1.259 + 1.260 +// All attributes are set in the constructor above 1.261 +queryData.prototype = { } 1.262 + 1.263 + 1.264 +/** 1.265 + * Helper function to compare an array of query objects with a result set. 1.266 + * It assumes the array of query objects contains the SAME SORT as the result 1.267 + * set. It checks the the uri, title, time, and bookmarkIndex properties of 1.268 + * the results, where appropriate. 1.269 + */ 1.270 +function compareArrayToResult(aArray, aRoot) { 1.271 + LOG("Comparing Array to Results"); 1.272 + 1.273 + var wasOpen = aRoot.containerOpen; 1.274 + if (!wasOpen) 1.275 + aRoot.containerOpen = true; 1.276 + 1.277 + // check expected number of results against actual 1.278 + var expectedResultCount = aArray.filter(function(aEl) { return aEl.isInQuery; }).length; 1.279 + if (expectedResultCount != aRoot.childCount) { 1.280 + // Debugging code for failures. 1.281 + dump_table("moz_places"); 1.282 + dump_table("moz_historyvisits"); 1.283 + LOG("Found children:"); 1.284 + for (let i = 0; i < aRoot.childCount; i++) { 1.285 + LOG(aRoot.getChild(i).uri); 1.286 + } 1.287 + LOG("Expected:"); 1.288 + for (let i = 0; i < aArray.length; i++) { 1.289 + if (aArray[i].isInQuery) 1.290 + LOG(aArray[i].uri); 1.291 + } 1.292 + } 1.293 + do_check_eq(expectedResultCount, aRoot.childCount); 1.294 + 1.295 + var inQueryIndex = 0; 1.296 + for (var i = 0; i < aArray.length; i++) { 1.297 + if (aArray[i].isInQuery) { 1.298 + var child = aRoot.getChild(inQueryIndex); 1.299 + //LOG("testing testData[" + i + "] vs result[" + inQueryIndex + "]"); 1.300 + if (!aArray[i].isFolder && !aArray[i].isSeparator) { 1.301 + LOG("testing testData[" + aArray[i].uri + "] vs result[" + child.uri + "]"); 1.302 + if (aArray[i].uri != child.uri) { 1.303 + dump_table("moz_places"); 1.304 + do_throw("Expected " + aArray[i].uri + " found " + child.uri); 1.305 + } 1.306 + } 1.307 + if (!aArray[i].isSeparator && aArray[i].title != child.title) 1.308 + do_throw("Expected " + aArray[i].title + " found " + child.title); 1.309 + if (aArray[i].hasOwnProperty("lastVisit") && 1.310 + aArray[i].lastVisit != child.time) 1.311 + do_throw("Expected " + aArray[i].lastVisit + " found " + child.time); 1.312 + if (aArray[i].hasOwnProperty("index") && 1.313 + aArray[i].index != PlacesUtils.bookmarks.DEFAULT_INDEX && 1.314 + aArray[i].index != child.bookmarkIndex) 1.315 + do_throw("Expected " + aArray[i].index + " found " + child.bookmarkIndex); 1.316 + 1.317 + inQueryIndex++; 1.318 + } 1.319 + } 1.320 + 1.321 + if (!wasOpen) 1.322 + aRoot.containerOpen = false; 1.323 + LOG("Comparing Array to Results passes"); 1.324 +} 1.325 + 1.326 + 1.327 +/** 1.328 + * Helper function to check to see if one object either is or is not in the 1.329 + * result set. It can accept either a queryData object or an array of queryData 1.330 + * objects. If it gets an array, it only compares the first object in the array 1.331 + * to see if it is in the result set. 1.332 + * Returns: True if item is in query set, and false if item is not in query set 1.333 + * If input is an array, returns True if FIRST object in array is in 1.334 + * query set. To compare entire array, use the function above. 1.335 + */ 1.336 +function isInResult(aQueryData, aRoot) { 1.337 + var rv = false; 1.338 + var uri; 1.339 + var wasOpen = aRoot.containerOpen; 1.340 + if (!wasOpen) 1.341 + aRoot.containerOpen = true; 1.342 + 1.343 + // If we have an array, pluck out the first item. If an object, pluc out the 1.344 + // URI, we just compare URI's here. 1.345 + if ("uri" in aQueryData) { 1.346 + uri = aQueryData.uri; 1.347 + } else { 1.348 + uri = aQueryData[0].uri; 1.349 + } 1.350 + 1.351 + for (var i=0; i < aRoot.childCount; i++) { 1.352 + if (uri == aRoot.getChild(i).uri) { 1.353 + rv = true; 1.354 + break; 1.355 + } 1.356 + } 1.357 + if (!wasOpen) 1.358 + aRoot.containerOpen = false; 1.359 + return rv; 1.360 +} 1.361 + 1.362 + 1.363 +/** 1.364 + * A nice helper function for debugging things. It LOGs the contents of a 1.365 + * result set. 1.366 + */ 1.367 +function displayResultSet(aRoot) { 1.368 + 1.369 + var wasOpen = aRoot.containerOpen; 1.370 + if (!wasOpen) 1.371 + aRoot.containerOpen = true; 1.372 + 1.373 + if (!aRoot.hasChildren) { 1.374 + // Something wrong? Empty result set? 1.375 + LOG("Result Set Empty"); 1.376 + return; 1.377 + } 1.378 + 1.379 + for (var i=0; i < aRoot.childCount; ++i) { 1.380 + LOG("Result Set URI: " + aRoot.getChild(i).uri + " Title: " + 1.381 + aRoot.getChild(i).title + " Visit Time: " + aRoot.getChild(i).time); 1.382 + } 1.383 + if (!wasOpen) 1.384 + aRoot.containerOpen = false; 1.385 +}