зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1083469 - Allow to use old keywords APIs along with Bookmarks.jsm r=mano
--HG-- extra : rebase_source : 606724d24ed36d9de8045f526d1bb452bf5d0530
This commit is contained in:
Родитель
fab9295e2e
Коммит
8d53fcb53e
|
@ -834,6 +834,17 @@ this.PlacesUtils = {
|
|||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the href and the post data for a given keyword, if any.
|
||||
*
|
||||
* @param keyword
|
||||
* The string keyword to look for.
|
||||
* @return {Promise}
|
||||
* @resolves to a { href, postData } object. Both properties evaluate to null
|
||||
* if no keyword is found.
|
||||
*/
|
||||
promiseHrefAndPostDataForKeyword(keyword) KeywordsCache.promiseEntry(keyword),
|
||||
|
||||
/**
|
||||
* Get the URI (and any associated POST data) for a given keyword.
|
||||
* @param aKeyword string keyword
|
||||
|
@ -1965,6 +1976,114 @@ let GuidHelper = {
|
|||
}
|
||||
};
|
||||
|
||||
// Cache of bookmarks keywords, used to quickly resolve keyword => URL requests.
|
||||
let KeywordsCache = {
|
||||
/**
|
||||
* Initializes the cache.
|
||||
* Every method should check _initialized and, if false, yield _initialize().
|
||||
*/
|
||||
_initialized: false,
|
||||
_initialize: Task.async(function* () {
|
||||
// First populate the cache...
|
||||
yield this._reloadCache();
|
||||
|
||||
// ...then observe changes to keep the cache up-to-date.
|
||||
PlacesUtils.bookmarks.addObserver(this, false);
|
||||
PlacesUtils.registerShutdownFunction(() => {
|
||||
PlacesUtils.bookmarks.removeObserver(this);
|
||||
});
|
||||
|
||||
this._initialized = true;
|
||||
}),
|
||||
|
||||
// nsINavBookmarkObserver
|
||||
// Manually updating the cache would be tricky because some notifications
|
||||
// don't report the original bookmark url and we also keep urls sorted by
|
||||
// last modified. Since changing a keyword-ed bookmark is a rare event,
|
||||
// it's easier to reload the cache.
|
||||
onItemChanged(itemId, property, isAnno, val, lastModified, type,
|
||||
parentId, guid, parentGuid) {
|
||||
if (property == "keyword" || property == this.POST_DATA_ANNO ||
|
||||
this._keywordedGuids.has(guid)) {
|
||||
// Since this cache is used in hot paths, it should be readily available
|
||||
// as fast as possible.
|
||||
this._reloadCache().catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
onItemRemoved(itemId, parentId, index, type, uri, guid, parentGuid) {
|
||||
if (this._keywordedGuids.has(guid)) {
|
||||
// Since this cache is used in hot paths, it should be readily available
|
||||
// as fast as possible.
|
||||
this._reloadCache().catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([ Ci.nsINavBookmarkObserver ]),
|
||||
__noSuchMethod__() {}, // Catch all remaining onItem* methods.
|
||||
|
||||
// Maps an { href, postData } object to each keyword.
|
||||
// Even if a keyword may be associated to multiple URLs, only the last
|
||||
// modified bookmark href is retained here.
|
||||
_urlDataForKeyword: null,
|
||||
// Tracks GUIDs having a keyword.
|
||||
_keywordedGuids: null,
|
||||
|
||||
/**
|
||||
* Reloads the cache.
|
||||
*/
|
||||
_reloadPromise: null,
|
||||
_reloadCache() {
|
||||
return this._reloadPromise = Task.spawn(function* () {
|
||||
let db = yield PlacesUtils.promiseDBConnection();
|
||||
let rows = yield db.execute(
|
||||
`/* do not warn (bug no) - there is no index on keyword_id */
|
||||
SELECT b.id, b.guid, h.url, k.keyword FROM moz_bookmarks b
|
||||
JOIN moz_places h ON h.id = b.fk
|
||||
JOIN moz_keywords k ON k.id = b.keyword_id
|
||||
ORDER BY b.lastModified DESC
|
||||
`);
|
||||
|
||||
this._urlDataForKeyword = new Map();
|
||||
this._keywordedGuids = new Set();
|
||||
|
||||
for (let row of rows) {
|
||||
let guid = row.getResultByName("guid");
|
||||
this._keywordedGuids.add(guid);
|
||||
|
||||
let keyword = row.getResultByName("keyword");
|
||||
// Only keep the most recent href.
|
||||
let urlData = this._urlDataForKeyword.get(keyword);
|
||||
if (urlData)
|
||||
continue;
|
||||
|
||||
let id = row.getResultByName("id");
|
||||
let href = row.getResultByName("url");
|
||||
let postData = PlacesUtils.getPostDataForBookmark(id);
|
||||
this._urlDataForKeyword.set(keyword, { href, postData });
|
||||
}
|
||||
}.bind(this)).then(() => {
|
||||
this._reloadPromise = null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches a { href, postData } entry for the given keyword.
|
||||
*
|
||||
* @param keyword
|
||||
* The keyword to look for.
|
||||
* @return {promise}
|
||||
* @resolves when the fetching is complete.
|
||||
*/
|
||||
promiseEntry: Task.async(function* (keyword) {
|
||||
// We could yield regardless and do the checks internally, but that would
|
||||
// waste at least a couple ticks and this can be used on hot paths.
|
||||
if (!this._initialized)
|
||||
yield this._initialize();
|
||||
if (this._reloadPromise)
|
||||
yield this._reloadPromise;
|
||||
return this._urlDataForKeyword.get(keyword) || { href: null, postData: null };
|
||||
}),
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Transactions handlers.
|
||||
|
||||
|
|
|
@ -768,7 +768,7 @@ Search.prototype = {
|
|||
let hasFirstResult = false;
|
||||
|
||||
if (this._searchTokens.length > 0 &&
|
||||
PlacesUtils.bookmarks.getURIForKeyword(this._searchTokens[0])) {
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword(this._searchTokens[0])).href) {
|
||||
// This may be a keyword of a bookmark.
|
||||
queries.unshift(this._keywordQuery);
|
||||
hasFirstResult = true;
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
#define BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH 32
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
// These columns sit to the right of the kGetInfoIndex_* columns.
|
||||
|
@ -40,25 +38,6 @@ PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
|
|||
|
||||
namespace {
|
||||
|
||||
struct keywordSearchData
|
||||
{
|
||||
int64_t itemId;
|
||||
nsString keyword;
|
||||
};
|
||||
|
||||
PLDHashOperator
|
||||
SearchBookmarkForKeyword(nsTrimInt64HashKey::KeyType aKey,
|
||||
const nsString aValue,
|
||||
void* aUserArg)
|
||||
{
|
||||
keywordSearchData* data = reinterpret_cast<keywordSearchData*>(aUserArg);
|
||||
if (data->keyword.Equals(aValue)) {
|
||||
data->itemId = aKey;
|
||||
return PL_DHASH_STOP;
|
||||
}
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
template<typename Method, typename DataType>
|
||||
class AsyncGetBookmarksForURI : public AsyncStatementCallback
|
||||
{
|
||||
|
@ -143,8 +122,6 @@ nsNavBookmarks::nsNavBookmarks()
|
|||
, mCanNotify(false)
|
||||
, mCacheObservers("bookmark-observers")
|
||||
, mBatching(false)
|
||||
, mBookmarkToKeywordHash(BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH)
|
||||
, mBookmarkToKeywordHashInitialized(false)
|
||||
{
|
||||
NS_ASSERTION(!gBookmarksService,
|
||||
"Attempting to create two instances of the service!");
|
||||
|
@ -646,7 +623,7 @@ nsNavBookmarks::RemoveItem(int64_t aItemId)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
rv = UpdateKeywordsHashForRemovedBookmark(aItemId);
|
||||
rv = removeOrphanKeywords();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// A broken url should not interrupt the removal process.
|
||||
|
@ -1119,7 +1096,7 @@ nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
rv = UpdateKeywordsHashForRemovedBookmark(child.id);
|
||||
rv = removeOrphanKeywords();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
|
@ -2255,39 +2232,23 @@ nsNavBookmarks::SetItemIndex(int64_t aItemId, int32_t aNewIndex)
|
|||
|
||||
|
||||
nsresult
|
||||
nsNavBookmarks::UpdateKeywordsHashForRemovedBookmark(int64_t aItemId)
|
||||
nsNavBookmarks::removeOrphanKeywords()
|
||||
{
|
||||
nsAutoString keyword;
|
||||
if (NS_SUCCEEDED(GetKeywordForBookmark(aItemId, keyword)) &&
|
||||
!keyword.IsEmpty()) {
|
||||
nsresult rv = EnsureKeywordsHash();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mBookmarkToKeywordHash.Remove(aItemId);
|
||||
// If the keyword is unused, remove it from the database.
|
||||
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
|
||||
"DELETE FROM moz_keywords "
|
||||
"WHERE NOT EXISTS ( "
|
||||
"SELECT id "
|
||||
"FROM moz_bookmarks "
|
||||
"WHERE keyword_id = moz_keywords.id "
|
||||
")"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
|
||||
// If the keyword is unused, remove it from the database.
|
||||
keywordSearchData searchData;
|
||||
searchData.keyword.Assign(keyword);
|
||||
searchData.itemId = -1;
|
||||
mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData);
|
||||
if (searchData.itemId == -1) {
|
||||
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
|
||||
"DELETE FROM moz_keywords "
|
||||
"WHERE keyword = :keyword "
|
||||
"AND NOT EXISTS ( "
|
||||
"SELECT id "
|
||||
"FROM moz_bookmarks "
|
||||
"WHERE keyword_id = moz_keywords.id "
|
||||
")"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
|
||||
nsresult rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
|
||||
rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2303,9 +2264,6 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
|
|||
nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = EnsureKeywordsHash();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Shortcuts are always lowercased internally.
|
||||
nsAutoString keyword(aUserCasedKeyword);
|
||||
ToLowerCase(keyword);
|
||||
|
@ -2331,8 +2289,6 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
|
|||
mozStorageStatementScoper updateBookmarkScoper(updateBookmarkStmt);
|
||||
|
||||
if (keyword.IsEmpty()) {
|
||||
// Remove keyword association from the hash.
|
||||
mBookmarkToKeywordHash.Remove(bookmark.id);
|
||||
rv = updateBookmarkStmt->BindNullByName(NS_LITERAL_CSTRING("keyword"));
|
||||
}
|
||||
else {
|
||||
|
@ -2350,10 +2306,6 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
|
|||
rv = newKeywordStmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Add new keyword association to the hash, removing the old one if needed.
|
||||
if (!oldKeyword.IsEmpty())
|
||||
mBookmarkToKeywordHash.Remove(bookmark.id);
|
||||
mBookmarkToKeywordHash.Put(bookmark.id, keyword);
|
||||
rv = updateBookmarkStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -2411,12 +2363,12 @@ nsNavBookmarks::GetKeywordForURI(nsIURI* aURI, nsAString& aKeyword)
|
|||
rv = stmt->ExecuteStep(&hasMore);
|
||||
if (NS_FAILED(rv) || !hasMore) {
|
||||
aKeyword.SetIsVoid(true);
|
||||
return NS_OK; // not found: return void keyword string
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// found, get the keyword
|
||||
rv = stmt->GetString(0, aKeyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2427,16 +2379,28 @@ nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
|
|||
NS_ENSURE_ARG_MIN(aBookmarkId, 1);
|
||||
aKeyword.Truncate(0);
|
||||
|
||||
nsresult rv = EnsureKeywordsHash();
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"/* do not warn (bug no) - there is no index on keyword_id) */ "
|
||||
"SELECT k.keyword "
|
||||
"FROM moz_bookmarks b "
|
||||
"JOIN moz_keywords k ON k.id = b.keyword_id "
|
||||
"WHERE b.id = :id "
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
|
||||
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aBookmarkId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoString keyword;
|
||||
if (!mBookmarkToKeywordHash.Get(aBookmarkId, &keyword)) {
|
||||
bool hasMore = false;
|
||||
rv = stmt->ExecuteStep(&hasMore);
|
||||
if (NS_FAILED(rv) || !hasMore) {
|
||||
aKeyword.SetIsVoid(true);
|
||||
return NS_OK;
|
||||
}
|
||||
else {
|
||||
aKeyword.Assign(keyword);
|
||||
}
|
||||
|
||||
rv = stmt->GetString(0, aKeyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -2454,53 +2418,33 @@ nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
|
|||
nsAutoString keyword(aUserCasedKeyword);
|
||||
ToLowerCase(keyword);
|
||||
|
||||
nsresult rv = EnsureKeywordsHash();
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"/* do not warn (bug no) - there is no index on keyword_id) */ "
|
||||
"SELECT url FROM moz_keywords k "
|
||||
"JOIN moz_bookmarks b ON b.keyword_id = k.id "
|
||||
"JOIN moz_places h ON b.fk = h.id "
|
||||
"WHERE k.keyword = :keyword "
|
||||
"ORDER BY b.dateAdded DESC"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
|
||||
nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
keywordSearchData searchData;
|
||||
searchData.keyword.Assign(keyword);
|
||||
searchData.itemId = -1;
|
||||
mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData);
|
||||
|
||||
if (searchData.itemId == -1) {
|
||||
// Not found.
|
||||
bool hasMore = false;
|
||||
rv = stmt->ExecuteStep(&hasMore);
|
||||
if (NS_FAILED(rv) || !hasMore) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
rv = GetBookmarkURI(searchData.itemId, aURI);
|
||||
nsCString url;
|
||||
rv = stmt->GetUTF8String(0, url);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
nsNavBookmarks::EnsureKeywordsHash() {
|
||||
if (mBookmarkToKeywordHashInitialized) {
|
||||
return NS_OK;
|
||||
}
|
||||
mBookmarkToKeywordHashInitialized = true;
|
||||
|
||||
nsCOMPtr<mozIStorageStatement> stmt;
|
||||
nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT b.id, k.keyword "
|
||||
"FROM moz_bookmarks b "
|
||||
"JOIN moz_keywords k ON k.id = b.keyword_id "
|
||||
), getter_AddRefs(stmt));
|
||||
rv = NS_NewURI(aURI, url);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool hasMore;
|
||||
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
|
||||
int64_t itemId;
|
||||
rv = stmt->GetInt64(0, &itemId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsAutoString keyword;
|
||||
rv = stmt->GetString(1, keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mBookmarkToKeywordHash.Put(itemId, keyword);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -422,20 +422,9 @@ private:
|
|||
bool mBatching;
|
||||
|
||||
/**
|
||||
* Always call EnsureKeywordsHash() and check it for errors before actually
|
||||
* using the hash. Internal keyword methods are already doing that.
|
||||
* Removes orphan keywords.
|
||||
*/
|
||||
nsresult EnsureKeywordsHash();
|
||||
nsDataHashtable<nsTrimInt64HashKey, nsString> mBookmarkToKeywordHash;
|
||||
bool mBookmarkToKeywordHashInitialized;
|
||||
|
||||
/**
|
||||
* This function must be called every time a bookmark is removed.
|
||||
*
|
||||
* @param aURI
|
||||
* Uri to test.
|
||||
*/
|
||||
nsresult UpdateKeywordsHashForRemovedBookmark(int64_t aItemId);
|
||||
nsresult removeOrphanKeywords();
|
||||
};
|
||||
|
||||
#endif // nsNavBookmarks_h_
|
||||
|
|
|
@ -12,6 +12,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
|
|||
"resource://gre/modules/TelemetryStopwatch.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Constants
|
||||
|
@ -1451,74 +1453,76 @@ urlInlineComplete.prototype = {
|
|||
|
||||
this._listener = aListener;
|
||||
|
||||
// Don't autoFill if the search term is recognized as a keyword, otherwise
|
||||
// it will override default keywords behavior. Note that keywords are
|
||||
// hashed on first use, so while the first query may delay a little bit,
|
||||
// next ones will just hit the memory hash.
|
||||
if (this._currentSearchString.length == 0 || !this._db ||
|
||||
PlacesUtils.bookmarks.getURIForKeyword(this._currentSearchString)) {
|
||||
this._finishSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't try to autofill if the search term includes any whitespace.
|
||||
// This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
|
||||
// tokenizer ends up trimming the search string and returning a value
|
||||
// that doesn't match it, or is even shorter.
|
||||
if (/\s/.test(this._currentSearchString)) {
|
||||
this._finishSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Hosts have no "/" in them.
|
||||
let lastSlashIndex = this._currentSearchString.lastIndexOf("/");
|
||||
|
||||
// Search only URLs if there's a slash in the search string...
|
||||
if (lastSlashIndex != -1) {
|
||||
// ...but not if it's exactly at the end of the search string.
|
||||
if (lastSlashIndex < this._currentSearchString.length - 1)
|
||||
this._queryURL();
|
||||
else
|
||||
Task.spawn(function* () {
|
||||
// Don't autoFill if the search term is recognized as a keyword, otherwise
|
||||
// it will override default keywords behavior. Note that keywords are
|
||||
// hashed on first use, so while the first query may delay a little bit,
|
||||
// next ones will just hit the memory hash.
|
||||
if (this._currentSearchString.length == 0 || !this._db ||
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword(this._currentSearchString)).href) {
|
||||
this._finishSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Do a synchronous search on the table of hosts.
|
||||
let query = this._hostQuery;
|
||||
query.params.search_string = this._currentSearchString.toLowerCase();
|
||||
// This is just to measure the delay to reach the UI, not the query time.
|
||||
TelemetryStopwatch.start(DOMAIN_QUERY_TELEMETRY);
|
||||
let ac = this;
|
||||
let wrapper = new AutoCompleteStatementCallbackWrapper(this, {
|
||||
handleResult: function (aResultSet) {
|
||||
let row = aResultSet.getNextRow();
|
||||
let trimmedHost = row.getResultByIndex(0);
|
||||
let untrimmedHost = row.getResultByIndex(1);
|
||||
// If the untrimmed value doesn't preserve the user's input just
|
||||
// ignore it and complete to the found host.
|
||||
if (untrimmedHost &&
|
||||
!untrimmedHost.toLowerCase().contains(ac._originalSearchString.toLowerCase())) {
|
||||
untrimmedHost = null;
|
||||
}
|
||||
|
||||
ac._result.appendMatch(ac._strippedPrefix + trimmedHost, "", "", "", untrimmedHost);
|
||||
|
||||
// handleCompletion() will cause the result listener to be called, and
|
||||
// will display the result in the UI.
|
||||
},
|
||||
|
||||
handleError: function (aError) {
|
||||
Components.utils.reportError(
|
||||
"URL Inline Complete: An async statement encountered an " +
|
||||
"error: " + aError.result + ", '" + aError.message + "'");
|
||||
},
|
||||
|
||||
handleCompletion: function (aReason) {
|
||||
TelemetryStopwatch.finish(DOMAIN_QUERY_TELEMETRY);
|
||||
ac._finishSearch();
|
||||
return;
|
||||
}
|
||||
}, this._db);
|
||||
this._pendingQuery = wrapper.executeAsync([query]);
|
||||
|
||||
// Don't try to autofill if the search term includes any whitespace.
|
||||
// This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
|
||||
// tokenizer ends up trimming the search string and returning a value
|
||||
// that doesn't match it, or is even shorter.
|
||||
if (/\s/.test(this._currentSearchString)) {
|
||||
this._finishSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Hosts have no "/" in them.
|
||||
let lastSlashIndex = this._currentSearchString.lastIndexOf("/");
|
||||
|
||||
// Search only URLs if there's a slash in the search string...
|
||||
if (lastSlashIndex != -1) {
|
||||
// ...but not if it's exactly at the end of the search string.
|
||||
if (lastSlashIndex < this._currentSearchString.length - 1)
|
||||
this._queryURL();
|
||||
else
|
||||
this._finishSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Do a synchronous search on the table of hosts.
|
||||
let query = this._hostQuery;
|
||||
query.params.search_string = this._currentSearchString.toLowerCase();
|
||||
// This is just to measure the delay to reach the UI, not the query time.
|
||||
TelemetryStopwatch.start(DOMAIN_QUERY_TELEMETRY);
|
||||
let ac = this;
|
||||
let wrapper = new AutoCompleteStatementCallbackWrapper(this, {
|
||||
handleResult: function (aResultSet) {
|
||||
let row = aResultSet.getNextRow();
|
||||
let trimmedHost = row.getResultByIndex(0);
|
||||
let untrimmedHost = row.getResultByIndex(1);
|
||||
// If the untrimmed value doesn't preserve the user's input just
|
||||
// ignore it and complete to the found host.
|
||||
if (untrimmedHost &&
|
||||
!untrimmedHost.toLowerCase().contains(ac._originalSearchString.toLowerCase())) {
|
||||
untrimmedHost = null;
|
||||
}
|
||||
|
||||
ac._result.appendMatch(ac._strippedPrefix + trimmedHost, "", "", "", untrimmedHost);
|
||||
|
||||
// handleCompletion() will cause the result listener to be called, and
|
||||
// will display the result in the UI.
|
||||
},
|
||||
|
||||
handleError: function (aError) {
|
||||
Components.utils.reportError(
|
||||
"URL Inline Complete: An async statement encountered an " +
|
||||
"error: " + aError.result + ", '" + aError.message + "'");
|
||||
},
|
||||
|
||||
handleCompletion: function (aReason) {
|
||||
TelemetryStopwatch.finish(DOMAIN_QUERY_TELEMETRY);
|
||||
ac._finishSearch();
|
||||
}
|
||||
}, this._db);
|
||||
this._pendingQuery = wrapper.executeAsync([query]);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
add_task(function* test_no_keyword() {
|
||||
Assert.deepEqual({ href: null, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should not exist");
|
||||
});
|
||||
|
||||
add_task(function* test_add_remove() {
|
||||
let item1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: "http://example1.com/",
|
||||
keyword: "test" });
|
||||
Assert.deepEqual({ href: item1.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + item1.url.href);
|
||||
|
||||
// Add a second url for the same keyword, since it's newer it should be
|
||||
// returned.
|
||||
let item2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: "http://example2.com/",
|
||||
keyword: "test" });
|
||||
Assert.deepEqual({ href: item2.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + item2.url.href);
|
||||
|
||||
// Now remove item2, should return item1 again.
|
||||
yield PlacesUtils.bookmarks.remove(item2);
|
||||
Assert.deepEqual({ href: item1.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + item1.url.href);
|
||||
|
||||
// Now remove item1, should return null again.
|
||||
yield PlacesUtils.bookmarks.remove(item1);
|
||||
Assert.deepEqual({ href: null, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should not exist");
|
||||
});
|
||||
|
||||
add_task(function* test_change_url() {
|
||||
let item = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: "http://example.com/",
|
||||
keyword: "test" });
|
||||
Assert.deepEqual({ href: item.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + item.url.href);
|
||||
|
||||
// Change the bookmark url.
|
||||
let updatedItem = yield PlacesUtils.bookmarks.update({ guid: item.guid,
|
||||
url: "http://example2.com" });
|
||||
Assert.deepEqual({ href: updatedItem.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + updatedItem.url.href);
|
||||
yield PlacesUtils.bookmarks.remove(updatedItem);
|
||||
});
|
||||
|
||||
add_task(function* test_change_keyword() {
|
||||
let item = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: "http://example.com/",
|
||||
keyword: "test" });
|
||||
Assert.deepEqual({ href: item.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + item.url.href);
|
||||
|
||||
// Change the bookmark keywprd.
|
||||
let updatedItem = yield PlacesUtils.bookmarks.update({ guid: item.guid,
|
||||
keyword: "test2" });
|
||||
Assert.deepEqual({ href: null, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should not exist");
|
||||
Assert.deepEqual({ href: updatedItem.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test2")),
|
||||
"Keyword 'test' should point to " + updatedItem.url.href);
|
||||
|
||||
// Remove the bookmark keyword.
|
||||
updatedItem = yield PlacesUtils.bookmarks.update({ guid: item.guid,
|
||||
keyword: "" });
|
||||
Assert.deepEqual({ href: null, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should not exist");
|
||||
Assert.deepEqual({ href: null, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test2")),
|
||||
"Keyword 'test' should not exist");
|
||||
yield PlacesUtils.bookmarks.remove(updatedItem);
|
||||
});
|
||||
|
||||
add_task(function* test_postData() {
|
||||
let item1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: "http://example1.com/",
|
||||
keyword: "test" });
|
||||
let itemId1 = yield PlacesUtils.promiseItemId(item1.guid);
|
||||
PlacesUtils.setPostDataForBookmark(itemId1, "testData");
|
||||
Assert.deepEqual({ href: item1.url.href, postData: "testData" },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + item1.url.href);
|
||||
|
||||
// Add a second url for the same keyword, but without postData.
|
||||
let item2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: "http://example2.com/",
|
||||
keyword: "test" });
|
||||
Assert.deepEqual({ href: item2.url.href, postData: null },
|
||||
(yield PlacesUtils.promiseHrefAndPostDataForKeyword("test")),
|
||||
"Keyword 'test' should point to " + item2.url.href);
|
||||
|
||||
yield PlacesUtils.bookmarks.remove(item1);
|
||||
yield PlacesUtils.bookmarks.remove(item2);
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
|
@ -119,6 +119,7 @@ skip-if = true
|
|||
[test_PlacesSearchAutocompleteProvider.js]
|
||||
[test_PlacesUtils_asyncGetBookmarkIds.js]
|
||||
[test_PlacesUtils_lazyobservers.js]
|
||||
[test_PlacesUtils_promiseHrefAndPostDataForKeyword.js]
|
||||
[test_placesTxn.js]
|
||||
[test_preventive_maintenance.js]
|
||||
# Bug 676989: test hangs consistently on Android
|
||||
|
|
Загрузка…
Ссылка в новой задаче