Bug 598229 (part 2) - Increase performance of Win7 JumpList favorites queries. r=sdwilsh,jimm sr=rstrong a=blocking

This commit is contained in:
Marco Bonardo 2010-10-14 10:46:38 +02:00
Родитель f0e456d04f
Коммит 00b2650bc7
6 изменённых файлов: 383 добавлений и 106 удалений

Просмотреть файл

@ -961,7 +961,7 @@ pref("browser.taskbar.lists.frequent.enabled", true);
pref("browser.taskbar.lists.recent.enabled", false);
pref("browser.taskbar.lists.maxListItemCount", 7);
pref("browser.taskbar.lists.tasks.enabled", true);
pref("browser.taskbar.lists.refreshInSeconds", 30);
pref("browser.taskbar.lists.refreshInSeconds", 120);
#endif
#endif

Просмотреть файл

@ -21,6 +21,7 @@
*
* Contributor(s):
* Jim Mathies <jmathies@mozilla.com> (Original author)
* Marco Bonardo <mak77@bonardo.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -46,6 +47,9 @@ Components.utils.import("resource://gre/modules/Services.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
// Stop updating jumplists after some idle time.
const IDLE_TIMEOUT_SECONDS = 5 * 60;
// Prefs
const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
const PREF_TASKBAR_ENABLED = "enabled";
@ -55,6 +59,12 @@ const PREF_TASKBAR_RECENT = "recent.enabled";
const PREF_TASKBAR_TASKS = "tasks.enabled";
const PREF_TASKBAR_REFRESH = "refreshInSeconds";
// Hash keys for pendingStatements.
const LIST_TYPE = {
FREQUENT: 0
, RECENT: 1
}
/**
* Exports
*/
@ -87,6 +97,10 @@ XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
return NetUtil;
});
XPCOMUtils.defineLazyServiceGetter(this, "_idle",
"@mozilla.org/widget/idleservice;1",
"nsIIdleService");
XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
"@mozilla.org/windows-taskbar;1",
"nsIWinTaskbar");
@ -129,7 +143,9 @@ var tasksCfg = [
args: "-new-tab about:blank",
iconIndex: 0, // Fx app icon
open: true,
close: false, // The jump list already has an app launch icon
close: true, // The jump list already has an app launch icon, but
// we don't always update the list on shutdown.
// Thus true for consistency.
},
// Open new tab
@ -139,7 +155,8 @@ var tasksCfg = [
args: "-browser",
iconIndex: 0, // Fx app icon
open: true,
close: false, // no point
close: true, // No point, but we don't always update the list on
// shutdown. Thus true for consistency.
},
// Toggle the Private Browsing mode
@ -202,7 +219,7 @@ var WinTaskbarJumpList =
this._initObs();
// jump list refresh timer
this._initTimer();
this._updateTimer();
},
update: function WTBJL_update() {
@ -216,7 +233,14 @@ var WinTaskbarJumpList =
_shutdown: function WTBJL__shutdown() {
this._shuttingDown = true;
this.update();
// Correctly handle a clear history on shutdown. If there are no
// entries be sure to empty all history lists. Luckily Places caches
// this value, so it's a pretty fast call.
if (!PlacesUtils.history.hasHistoryEntries) {
this.update();
}
this._free();
},
@ -226,9 +250,34 @@ var WinTaskbarJumpList =
/**
* List building
*/
*
* @note Async builders must add their mozIStoragePendingStatement to
* _pendingStatements object, using a different LIST_TYPE entry for
* each statement. Once finished they must remove it and call
* commitBuild(). When there will be no more _pendingStatements,
* commitBuild() will commit for real.
*/
_pendingStatements: {},
_hasPendingStatements: function WTBJL__hasPendingStatements() {
for (let listType in this._pendingStatements) {
return true;
}
return false;
},
_buildList: function WTBJL__buildList() {
if (this._hasPendingStatements()) {
// We were requested to update the list while another update was in
// progress, this could happen at shutdown, idle or privatebrowsing.
// Abort the current list building.
for (let listType in this._pendingStatements) {
this._pendingStatements[listType].cancel();
delete this._pendingStatements[listType];
}
this._builder.abortListBuild();
}
// anything to build?
if (!this._showFrequent && !this._showRecent && !this._showTasks) {
// don't leave the last list hanging on the taskbar.
@ -269,8 +318,9 @@ var WinTaskbarJumpList =
},
_commitBuild: function WTBJL__commitBuild() {
if (!this._builder.commitListBuild())
if (!this._hasPendingStatements() && !this._builder.commitListBuild()) {
this._builder.abortListBuild();
}
},
_buildTasks: function WTBJL__buildTasks() {
@ -301,46 +351,67 @@ var WinTaskbarJumpList =
var items = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
var list = this._getNavFrequent(this._maxItemCount);
if (!list || list.length == 0)
return;
// track frequent items so that we don't add them to
// the recent list.
this._frequentHashList = [];
list.forEach(function (entry) {
let shortcut = this._getHandlerAppItem(entry.title, entry.title, entry.uri, 1);
items.appendElement(shortcut, false);
this._frequentHashList.push(entry.uri);
}, this);
this._buildCustom(_getString("taskbar.frequent.label"), items);
this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
this._maxItemCount,
function (aResult) {
if (!aResult) {
delete this._pendingStatements[LIST_TYPE.FREQUENT];
// The are no more results, build the list.
this._buildCustom(_getString("taskbar.frequent.label"), items);
this._commitBuild();
return;
}
let title = aResult.title || aResult.uri;
let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1);
items.appendElement(shortcut, false);
this._frequentHashList.push(aResult.uri);
},
this
);
},
_buildRecent: function WTBJL__buildRecent() {
var items = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
var list = this._getNavRecent(this._maxItemCount*2);
// Frequent items will be skipped, so we select a double amount of
// entries and stop fetching results at _maxItemCount.
var count = 0;
if (!list || list.length == 0)
return;
this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
this._maxItemCount * 2,
function (aResult) {
if (!aResult) {
// The are no more results, build the list.
this._buildCustom(_getString("taskbar.recent.label"), items);
delete this._pendingStatements[LIST_TYPE.RECENT];
this._commitBuild();
return;
}
let count = 0;
for (let idx = 0; idx < list.length; idx++) {
if (count >= this._maxItemCount)
break;
let entry = list[idx];
// do not add items to recent that have already been added
// to frequent.
if (this._frequentHashList &&
this._frequentHashList.indexOf(entry.uri) != -1)
continue;
let shortcut = this._getHandlerAppItem(entry.title, entry.title, entry.uri, 1);
items.appendElement(shortcut, false);
count++;
}
this._buildCustom(_getString("taskbar.recent.label"), items);
if (count >= this._maxItemCount) {
return;
}
// Do not add items to recent that have already been added to frequent.
if (this._frequentHashList &&
this._frequentHashList.indexOf(aResult.uri) != -1) {
return;
}
let title = aResult.title || aResult.uri;
let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1);
items.appendElement(shortcut, false);
count++;
},
this
);
},
_deleteActiveJumpList: function WTBJL__deleteAJL() {
@ -381,79 +452,57 @@ var WinTaskbarJumpList =
/**
* Nav history helpers
*/
*/
_getNavFrequent: function WTBJL__getNavFrequent(depth) {
var options = PlacesUtils.history.getNewQueryOptions();
var query = PlacesUtils.history.getNewQuery();
query.beginTimeReference = query.TIME_RELATIVE_NOW;
query.beginTime = -24 * 30 * 60 * 60 * 1000000; // one month
query.endTimeReference = query.TIME_RELATIVE_NOW;
options.maxResults = depth;
options.queryType = options.QUERY_TYPE_HISTORY;
options.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING;
options.resultType = options.RESULT_TYPE_URI;
var result = PlacesUtils.history.executeQuery(query, options);
var list = [];
var rootNode = result.root;
rootNode.containerOpen = true;
for (let idx = 0; idx < rootNode.childCount; idx++) {
let node = rootNode.getChild(idx);
list.push({uri: node.uri, title: node.title});
}
rootNode.containerOpen = false;
return list;
},
_getNavRecent: function WTBJL__getNavRecent(depth) {
_getHistoryResults:
function WTBLJL__getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
var options = PlacesUtils.history.getNewQueryOptions();
options.maxResults = aLimit;
options.sortingMode = aSortingMode;
// We don't want source redirects for these queries.
options.redirectsMode = Ci.nsINavHistoryQueryOptions.REDIRECTS_MODE_TARGET;
var query = PlacesUtils.history.getNewQuery();
query.beginTimeReference = query.TIME_RELATIVE_NOW;
query.beginTime = -48 * 60 * 60 * 1000000; // two days
query.endTimeReference = query.TIME_RELATIVE_NOW;
options.maxResults = depth;
options.queryType = options.QUERY_TYPE_HISTORY;
options.sortingMode = options.SORT_BY_LASTMODIFIED_DESCENDING;
options.resultType = options.RESULT_TYPE_URI;
var result = PlacesUtils.history.executeQuery(query, options);
var list = [];
var rootNode = result.root;
rootNode.containerOpen = true;
for (var idx = 0; idx < rootNode.childCount; idx++) {
var node = rootNode.getChild(idx);
list.push({uri: node.uri, title: node.title});
}
rootNode.containerOpen = false;
return list;
// Return the pending statement to the caller, to allow cancelation.
return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.asyncExecuteLegacyQueries([query], 1, options, {
handleResult: function (aResultSet) {
for (let row; (row = aResultSet.getNextRow());) {
try {
aCallback.call(aScope,
{ uri: row.getResultByIndex(1)
, title: row.getResultByIndex(2)
});
} catch (e) {}
}
},
handleError: function (aError) {
Components.utils.reportError(
"Async execution error (" + aError.result + "): " + aError.message);
},
handleCompletion: function (aReason) {
aCallback.call(WinTaskbarJumpList, null);
},
});
},
_clearHistory: function WTBJL__clearHistory(items) {
if (!items)
return;
var URIsToRemove = [];
var enum = items.enumerate();
while (enum.hasMoreElements()) {
let oldItem = enum.getNext().QueryInterface(Ci.nsIJumpListShortcut);
if (oldItem) {
try { // in case we get a bad uri
let uriSpec = oldItem.app.getParameter(0);
PlacesUtils.bhistory.removePage(NetUtil.newURI(uriSpec));
URIsToRemove.push(NetUtil.newURI(uriSpec));
} catch (err) { }
}
}
if (URIsToRemove.length > 0) {
PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
}
},
/**
@ -482,29 +531,51 @@ var WinTaskbarJumpList =
_initObs: function WTBJL__initObs() {
Services.obs.addObserver(this, "private-browsing", false);
Services.obs.addObserver(this, "quit-application-granted", false);
// If the browser is closed while in private browsing mode, the "exit"
// notification is fired on quit-application-granted.
// History cleanup can happen at profile-change-teardown.
Services.obs.addObserver(this, "profile-before-change", false);
Services.obs.addObserver(this, "browser:purge-session-history", false);
_prefs.addObserver("", this, false);
},
_freeObs: function WTBJL__freeObs() {
Services.obs.removeObserver(this, "private-browsing");
Services.obs.removeObserver(this, "quit-application-granted");
Services.obs.removeObserver(this, "profile-before-change");
Services.obs.removeObserver(this, "browser:purge-session-history");
_prefs.removeObserver("", this);
},
_initTimer: function WTBJL__initTimer(aTimer) {
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._timer.initWithCallback(this,
_prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
this._timer.TYPE_REPEATING_SLACK);
_updateTimer: function WTBJL__updateTimer() {
if (this._enabled && !this._shuttingDown && !this._timer) {
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._timer.initWithCallback(this,
_prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
this._timer.TYPE_REPEATING_SLACK);
}
else if ((!this._enabled || this._shuttingDown) && this._timer) {
this._timer.cancel();
delete this._timer;
}
},
_hasIdleObserver: false,
_updateIdleObserver: function WTBJL__updateIdleObserver() {
if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
_idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
this._hasIdleObserver = true;
}
else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) {
_idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
this._hasIdleObserver = false;
}
},
_free: function WTBJL__free() {
this._freeObs();
this._updateTimer();
this._updateIdleObserver();
delete this._builder;
delete this._timer;
},
/**
@ -512,6 +583,8 @@ var WinTaskbarJumpList =
*/
notify: function WTBJL_notify(aTimer) {
// Add idle observer on the first notification so it doesn't hit startup.
this._updateIdleObserver();
this.update();
},
@ -521,10 +594,12 @@ var WinTaskbarJumpList =
if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED))
this._deleteActiveJumpList();
this._refreshPrefs();
this._updateTimer();
this._updateIdleObserver();
this.update();
break;
case "quit-application-granted":
case "profile-before-change":
this._shutdown();
break;
@ -535,6 +610,17 @@ var WinTaskbarJumpList =
case "private-browsing":
this.update();
break;
case "idle":
if (this._timer) {
this._timer.cancel();
delete this._timer;
}
break;
case "back":
this._updateTimer();
break;
}
},
};

Просмотреть файл

@ -47,18 +47,18 @@
*/
#include "nsISupports.idl"
#include "nsIArray.idl"
#include "nsIURI.idl"
#include "nsIVariant.idl"
interface nsIArray;
interface nsIURI;
interface nsIVariant;
interface nsIFile;
interface nsINavHistoryContainerResultNode;
interface nsINavHistoryQueryResultNode;
interface nsINavHistoryQuery;
interface nsINavHistoryQueryOptions;
interface nsINavHistoryResult;
interface nsINavHistoryBatchCallback;
interface nsITreeColumn;
[scriptable, uuid(081452e5-be5c-4038-a5ea-f1f34cb6fd81)]
interface nsINavHistoryResultNode : nsISupports

Просмотреть файл

@ -40,17 +40,39 @@
#include "nsISupports.idl"
interface mozIStorageConnection;
interface nsINavHistoryQuery;
interface nsINavHistoryQueryOptions;
interface mozIStorageStatementCallback;
interface mozIStoragePendingStatement;
/**
* This is a private interface used by Places components to get access to the
* database. If outside consumers wish to use this, they should only read from
* the database so they do not break any internal invariants.
*/
[scriptable, uuid(5fd91813-229c-4d30-851b-700afa39a987)]
[scriptable, uuid(6eb7ed3d-13ca-450b-b370-15c75e2f3dab)]
interface nsPIPlacesDatabase : nsISupports
{
/**
* The database connection used by Places.
*/
readonly attribute mozIStorageConnection DBConnection;
/**
* Asynchronously executes the statement created from queries.
*
* @see nsINavHistoryService::executeQueries
* @note THIS IS A TEMPORARY API. Don't rely on it, since it will be replaced
* in future versions by a real async querying API.
* @note Results obtained from this method differ from results obtained from
* executeQueries, because there is additional filtering and sorting
* done by the latter. Thus you should use executeQueries, unless you
* are absolutely sure that the returned results are fine for
* your use-case.
*/
mozIStoragePendingStatement asyncExecuteLegacyQueries(
[array, size_is(aQueryCount)] in nsINavHistoryQuery aQueries,
in unsigned long aQueryCount,
in nsINavHistoryQueryOptions aOptions,
in mozIStorageStatementCallback aCallback);
};

Просмотреть файл

@ -4340,6 +4340,7 @@ nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
return NS_OK;
}
// nsNavHistory::AddObserver
NS_IMETHODIMP
@ -5689,6 +5690,72 @@ nsNavHistory::FinalizeInternalStatements()
return NS_OK;
}
NS_IMETHODIMP
nsNavHistory::AsyncExecuteLegacyQueries(nsINavHistoryQuery** aQueries,
PRUint32 aQueryCount,
nsINavHistoryQueryOptions* aOptions,
mozIStorageStatementCallback* aCallback,
mozIStoragePendingStatement** _stmt)
{
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG(aQueries);
NS_ENSURE_ARG(aOptions);
NS_ENSURE_ARG(aCallback);
NS_ENSURE_ARG_POINTER(_stmt);
nsCOMArray<nsNavHistoryQuery> queries;
for (PRUint32 i = 0; i < aQueryCount; i ++) {
nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i]);
NS_ENSURE_STATE(query);
queries.AppendObject(query);
}
NS_ENSURE_ARG_MIN(queries.Count(), 1);
nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions);
NS_ENSURE_ARG(options);
nsCString queryString;
PRBool paramsPresent = PR_FALSE;
nsNavHistory::StringHash addParams;
addParams.Init(HISTORY_DATE_CONT_MAX);
nsresult rv = ConstructQueryString(queries, options, queryString,
paramsPresent, addParams);
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<mozIStorageStatement> statement;
rv = mDBConn->CreateStatement(queryString, getter_AddRefs(statement));
#ifdef DEBUG
if (NS_FAILED(rv)) {
nsCAutoString lastErrorString;
(void)mDBConn->GetLastErrorString(lastErrorString);
PRInt32 lastError = 0;
(void)mDBConn->GetLastError(&lastError);
printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
PromiseFlatCString(queryString).get(),
lastError,
PromiseFlatCString(lastErrorString).get());
}
#endif
NS_ENSURE_SUCCESS(rv, rv);
if (paramsPresent) {
// bind parameters
PRInt32 i;
for (i = 0; i < queries.Count(); i++) {
rv = BindQueryClauseParameters(statement, i, queries[i], options);
NS_ENSURE_SUCCESS(rv, rv);
}
}
addParams.EnumerateRead(BindAdditionalParameter, statement.get());
rv = statement->ExecuteAsync(aCallback, _stmt);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// nsPIPlacesHistoryListenersNotifier ******************************************
NS_IMETHODIMP

Просмотреть файл

@ -0,0 +1,102 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// This is a test for asyncExecuteLegacyQueries API.
let tests = [
function test_history_query() {
let uri = NetUtil.newURI("http://test.visit.mozilla.com/");
let title = "Test visit";
visit(uri, title);
let options = PlacesUtils.history.getNewQueryOptions();
options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
let query = PlacesUtils.history.getNewQuery();
PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.asyncExecuteLegacyQueries([query], 1, options, {
handleResult: function (aResultSet) {
for (let row; (row = aResultSet.getNextRow());) {
try {
do_check_eq(row.getResultByIndex(1), uri.spec);
do_check_eq(row.getResultByIndex(2), title);
} catch (e) {
do_throw("Error while fetching page data.");
}
}
},
handleError: function (aError) {
do_throw("Async execution error (" + aError.result + "): " + aError.message);
},
handleCompletion: function (aReason) {
run_next_test();
},
});
},
function test_bookmarks_query() {
let uri = NetUtil.newURI("http://test.bookmark.mozilla.com/");
let title = "Test bookmark";
bookmark(uri, title);
let options = PlacesUtils.history.getNewQueryOptions();
options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_LASMODIFIED_DESCENDING;
options.queryType = options.QUERY_TYPE_BOOKMARKS;
let query = PlacesUtils.history.getNewQuery();
PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.asyncExecuteLegacyQueries([query], 1, options, {
handleResult: function (aResultSet) {
for (let row; (row = aResultSet.getNextRow());) {
try {
do_check_eq(row.getResultByIndex(1), uri.spec);
do_check_eq(row.getResultByIndex(2), title);
} catch (e) {
do_throw("Error while fetching page data.");
}
}
},
handleError: function (aError) {
do_throw("Async execution error (" + aError.result + "): " + aError.message);
},
handleCompletion: function (aReason) {
run_next_test();
},
});
},
];
function visit(aURI, aTitle)
{
PlacesUtils.history.addVisit(aURI, Date.now() * 1000, null,
PlacesUtils.history.TRANSITION_TYPED, false, 0);
setPageTitle(aURI, aTitle);
}
function bookmark(aURI, aTitle)
{
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
aURI,
PlacesUtils.bookmarks.DEFAULT_INDEX,
aTitle);
}
function run_test()
{
do_test_pending();
run_next_test();
}
function run_next_test() {
if (tests.length == 0) {
do_test_finished();
return;
}
let test = tests.shift();
waitForClearHistory(function() {
remove_all_bookmarks();
do_execute_soon(test);
});
}