/* -*- 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. await PlacesTestUtils.updateDatabaseValues( "moz_places", { visit_count: qdata.visitCount }, { url: qdata.uri } ); } } if (qdata.isRedirect) { // This must be async to properly enqueue after the updateFrecency call // done by the visit addition. await PlacesTestUtils.updateDatabaseValues( "moz_places", { hidden: 1 }, { url: qdata.uri } ); } 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 {nsINavHistoryResultNode}: Either the node, if found, or null. * If input is an array, returns a result only for the first node. * To compare entire array, use the function above. */ function nodeInResult(aQueryData, aRoot) { var rv = null; 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++) { let node = aRoot.getChild(i); if (uri == node.uri) { rv = node; 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; } }