Bug 564270 - Can not search Places by transition type

This commit is contained in:
Mehdi Mulani 2010-06-23 10:01:27 -07:00
parent 96299b3d2f
commit 4256887d4f
6 changed files with 351 additions and 4 deletions

View File

@ -912,7 +912,7 @@ interface nsINavHistoryObserver : nsISupports
* can be non-negligible.
*/
[scriptable, uuid(6f5668f0-da8e-4069-a0de-6680e5cd8570)]
[scriptable, uuid(dc87ae79-22f1-4dcf-975b-852b01d210cb)]
interface nsINavHistoryQuery : nsISupports
{
/**
@ -967,6 +967,27 @@ interface nsINavHistoryQuery : nsISupports
attribute long minVisits;
attribute long maxVisits;
/**
* When the set of transitions is nonempty, results are limited to pages which
* have at least one visit for each of the transition types.
* @note: For searching on more than one transition this can be very slow.
*
* Limit results to the specified list of transition types.
*/
void setTransitions([const,array, size_is(count)] in unsigned long transitions,
in unsigned long count);
/**
* Get the transitions set for this query.
*/
void getTransitions([optional] out unsigned long count,
[retval,array,size_is(count)] out unsigned long transitions);
/**
* Get the count of the set query transitions.
*/
readonly attribute unsigned long transitionCount;
/**
* When set, returns only bookmarked items, when unset, returns anything. Setting this
* is equivalent to listing all bookmark folders in the 'folders' parameter.

View File

@ -2305,6 +2305,7 @@ nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQuerie
PRBool nonTimeBasedItems = PR_FALSE;
PRBool domainBasedItems = PR_FALSE;
PRBool queryContainsTransitions = PR_FALSE;
for (i = 0; i < aQueries.Count(); i ++) {
nsNavHistoryQuery* query = aQueries[i];
@ -2314,6 +2315,10 @@ nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQuerie
query->Tags().Length() > 0) {
return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
}
if (query->Transitions().Length() > 0)
queryContainsTransitions = PR_TRUE;
// Note: we don't currently have any complex non-bookmarked items, but these
// are expected to be added. Put detection of these items here.
if (! query->SearchTerms().IsEmpty() ||
@ -2329,6 +2334,9 @@ nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQuerie
nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
if (queryContainsTransitions)
return QUERYUPDATE_COMPLEX;
// Whenever there is a maximum number of results,
// and we are not a bookmark query we must requery. This
// is because we can't generally know if any given addition/change causes
@ -2859,9 +2867,7 @@ nsNavHistory::AddVisit(nsIURI* aURI, PRTime aTime, nsIURI* aReferringURI,
// GetQueryResults to maintain consistency.
// FIXME bug 325241: make a way to observe hidden URLs
PRUint32 added = 0;
if (!hidden && aTransitionType != TRANSITION_EMBED &&
aTransitionType != TRANSITION_FRAMED_LINK &&
aTransitionType != TRANSITION_DOWNLOAD) {
if (!hidden) {
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavHistoryObserver,
OnVisit(aURI, *aVisitID, aTime, aSessionID,
@ -3064,6 +3070,9 @@ PRBool IsOptimizableHistoryQuery(const nsCOMArray<nsNavHistoryQuery>& aQueries,
if (aQuery->Tags().Length() > 0)
return PR_FALSE;
if (aQuery->Transitions().Length() > 0)
return PR_FALSE;
return PR_TRUE;
}
@ -6340,6 +6349,26 @@ nsNavHistory::QueryToSelectClause(nsNavHistoryQuery* aQuery, // const
clause.Str(")");
}
// transitions
const nsTArray<PRUint32>& transitions = aQuery->Transitions();
// Optimize single transition query, since this is the most common use case.
if (transitions.Length() == 1) {
clause.Condition("v.visit_type =").Param(":transition0_");
}
else if (transitions.Length() > 1) {
for (PRUint32 i = 0; i < transitions.Length(); ++i) {
nsPrintfCString param(":transition%d_", i);
clause.Str("EXISTS (SELECT 1 FROM moz_historyvisits "
"WHERE place_id = h.id AND visit_type = "
).Param(param.get()).Str(" UNION ALL "
"SELECT 1 FROM moz_historyvisits_temp "
"WHERE place_id = h.id AND visit_type = "
).Param(param.get()).Str(" LIMIT 1)");
if (i < transitions.Length() - 1)
clause.Str("AND");
}
}
// parent parameter is used in tag contents queries.
// Only one folder should be defined for them.
if (aOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS &&
@ -6485,6 +6514,16 @@ nsNavHistory::BindQueryClauseParameters(mozIStorageStatement* statement,
}
}
// transitions
const nsTArray<PRUint32>& transitions = aQuery->Transitions();
if (transitions.Length() > 0) {
for (PRUint32 i = 0; i < transitions.Length(); ++i) {
nsPrintfCString paramName("transition%d_", i);
rv = statement->BindInt64ByName(paramName + qIndex, transitions[i]);
NS_ENSURE_SUCCESS(rv, rv);
}
}
// parent parameter
if (aOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS &&
aQuery->Folders().Length() == 1) {

View File

@ -175,6 +175,7 @@ static void SetOptionsKeyUint32(const nsCString& aValue,
#define QUERYKEY_TAG "tag"
#define QUERYKEY_NOTTAGS "!tags"
#define QUERYKEY_ASYNC_ENABLED "asyncEnabled"
#define QUERYKEY_TRANSITION "transition"
inline void AppendAmpersandIfNonempty(nsACString& aString)
{
@ -524,6 +525,14 @@ nsNavHistory::QueriesToQueryString(nsINavHistoryQuery **aQueries,
NS_LITERAL_CSTRING(QUERYKEY_NOTTAGS),
query,
&nsINavHistoryQuery::GetTagsAreNot);
// transitions
const nsTArray<PRUint32>& transitions = query->Transitions();
for (PRUint32 i = 0; i < transitions.Length(); ++i) {
AppendAmpersandIfNonempty(queryString);
queryString += NS_LITERAL_CSTRING(QUERYKEY_TRANSITION "=");
AppendInt64(queryString, transitions[i]);
}
}
// sorting
@ -687,6 +696,7 @@ nsNavHistory::TokensToQueries(const nsTArray<QueryKeyValuePair>& aTokens,
nsTArray<PRInt64> folders;
nsTArray<nsString> tags;
nsTArray<PRUint32> transitions;
for (PRUint32 i = 0; i < aTokens.Length(); i ++) {
const QueryKeyValuePair& kvp = aTokens[i];
@ -800,6 +810,18 @@ nsNavHistory::TokensToQueries(const nsTArray<QueryKeyValuePair>& aTokens,
} else if (kvp.key.EqualsLiteral(QUERYKEY_NOTTAGS)) {
SetQueryKeyBool(kvp.value, query, &nsINavHistoryQuery::SetTagsAreNot);
// transition
} else if (kvp.key.EqualsLiteral(QUERYKEY_TRANSITION)) {
PRUint32 transition = kvp.value.ToInteger(&rv);
if (NS_SUCCEEDED(rv)) {
if (!transitions.Contains(transition))
NS_ENSURE_TRUE(transitions.AppendElement(transition),
NS_ERROR_OUT_OF_MEMORY);
}
else {
NS_WARNING("Invalid Int32 transition value.");
}
// new query component
} else if (kvp.key.EqualsLiteral(QUERYKEY_SEPARATOR)) {
@ -814,6 +836,12 @@ nsNavHistory::TokensToQueries(const nsTArray<QueryKeyValuePair>& aTokens,
tags.Clear();
}
if (transitions.Length() > 0) {
rv = query->SetTransitions(transitions);
NS_ENSURE_SUCCESS(rv, rv);
transitions.Clear();
}
query = new nsNavHistoryQuery();
if (! query)
return NS_ERROR_OUT_OF_MEMORY;
@ -896,6 +924,11 @@ nsNavHistory::TokensToQueries(const nsTArray<QueryKeyValuePair>& aTokens,
NS_ENSURE_SUCCESS(rv, rv);
}
if (transitions.Length() > 0) {
rv = query->SetTransitions(transitions);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
@ -1336,6 +1369,40 @@ NS_IMETHODIMP nsNavHistoryQuery::SetFolders(const PRInt64 *aFolders,
return NS_OK;
}
NS_IMETHODIMP nsNavHistoryQuery::GetTransitions(PRUint32* aCount,
PRUint32** aTransitions)
{
PRUint32 count = mTransitions.Length();
PRUint32* transitions = nsnull;
if (count > 0) {
transitions = reinterpret_cast<PRUint32*>
(NS_Alloc(count * sizeof(PRUint32)));
NS_ENSURE_TRUE(transitions, NS_ERROR_OUT_OF_MEMORY);
for (PRUint32 i = 0; i < count; ++i) {
transitions[i] = mTransitions[i];
}
}
*aCount = count;
*aTransitions = transitions;
return NS_OK;
}
NS_IMETHODIMP nsNavHistoryQuery::GetTransitionCount(PRUint32* aCount)
{
*aCount = mTransitions.Length();
return NS_OK;
}
NS_IMETHODIMP nsNavHistoryQuery::SetTransitions(const PRUint32* aTransitions,
PRUint32 aCount)
{
if (!mTransitions.ReplaceElementsAt(0, mTransitions.Length(), aTransitions,
aCount))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
NS_IMETHODIMP nsNavHistoryQuery::Clone(nsINavHistoryQuery** _retval)
{
*_retval = nsnull;

View File

@ -88,6 +88,16 @@ public:
}
PRBool TagsAreNot() { return mTagsAreNot; }
const nsTArray<PRUint32>& Transitions() const { return mTransitions; }
nsresult SetTransitions(const nsTArray<PRUint32>& aTransitions)
{
if (!mTransitions.ReplaceElementsAt(0, mTransitions.Length(),
aTransitions))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
private:
~nsNavHistoryQuery() {}
@ -110,6 +120,7 @@ protected:
nsTArray<PRInt64> mFolders;
nsTArray<nsString> mTags;
PRBool mTagsAreNot;
nsTArray<PRUint32> mTransitions;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHistoryQuery, NS_NAVHISTORYQUERY_IID)

View File

@ -349,6 +349,38 @@ const querySwitches = [
}
]
},
// transitions
{
desc: "tests nsINavHistoryQuery.getTransitions",
matches: function (aQuery1, aQuery2) {
var q1Trans = aQuery1.getTransitions();
var q2Trans = aQuery2.getTransitions();
if (q1Trans.length !== q2Trans.length)
return false;
for (let i = 0; i < q1Trans.length; i++) {
if (q2Trans.indexOf(q1Trans[i]) < 0)
return false;
}
for (let i = 0; i < q2Trans.length; i++) {
if (q1Trans.indexOf(q2Trans[i]) < 0)
return false;
}
return true;
},
runs: [
function (aQuery, aQueryOptions) {
aQuery.setTransitions([], 0);
},
function (aQuery, aQueryOptions) {
aQuery.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD],
1);
},
function (aQuery, aQueryOptions) {
aQuery.setTransitions([Ci.nsINavHistoryService.TRANSITION_TYPED,
Ci.nsINavHistoryService.TRANSITION_BOOKMARK], 2);
}
]
},
];
// nsINavHistoryQueryOptions switches

View File

@ -0,0 +1,177 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
* ***** END LICENSE BLOCK ***** */
var beginTime = Date.now();
var testData = [
{
isVisit: true,
title: "page 0",
uri: "http://mozilla.com/",
transType: Ci.nsINavHistoryService.TRANSITION_TYPED
},
{
isVisit: true,
title: "page 1",
uri: "http://google.com/",
transType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD
},
{
isVisit: true,
title: "page 2",
uri: "http://microsoft.com/",
transType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD
},
{
isVisit: true,
title: "page 3",
uri: "http://en.wikipedia.org/",
transType: Ci.nsINavHistoryService.TRANSITION_BOOKMARK
},
{
isVisit: true,
title: "page 4",
uri: "http://fr.wikipedia.org/",
transType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD
},
{
isVisit: true,
title: "page 5",
uri: "http://apple.com/",
transType: Ci.nsINavHistoryService.TRANSITION_TYPED
},
{
isVisit: true,
title: "page 6",
uri: "http://campus-bike-store.com/",
transType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD
},
{
isVisit: true,
title: "page 7",
uri: "http://uwaterloo.ca/",
transType: Ci.nsINavHistoryService.TRANSITION_TYPED
},
{
isVisit: true,
title: "page 8",
uri: "http://pugcleaner.com/",
transType: Ci.nsINavHistoryService.TRANSITION_BOOKMARK
},
{
isVisit: true,
title: "page 9",
uri: "http://de.wikipedia.org/",
transType: Ci.nsINavHistoryService.TRANSITION_TYPED
},
{
isVisit: true,
title: "arewefastyet",
uri: "http://arewefastyet.com/",
transType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD
},
{
isVisit: true,
title: "arewefastyet",
uri: "http://arewefastyet.com/",
transType: Ci.nsINavHistoryService.TRANSITION_BOOKMARK
}];
// sets of indices of testData array by transition type
var testDataTyped = [0, 5, 7, 9];
var testDataDownload = [1, 2, 4, 6, 10];
var testDataBookmark = [3, 8, 11];
/**
* run_test is where the magic happens. This is automatically run by the test
* harness. It is where you do the work of creating the query, running it, and
* playing with the result set.
*/
function run_test() {
let timeNow = Date.now();
for each (let item in testData) {
PlacesUtils.history.addVisit(uri(item.uri), timeNow++ * 1000, null,
item.transType, false, 0);
}
for (let i in testData) {
testData[i].title = null;
}
dump_table("moz_places");
dump_table("moz_places_temp");
dump_table("moz_historyvisits");
dump_table("moz_historyvisits_temp");
var numSortFunc = function (a,b) { return (a - b); };
var arrs = testDataTyped.concat(testDataDownload).concat(testDataBookmark)
.sort(numSortFunc);
// Four tests which compare the result of a query to an expected set.
var data = arrs.filter(function (index) {
return (testData[index].uri.match(/arewefastyet\.com/) &&
testData[index].transType ==
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
});
compareQueryToTestData("place:domain=arewefastyet.com&transition=" +
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
data.slice());
compareQueryToTestData("place:transition=" +
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
testDataDownload.slice());
compareQueryToTestData("place:transition=" +
Ci.nsINavHistoryService.TRANSITION_TYPED,
testDataTyped.slice());
compareQueryToTestData("place:transition=" +
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
"&transition=" +
Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
data);
// Tests the live update property of transitions.
var query = {};
var options = {};
PlacesUtils.history.
queryStringToQueries("place:transition=" +
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
query, {}, options);
query = (query.value)[0];
options = PlacesUtils.history.getNewQueryOptions();
var result = PlacesUtils.history.executeQuery(query, options);
var root = result.root;
root.containerOpen = true;
do_check_eq(testDataDownload.length, root.childCount);
PlacesUtils.history
.addVisit(PlacesUtils._uri("http://getfirefox.com"),
Date.now() * 1000, null,
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD, false, 0);
do_check_eq(testDataDownload.length + 1, root.childCount);
root.containerOpen = false;
PlacesUtils.bhistory.removeAllPages();
}
/*
* Takes a query and a set of indices. The indices correspond to elements
* of testData that are the result of the query.
*/
function compareQueryToTestData(queryStr, data) {
var query = {};
var options = {};
PlacesUtils.history.queryStringToQueries(queryStr, query, {}, options);
query = query.value[0];
options = options.value;
var result = PlacesUtils.history.executeQuery(query, options);
var root = result.root;
for (var i = 0; i < data.length; i++) {
data[i] = testData[data[i]];
data[i].isInQuery = true;
}
compareArrayToResult(data, root);
}