Merge mozilla-central to Places.

This commit is contained in:
Shawn Wilsher 2011-01-14 12:39:31 -08:00
Родитель b4361a8eb5 0ae4277be7
Коммит 5456e6f495
28 изменённых файлов: 2199 добавлений и 1239 удалений

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

@ -1036,10 +1036,6 @@ FeedWriter.prototype = {
Cu.evalInSandbox(codeStr, this._contentSandbox);
var historySvc = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
historySvc.addObserver(this, false);
// List of web handlers
var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
@ -1056,15 +1052,7 @@ FeedWriter.prototype = {
codeStr = "handlersMenuPopup.appendChild(menuItem);";
Cu.evalInSandbox(codeStr, this._contentSandbox);
// For privacy reasons we cannot set the image attribute directly
// to the icon url, see Bug 358878
var uri = makeURI(handlers[i].uri);
if (!this._setFaviconForWebReader(uri, menuItem)) {
if (uri && /^https?/.test(uri.scheme)) {
var iconURL = makeURI(uri.prePath + "/favicon.ico");
this._faviconService.setAndLoadFaviconForPage(uri, iconURL, true);
}
}
this._setFaviconForWebReader(handlers[i].uri, menuItem);
}
this._contentSandbox.menuItem = null;
}
@ -1235,10 +1223,6 @@ FeedWriter.prototype = {
this.__bundle = null;
this._feedURI = null;
this.__contentSandbox = null;
var historySvc = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
historySvc.removeObserver(this);
},
_removeFeedFromCache: function FW__removeFeedFromCache() {
@ -1358,65 +1342,41 @@ FeedWriter.prototype = {
},
/**
* Sets the icon for the given web-reader item in the readers menu
* if the favicon-service has the necessary icon stored.
* @param aURI
* the reader URI.
* Sets the icon for the given web-reader item in the readers menu.
* The icon is fetched and stored through the favicon service.
*
* @param aReaderUrl
* the reader url.
* @param aMenuItem
* the reader item in the readers menulist.
* @return true if the icon was set, false otherwise.
*
* @note For privacy reasons we cannot set the image attribute directly
* to the icon url. See Bug 358878 for details.
*/
_setFaviconForWebReader:
function FW__setFaviconForWebReader(aURI, aMenuItem) {
var faviconsSvc = this._faviconService;
var faviconURI = null;
try {
faviconURI = faviconsSvc.getFaviconForPage(aURI);
function FW__setFaviconForWebReader(aReaderUrl, aMenuItem) {
var readerURI = makeURI(aReaderUrl);
if (!/^https?/.test(readerURI.scheme)) {
// Don't try to get a favicon for non http(s) URIs.
return;
}
catch(ex) { }
if (faviconURI) {
var dataURL = faviconsSvc.getFaviconDataAsDataURL(faviconURI);
if (dataURL) {
this._contentSandbox.menuItem = aMenuItem;
this._contentSandbox.dataURL = dataURL;
var codeStr = "menuItem.setAttribute('image', dataURL);";
Cu.evalInSandbox(codeStr, this._contentSandbox);
this._contentSandbox.menuItem = null;
this._contentSandbox.dataURL = null;
return true;
}
}
return false;
var faviconURI = makeURI(readerURI.prePath + "/favicon.ico");
var self = this;
this._faviconService.setAndLoadFaviconForPage(readerURI, faviconURI, false,
function (aURI, aDataLen, aData, aMimeType) {
if (aDataLen > 0) {
var dataURL = "data:" + aMimeType + ";base64," +
btoa(String.fromCharCode.apply(null, aData));
self._contentSandbox.menuItem = aMenuItem;
self._contentSandbox.dataURL = dataURL;
var codeStr = "menuItem.setAttribute('image', dataURL);";
Cu.evalInSandbox(codeStr, self._contentSandbox);
self._contentSandbox.menuItem = null;
self._contentSandbox.dataURL = null;
}
});
},
// nsINavHistoryService
onPageChanged: function FW_onPageChanged(aURI, aWhat, aValue) {
if (aWhat == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
// Go through the readers menu and look for the corresponding
// reader menu-item for the page if any.
var spec = aURI.spec;
var possibleHandlers = this._handlersMenuList.firstChild.childNodes;
for (var i=0; i < possibleHandlers.length ; i++) {
if (possibleHandlers[i].getAttribute("webhandlerurl") == spec) {
this._setFaviconForWebReader(aURI, possibleHandlers[i]);
return;
}
}
}
},
onBeginUpdateBatch: function() { },
onEndUpdateBatch: function() { },
onVisit: function() { },
onTitleChanged: function() { },
onBeforeDeleteURI: function() { },
onDeleteURI: function() { },
onClearHistory: function() { },
onDeleteVisits: function() { },
// nsIClassInfo
getInterfaces: function FW_getInterfaces(countRef) {
var interfaces = [Ci.nsIFeedWriter, Ci.nsIClassInfo, Ci.nsISupports];

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

@ -276,27 +276,54 @@ function run_test() {
do_check_eq(observer._itemAddedParent, root);
do_check_eq(observer._itemAddedIndex, 3);
// Test removing an item with a keyword
// Test removing an item with a keyword and a tag.
// Notice in this case the tag persists since other bookmarks have same uri.
bmsvc.setKeywordForBookmark(bkmk2Id, "test_keyword");
tagssvc.tagURI(uri("http://www.example3.com"), ["test-tag"]);
var txn5 = ptSvc.removeItem(bkmk2Id);
txn5.doTransaction();
do_check_eq(observer._itemRemovedId, bkmk2Id);
do_check_eq(observer._itemRemovedFolder, root);
do_check_eq(observer._itemRemovedIndex, 2);
do_check_eq(bmsvc.getKeywordForBookmark(bkmk2Id), null);
do_check_eq(tagssvc.getTagsForURI(uri("http://www.example3.com"))[0], "test-tag");
txn5.undoTransaction();
var newbkmk2Id = observer._itemAddedId;
do_check_eq(observer._itemAddedParent, root);
do_check_eq(observer._itemAddedIndex, 2);
do_check_eq(bmsvc.getKeywordForBookmark(newbkmk2Id), "test_keyword");
do_check_eq(tagssvc.getTagsForURI(uri("http://www.example3.com"))[0], "test-tag");
txn5.redoTransaction();
do_check_eq(observer._itemRemovedId, newbkmk2Id);
do_check_eq(observer._itemRemovedFolder, root);
do_check_eq(observer._itemRemovedIndex, 2);
do_check_eq(bmsvc.getKeywordForBookmark(newbkmk2Id), null);
do_check_eq(tagssvc.getTagsForURI(uri("http://www.example3.com"))[0], "test-tag");
txn5.undoTransaction();
do_check_eq(observer._itemAddedParent, root);
do_check_eq(observer._itemAddedIndex, 2);
do_check_eq(tagssvc.getTagsForURI(uri("http://www.example3.com"))[0], "test-tag");
tagssvc.untagURI(uri("http://www.example3.com"), ["test-tag"]);
{
// Test removing an item with a tag (last bookmark for a uri).
let testURI = uri("http://www.taggedbm.com/");
ptSvc.doTransaction(
ptSvc.createItem(testURI, fldrId, bmStartIndex, "TaggedBm")
);
tagssvc.tagURI(testURI, ["test-tag"]);
let itemId = observer._itemAddedId;
txn = ptSvc.removeItem(itemId);
txn.doTransaction();
do_check_true(tagssvc.getTagsForURI(testURI).length == 0);
txn.undoTransaction();
do_check_eq(tagssvc.getTagsForURI(testURI)[0], "test-tag");
txn.redoTransaction();
do_check_true(tagssvc.getTagsForURI(testURI).length == 0);
txn.undoTransaction();
do_check_eq(tagssvc.getTagsForURI(testURI)[0], "test-tag");
txn.redoTransaction();
}
// Test creating a separator
var txn6 = ptSvc.createSeparator(root, 1);

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

@ -64,6 +64,7 @@ XPIDLSRCS += \
mozIPlacesAutoComplete.idl \
nsIMicrosummaryService.idl \
nsIPlacesImportExportService.idl \
mozIAsyncHistory.idl \
$(NULL)
endif

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

@ -0,0 +1,163 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Places Async History API.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Shawn Wilsher <me@shawnwilsher.com>
*
* 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
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsIURI;
interface nsIVariant;
%{C++
#include "jsapi.h"
%}
[scriptable, uuid(1a3b1260-4bdb-45d0-a306-dc377dd9baa4)]
interface mozIVisitInfo : nsISupports
{
/**
* The machine-local (internal) id of the visit.
*/
readonly attribute long long visitId;
/**
* The time the visit occurred.
*/
readonly attribute PRTime visitDate;
/**
* The transition type used to get to this visit. One of the TRANSITION_TYPE
* constants on nsINavHistory.
*
* @see nsINavHistory.idl
*/
readonly attribute unsigned long transitionType;
/**
* The referring URI of this visit. This may be null.
*/
readonly attribute nsIURI referrerURI;
/**
* The sessionId of this visit.
*
* @see nsINavHistory.idl
*/
readonly attribute long long sessionId;
};
[scriptable, uuid(ad83e137-c92a-4b7b-b67e-0a318811f91e)]
interface mozIPlaceInfo : nsISupports
{
/**
* The machine-local (internal) id of the place.
*/
readonly attribute long long placeId;
/**
* The globally unique id of the place.
*/
readonly attribute ACString guid;
/**
* The URI of the place.
*/
readonly attribute nsIURI uri;
/**
* The title associated with the place.
*/
readonly attribute AString title;
/**
* The frecency of the place.
*/
readonly attribute long long frecency;
/**
* An array of mozIVisitInfo objects for the place.
*/
[implicit_jscontext]
readonly attribute jsval visits;
};
[scriptable,function, uuid(3b97ca3c-5ea8-424f-b429-797477c52302)]
interface mozIVisitInfoCallback : nsISupports
{
/**
* Called for each visit added, title change, or guid change when passed to
* mozIAsyncHistory::updatePlaces.
*
* @param aResultCode
* nsresult of the change indicating success or failure reason.
* @param aPlaceInfo
* The information that was being entered into the database.
*/
void onComplete(in nsresult aResultCode,
in mozIPlaceInfo aPlaceInfo);
};
[scriptable, uuid(f79ca67c-7e57-4511-a400-ea31001c762f)]
interface mozIAsyncHistory : nsISupports
{
/**
* Adds a set of visits for one or more mozIPlaceInfo objects, and updates
* each mozIPlaceInfo's title or guid. It is not necessary to add visits to
* change a Place's title or guid.
*
* @param aPlaceInfo
* The mozIPlaceInfo object[s] containing the information to store or
* update. This can be a single object, or an array of objects.
* @param [optional] aCallback
* Callback to be notified for each visit addition, title change, or
* guid change. If more than one operation is done for a given place,
* only one callback will be given (i.e. title change and add visit).
*
* @throws NS_ERROR_INVALID_ARG
* - Passing in NULL for aPlaceInfo.
* - Not providing at least one valid guid, placeId, or uri for all
* mozIPlaceInfo object[s].
* - Not providing an array or nothing for the visits property of
* mozIPlaceInfo (the property can be undefined for title or guid
* changes).
* - Not providing a visitDate and transitionType for each
* mozIVisitInfo.
* - Providing an invalid transitionType for a mozIVisitInfo.
*/
[implicit_jscontext]
void updatePlaces(in jsval aPlaceInfo,
[optional] in mozIVisitInfoCallback aCallback);
};

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

@ -406,8 +406,8 @@ AsyncFetchAndSetIconForPage::start(nsIURI* aFaviconURI,
// In future evaluate to store a resample of the image. For now avoid that
// for database size concerns.
// Don't store favicons for error pages too.
if (page.spec.Equals(icon.spec) ||
page.spec.Equals(FAVICON_ERRORPAGE_URL)) {
if (icon.spec.Equals(page.spec) ||
icon.spec.Equals(FAVICON_ERRORPAGE_URL)) {
return NS_OK;
}
@ -754,8 +754,8 @@ AsyncAssociateIconToPage::Run()
if (mPage.id == 0) {
nsCOMPtr<mozIStorageStatement> stmt =
mFaviconSvc->mSyncStatements.GetCachedStatement(NS_LITERAL_CSTRING(
"INSERT INTO moz_places (url, rev_host, favicon_id, guid) "
"VALUES (:page_url, :rev_host, :favicon_id, GENERATE_GUID()) "
"INSERT INTO moz_places (url, rev_host, hidden, favicon_id, guid) "
"VALUES (:page_url, :rev_host, 1, :favicon_id, GENERATE_GUID()) "
));
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);

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

@ -348,5 +348,14 @@ ForceWALCheckpoint(mozIStorageConnection* aDBConn)
(void)stmt->ExecuteAsync(nsnull, getter_AddRefs(handle));
}
bool
GetHiddenState(bool aIsRedirect,
PRUint32 aTransitionType)
{
return aTransitionType == nsINavHistoryService::TRANSITION_FRAMED_LINK ||
aTransitionType == nsINavHistoryService::TRANSITION_EMBED ||
aIsRedirect;
}
} // namespace places
} // namespace mozilla

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

@ -267,6 +267,19 @@ protected:
*/
void ForceWALCheckpoint(mozIStorageConnection* aDBConn);
/**
* Determines if a visit should be marked as hidden given its transition type
* and whether or not it was a redirect.
*
* @param aIsRedirect
* True if this visit was a redirect, false otherwise.
* @param aTransitionType
* The transition type of the visit.
* @return true if this visit should be hidden.
*/
bool GetHiddenState(bool aIsRedirect,
PRUint32 aTransitionType);
} // namespace places
} // namespace mozilla

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

@ -73,6 +73,94 @@ namespace places {
// Observer event fired after a visit has been registered in the DB.
#define URI_VISIT_SAVED "uri-visit-saved"
////////////////////////////////////////////////////////////////////////////////
//// VisitData
struct VisitData {
VisitData()
: placeId(0)
, visitId(0)
, sessionId(0)
, hidden(true)
, typed(false)
, transitionType(PR_UINT32_MAX)
, visitTime(0)
{
guid.SetIsVoid(PR_TRUE);
title.SetIsVoid(PR_TRUE);
}
VisitData(nsIURI* aURI,
nsIURI* aReferrer = NULL)
: placeId(0)
, visitId(0)
, sessionId(0)
, hidden(true)
, typed(false)
, transitionType(PR_UINT32_MAX)
, visitTime(0)
{
(void)aURI->GetSpec(spec);
(void)GetReversedHostname(aURI, revHost);
if (aReferrer) {
(void)aReferrer->GetSpec(referrerSpec);
}
guid.SetIsVoid(PR_TRUE);
title.SetIsVoid(PR_TRUE);
}
/**
* Sets the transition type of the visit, as well as if it was typed and
* should be hidden (based on the transition type specified).
*
* @param aTransitionType
* The transition type constant to set. Must be one of the
* TRANSITION_ constants on nsINavHistoryService.
*/
void SetTransitionType(PRUint32 aTransitionType)
{
typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
bool redirected =
aTransitionType == nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY ||
aTransitionType == nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
hidden = GetHiddenState(redirected, aTransitionType);
transitionType = aTransitionType;
}
/**
* Determines if this refers to the same url as aOther, and updates aOther
* with missing information if so.
*
* @param aOther
* The other place to check against.
* @return true if this is a visit for the same place as aOther, false
* otherwise.
*/
bool IsSamePlaceAs(VisitData& aOther)
{
if (!spec.Equals(aOther.spec)) {
return false;
}
aOther.placeId = placeId;
aOther.guid = guid;
return true;
}
PRInt64 placeId;
nsCString guid;
PRInt64 visitId;
PRInt64 sessionId;
nsCString spec;
nsString revHost;
bool hidden;
bool typed;
PRUint32 transitionType;
PRTime visitTime;
nsString title;
nsCString referrerSpec;
};
////////////////////////////////////////////////////////////////////////////////
//// Anonymous Helpers
@ -186,30 +274,6 @@ private:
bool mIsVisited;
};
struct VisitData {
VisitData()
: placeId(0)
, visitId(0)
, sessionId(0)
, hidden(false)
, typed(false)
, transitionType(-1)
, visitTime(0)
{
}
PRInt64 placeId;
PRInt64 visitId;
PRInt64 sessionId;
nsCString spec;
nsString revHost;
bool hidden;
bool typed;
PRInt32 transitionType;
PRTime visitTime;
};
/**
* Notifies observers about a visit.
*/
@ -263,10 +327,47 @@ private:
VisitData mReferrer;
};
/**
* Notifies a callback object about completion.
*/
class NotifyCompletion : public nsRunnable
{
public:
NotifyCompletion(mozIVisitInfoCallback* aCallback,
const VisitData& aPlace,
nsresult aResult)
: mCallback(aCallback)
, mPlace(aPlace)
, mResult(aResult)
{
NS_PRECONDITION(aCallback, "Must pass a non-null callback!");
}
NS_IMETHOD Run()
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
// TODO build a mozIPlaceInfo object for the visit.
(void)mCallback->OnComplete(mResult, nsnull);
return NS_OK;
}
private:
/**
* Callers MUST hold a strong reference to this that outlives us because we
* may be created off of the main thread, and therefore cannot call AddRef on
* this object (and therefore cannot hold a strong reference to it).
*/
mozIVisitInfoCallback* mCallback;
VisitData mPlace;
const nsresult mResult;
};
/**
* Adds a visit to the database.
*/
class InsertVisitedURI : public nsRunnable
class InsertVisitedURIs : public nsRunnable
{
public:
/**
@ -274,28 +375,21 @@ public:
*
* @param aConnection
* The database connection to use for these operations.
* @param aPlace
* The location to record a visit.
* @param [optional] aReferrer
* The page that "referred" us to aPlace.
* @param aPlaces
* The locations to record visits.
* @param [optional] aCallback
* The callback to notify about the visit.
*/
static nsresult Start(mozIStorageConnection* aConnection,
VisitData& aPlace,
nsIURI* aReferrer = nsnull)
nsTArray<VisitData>& aPlaces,
mozIVisitInfoCallback* aCallback = NULL)
{
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
NS_PRECONDITION(aPlaces.Length() > 0, "Must pass a non-empty array!");
nsRefPtr<InsertVisitedURI> event =
new InsertVisitedURI(aConnection, aPlace, aReferrer);
// Speculatively get a new session id for our visit. While it is true that
// we will use the session id from the referrer if the visit was "recent"
// enough, we cannot call this method off of the main thread, so we have to
// consume an id now.
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(navHistory, NS_ERROR_UNEXPECTED);
event->mPlace.sessionId = navHistory->GetNewSessionID();
nsRefPtr<InsertVisitedURIs> event =
new InsertVisitedURIs(aConnection, aPlaces, aCallback);
// Get the target thread, and then start the work!
nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
@ -311,151 +405,123 @@ public:
NS_PRECONDITION(!NS_IsMainThread(),
"This should not be called on the main thread");
bool known = FetchPageInfo(mPlace);
// If we had a referrer, we want to know about its last visit to put this
// new visit into the same session.
if (!mReferrer.spec.IsEmpty()) {
bool recentVisit = FetchVisitInfo(mReferrer, mPlace.visitTime);
// At this point, we know the referrer's session id, which this new visit
// should also share.
if (recentVisit) {
mPlace.sessionId = mReferrer.sessionId;
}
// However, if it isn't recent enough, we don't care to log anything about
// the referrer and we'll start a new session.
else {
// This is sufficient to ignore our referrer. This behavior has test
// coverage, so if this invariant changes, we'll know.
mReferrer.visitId = 0;
}
}
mozStorageTransaction transaction(mDBConn, PR_FALSE,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
nsresult rv;
nsCOMPtr<mozIStorageStatement> stmt;
// If the page was in moz_places, we need to update the entry.
if (known) {
NS_ASSERTION(mPlace.placeId > 0, "must have a valid place id!");
stmt = mHistory->syncStatements.GetCachedStatement(
"UPDATE moz_places "
"SET hidden = :hidden, typed = :typed "
"WHERE id = :page_id "
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
VisitData* lastPlace = NULL;
for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
VisitData& place = mPlaces.ElementAt(i);
VisitData& referrer = mReferrers.ElementAt(i);
// We can avoid a database lookup if it's the same place as the last
// visit we added.
bool known = (lastPlace && lastPlace->IsSamePlaceAs(place)) ||
mHistory->FetchPageInfo(place);
FetchReferrerInfo(referrer, place);
nsresult rv = DoDatabaseInserts(known, place, referrer);
if (mCallback) {
nsCOMPtr<nsIRunnable> event =
new NotifyCompletion(mCallback, place, rv);
nsresult rv2 = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv2, rv2);
}
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
lastPlace = &mPlaces.ElementAt(i);
}
// Otherwise, the page was not in moz_places, so now we have to add it.
else {
NS_ASSERTION(mPlace.placeId == 0, "should not have a valid place id!");
stmt = mHistory->syncStatements.GetCachedStatement(
"INSERT INTO moz_places "
"(url, rev_host, hidden, typed, guid) "
"VALUES (:page_url, :rev_host, :hidden, :typed, GENERATE_GUID()) "
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
mPlace.revHost);
NS_ENSURE_SUCCESS(rv, rv);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPlace.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), mPlace.typed);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), mPlace.hidden);
NS_ENSURE_SUCCESS(rv, rv);
mozStorageStatementScoper scoper(stmt);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
rv = AddVisit(mPlace, mReferrer);
NS_ENSURE_SUCCESS(rv, rv);
rv = UpdateFrecency(mPlace);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// Finally, dispatch an event to the main thread to notify observers.
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(mPlace, mReferrer);
rv = NS_DispatchToMainThread(event);
nsresult rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
private:
InsertVisitedURI(mozIStorageConnection* aConnection,
VisitData& aPlace,
nsIURI* aReferrer)
InsertVisitedURIs(mozIStorageConnection* aConnection,
nsTArray<VisitData>& aPlaces,
mozIVisitInfoCallback* aCallback)
: mDBConn(aConnection)
, mPlace(aPlace)
, mCallback(aCallback)
, mHistory(History::GetService())
{
if (aReferrer) {
(void)aReferrer->GetSpec(mReferrer.spec);
NS_PRECONDITION(NS_IsMainThread(),
"This should be called on the main thread");
(void)mPlaces.SwapElements(aPlaces);
(void)mReferrers.SetLength(mPlaces.Length());
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
mReferrers[i].spec = mPlaces[i].referrerSpec;
// Speculatively get a new session id for our visit if the current session
// id is non-valid. While it is true that we will use the session id from
// the referrer if the visit was "recent" enough, we cannot call this
// method off of the main thread, so we have to consume an id now.
if (mPlaces[i].sessionId <= 0) {
mPlaces[i].sessionId = navHistory->GetNewSessionID();
}
}
// We AddRef on the main thread, and release it when we are destroyed.
NS_IF_ADDREF(mCallback);
}
virtual ~InsertVisitedURIs()
{
if (mCallback) {
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
(void)NS_ProxyRelease(mainThread, mCallback, PR_TRUE);
}
}
/**
* Loads information about the page into _place from moz_places.
* Inserts or updates the entry in moz_places for this visit, adds the visit,
* and updates the frecency of the place.
*
* @param _place
* The VisitData for the place we need to know information about.
* @return true if the page was recorded in moz_places, false otherwise.
* @param aKnown
* True if we already have an entry for this place in moz_places, false
* otherwise.
* @param aPlace
* The place we are adding a visit for.
* @param aReferrer
* The referrer for aPlace.
*/
bool FetchPageInfo(VisitData& _place)
nsresult DoDatabaseInserts(bool aKnown,
VisitData& aPlace,
VisitData& aReferrer)
{
NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
NS_PRECONDITION(!NS_IsMainThread(),
"This should not be called on the main thread");
nsCOMPtr<mozIStorageStatement> stmt =
mHistory->syncStatements.GetCachedStatement(
"SELECT id, typed, hidden "
"FROM moz_places "
"WHERE url = :page_url "
);
NS_ENSURE_TRUE(stmt, false);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
_place.spec);
NS_ENSURE_SUCCESS(rv, false);
PRBool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, false);
if (!hasResult) {
return false;
// If the page was in moz_places, we need to update the entry.
nsresult rv;
if (aKnown) {
rv = mHistory->UpdatePlace(aPlace);
NS_ENSURE_SUCCESS(rv, rv);
}
// Otherwise, the page was not in moz_places, so now we have to add it.
else {
rv = mHistory->InsertPlace(aPlace);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->GetInt64(0, &_place.placeId);
NS_ENSURE_SUCCESS(rv, false);
rv = AddVisit(aPlace, aReferrer);
NS_ENSURE_SUCCESS(rv, rv);
if (!_place.typed) {
// If this transition wasn't typed, others might have been. If database
// has location as typed, reflect that in our data structure.
PRInt32 typed;
rv = stmt->GetInt32(1, &typed);
_place.typed = !!typed;
NS_ENSURE_SUCCESS(rv, true);
}
if (_place.hidden) {
// If this transition was hidden, it is possible that others were not.
// Any one visible transition makes this location visible. If database
// has location as visible, reflect that in our data structure.
PRInt32 hidden;
rv = stmt->GetInt32(2, &hidden);
_place.hidden = !!hidden;
NS_ENSURE_SUCCESS(rv, true);
}
// TODO (bug 623969) we shouldn't update this after each visit, but
// rather only for each unique place to save disk I/O.
rv = UpdateFrecency(aPlace);
NS_ENSURE_SUCCESS(rv, rv);
return true;
return NS_OK;
}
/**
@ -512,6 +578,41 @@ private:
return false;
}
/**
* Fetches information about a referrer and sets the session id for aPlace if
* it was a recent visit or not.
*
* @param aReferrer
* The VisitData for the referrer. This will be populated with
* FetchVisitInfo.
* @param aPlace
* The VisitData for the visit we will eventually add.
*
*/
void FetchReferrerInfo(VisitData& aReferrer,
VisitData& aPlace)
{
if (aReferrer.spec.IsEmpty()) {
return;
}
// If we had a referrer, we want to know about its last visit to put this
// new visit into the same session.
bool recentVisit = FetchVisitInfo(aReferrer, aPlace.visitTime);
// At this point, we know the referrer's session id, which this new visit
// should also share.
if (recentVisit) {
aPlace.sessionId = aReferrer.sessionId;
}
// However, if it isn't recent enough, we don't care to log anything about
// the referrer and we'll start a new session.
else {
// This is sufficient to ignore our referrer. This behavior has test
// coverage, so if this invariant changes, we'll know.
aReferrer.visitId = 0;
}
}
/**
* Adds a visit for _place and updates it with the right visit id.
*
@ -532,7 +633,7 @@ private:
"VALUES (:from_visit, :page_id, :visit_date, :visit_type, :session) "
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
@ -594,7 +695,7 @@ private:
"WHERE id = :page_id"
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
@ -623,7 +724,7 @@ private:
"WHERE id = :page_id AND frecency <> 0"
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
@ -647,8 +748,15 @@ private:
mozIStorageConnection* mDBConn;
VisitData mPlace;
VisitData mReferrer;
nsTArray<VisitData> mPlaces;
nsTArray<VisitData> mReferrers;
/**
* We own a strong reference to this, but in an indirect way. We call AddRef
* in our constructor, which happens on the main thread, and proxy the relase
* of the object to the main thread in our destructor.
*/
mozIVisitInfoCallback* mCallback;
/**
* Strong reference to the History object because we do not want it to
@ -744,48 +852,26 @@ public:
"This should not be called on the main thread");
// First, see if the page exists in the database (we'll need its id later).
nsCOMPtr<mozIStorageStatement> stmt =
mHistory->syncStatements.GetCachedStatement(
"SELECT id, title "
"FROM moz_places "
"WHERE url = :page_url "
);
NS_ENSURE_STATE(stmt);
PRInt64 placeId = 0;
nsAutoString title;
{
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
mSpec);
NS_ENSURE_SUCCESS(rv, rv);
PRBool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
if (!hasResult) {
// We have no record of this page, so there is no need to do any further
// work.
return NS_OK;
}
rv = stmt->GetInt64(0, &placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetString(1, title);
NS_ENSURE_SUCCESS(rv, rv);
bool exists = mHistory->FetchPageInfo(mPlace);
if (!exists) {
// We have no record of this page, so there is no need to do any further
// work.
return NS_OK;
}
NS_ASSERTION(placeId > 0, "We somehow have an invalid place id here!");
NS_ASSERTION(mPlace.placeId > 0,
"We somehow have an invalid place id here!");
// Also, if we have the same title, there is no reason to do another write
// or notify our observers, so bail early.
if (mTitle.Equals(title) || (mTitle.IsVoid() && title.IsVoid())) {
if (mTitle.Equals(mPlace.title) ||
(mTitle.IsVoid() && mPlace.title.IsVoid())) {
return NS_OK;
}
// Now we can update our database record.
stmt = mHistory->syncStatements.GetCachedStatement(
nsCOMPtr<mozIStorageStatement> stmt =
mHistory->syncStatements.GetCachedStatement(
"UPDATE moz_places "
"SET title = :page_title "
"WHERE id = :page_id "
@ -795,7 +881,7 @@ public:
{
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
placeId);
mPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
if (mTitle.IsVoid()) {
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
@ -809,7 +895,7 @@ public:
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIRunnable> event = new NotifyTitleObservers(mSpec, mTitle);
nsCOMPtr<nsIRunnable> event = new NotifyTitleObservers(mPlace.spec, mTitle);
nsresult rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
@ -819,13 +905,13 @@ public:
private:
SetPageTitle(const nsCString& aSpec,
const nsString& aTitle)
: mSpec(aSpec)
, mTitle(aTitle)
: mTitle(aTitle)
, mHistory(History::GetService())
{
mPlace.spec = aSpec;
}
const nsCString mSpec;
VisitData mPlace;
const nsString mTitle;
/**
@ -835,6 +921,52 @@ private:
nsRefPtr<History> mHistory;
};
/**
* Stores an embed visit, and notifies observers.
*
* @param aPlace
* The VisitData of the visit to store as an embed visit.
* @param [optional] aCallback
* The mozIVisitInfoCallback to notify, if provided.
*/
void
StoreAndNotifyEmbedVisit(VisitData& aPlace,
mozIVisitInfoCallback* aCallback = NULL)
{
NS_PRECONDITION(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
"Must only pass TRANSITION_EMBED visits to this!");
NS_PRECONDITION(NS_IsMainThread(), "Must be called on the main thread!");
nsCOMPtr<nsIURI> uri;
(void)NS_NewURI(getter_AddRefs(uri), aPlace.spec);
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
if (!navHistory || !uri) {
return;
}
navHistory->registerEmbedVisit(uri, aPlace.visitTime);
if (aCallback) {
// NotifyCompletion does not hold a strong reference to the callback, so we
// have to manage it by AddRefing now and then releasing it after the event
// has run.
NS_ADDREF(aCallback);
nsCOMPtr<nsIRunnable> event =
new NotifyCompletion(aCallback, aPlace, NS_OK);
(void)NS_DispatchToMainThread(event);
// Also dispatch an event to release our reference to the callback after
// NotifyCompletion has run.
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
(void)NS_ProxyRelease(mainThread, aCallback, PR_TRUE);
}
VisitData noReferrer;
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
(void)NS_DispatchToMainThread(event);
}
} // anonymous namespace
////////////////////////////////////////////////////////////////////////////////
@ -936,6 +1068,130 @@ History::GetIsVisitedStatement()
return mIsVisitedStatement;
}
nsresult
History::InsertPlace(const VisitData& aPlace)
{
NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
nsCOMPtr<mozIStorageStatement> stmt = syncStatements.GetCachedStatement(
"INSERT INTO moz_places "
"(url, title, rev_host, hidden, typed, guid) "
"VALUES (:url, :title, :rev_host, :hidden, :typed, :guid) "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
aPlace.revHost);
NS_ENSURE_SUCCESS(rv, rv);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
NS_ENSURE_SUCCESS(rv, rv);
if (aPlace.title.IsVoid()) {
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
}
else {
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
StringHead(aPlace.title, TITLE_LENGTH_MAX));
}
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString guid(aPlace.guid);
if (aPlace.guid.IsVoid()) {
rv = GenerateGUID(guid);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
History::UpdatePlace(const VisitData& aPlace)
{
NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!");
NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
nsCOMPtr<mozIStorageStatement> stmt = syncStatements.GetCachedStatement(
"UPDATE moz_places "
"SET hidden = :hidden, typed = :typed "
"WHERE id = :page_id "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
aPlace.placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
bool
History::FetchPageInfo(VisitData& _place)
{
NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
nsCOMPtr<mozIStorageStatement> stmt = syncStatements.GetCachedStatement(
"SELECT id, typed, hidden, title "
"FROM moz_places "
"WHERE url = :page_url "
);
NS_ENSURE_TRUE(stmt, false);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
_place.spec);
NS_ENSURE_SUCCESS(rv, false);
PRBool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, false);
if (!hasResult) {
return false;
}
rv = stmt->GetInt64(0, &_place.placeId);
NS_ENSURE_SUCCESS(rv, false);
if (!_place.typed) {
// If this transition wasn't typed, others might have been. If database
// has location as typed, reflect that in our data structure.
PRInt32 typed;
rv = stmt->GetInt32(1, &typed);
_place.typed = !!typed;
NS_ENSURE_SUCCESS(rv, true);
}
if (_place.hidden) {
// If this transition was hidden, it is possible that others were not.
// Any one visible transition makes this location visible. If database
// has location as visible, reflect that in our data structure.
PRInt32 hidden;
rv = stmt->GetInt32(2, &hidden);
_place.hidden = !!hidden;
NS_ENSURE_SUCCESS(rv, true);
}
rv = stmt->GetString(3, _place.title);
NS_ENSURE_SUCCESS(rv, true);
return true;
}
/* static */
History*
History::GetService()
@ -1048,15 +1304,17 @@ History::VisitURI(nsIURI* aURI,
}
}
VisitData place;
rv = aURI->GetSpec(place.spec);
NS_ENSURE_SUCCESS(rv, rv);
(void)GetReversedHostname(aURI, place.revHost);
nsTArray<VisitData> placeArray(1);
NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)),
NS_ERROR_OUT_OF_MEMORY);
VisitData& place = placeArray.ElementAt(0);
NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
place.visitTime = PR_Now();
// Assigns a type to the edge in the visit linked list. Each type will be
// considered differently when weighting the frecency of a location.
PRUint32 recentFlags = navHistory->GetRecentFlags(aURI);
bool redirected = false;
bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED;
// Embed visits should never be added to the database, and the same is valid
@ -1067,53 +1325,39 @@ History::VisitURI(nsIURI* aURI,
if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) {
// A frame redirected to a new site without user interaction.
place.transitionType = nsINavHistoryService::TRANSITION_EMBED;
place.SetTransitionType(nsINavHistoryService::TRANSITION_EMBED);
}
else if (aFlags & IHistory::REDIRECT_TEMPORARY) {
place.transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY;
redirected = true;
place.SetTransitionType(nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
}
else if (aFlags & IHistory::REDIRECT_PERMANENT) {
place.transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
redirected = true;
place.SetTransitionType(nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT);
}
else if (recentFlags & nsNavHistory::RECENT_TYPED) {
place.transitionType = nsINavHistoryService::TRANSITION_TYPED;
place.SetTransitionType(nsINavHistoryService::TRANSITION_TYPED);
}
else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
place.transitionType = nsINavHistoryService::TRANSITION_BOOKMARK;
place.SetTransitionType(nsINavHistoryService::TRANSITION_BOOKMARK);
}
else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) {
// User activated a link in a frame.
place.transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
place.SetTransitionType(nsINavHistoryService::TRANSITION_FRAMED_LINK);
}
else {
// User was redirected or link was clicked in the main window.
place.transitionType = nsINavHistoryService::TRANSITION_LINK;
place.SetTransitionType(nsINavHistoryService::TRANSITION_LINK);
}
place.typed = place.transitionType == nsINavHistoryService::TRANSITION_TYPED;
place.hidden =
place.transitionType == nsINavHistoryService::TRANSITION_FRAMED_LINK ||
place.transitionType == nsINavHistoryService::TRANSITION_EMBED ||
redirected;
place.visitTime = PR_Now();
// EMBED visits are session-persistent and should not go through the database.
// They exist only to keep track of isVisited status during the session.
if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
navHistory->registerEmbedVisit(aURI, place.visitTime);
// Finally, enqueue an event to notify observers.
VisitData noReferrer;
nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, noReferrer);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
StoreAndNotifyEmbedVisit(place);
}
else {
mozIStorageConnection* dbConn = GetDBConn();
NS_ENSURE_STATE(dbConn);
rv = InsertVisitedURI::Start(dbConn, place, aLastVisitedURI);
rv = InsertVisitedURIs::Start(dbConn, placeArray);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -1286,6 +1530,19 @@ History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// mozIAsyncHistory
NS_IMETHODIMP
History::UpdatePlaces(const jsval& aPlaceInfos,
mozIVisitInfoCallback* aCallback,
JSContext* aCtx)
{
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIObserver
@ -1308,9 +1565,10 @@ History::Observe(nsISupports* aSubject, const char* aTopic,
////////////////////////////////////////////////////////////////////////////////
//// nsISupports
NS_IMPL_THREADSAFE_ISUPPORTS2(
NS_IMPL_THREADSAFE_ISUPPORTS3(
History
, IHistory
, mozIAsyncHistory
, nsIObserver
)

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

@ -16,7 +16,7 @@
* The Original Code is Places code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
@ -41,6 +41,7 @@
#define mozilla_places_History_h_
#include "mozilla/IHistory.h"
#include "mozIAsyncHistory.h"
#include "mozilla/dom/Link.h"
#include "nsTHashtable.h"
#include "nsString.h"
@ -54,15 +55,19 @@
namespace mozilla {
namespace places {
struct VisitData;
#define NS_HISTORYSERVICE_CID \
{0x0937a705, 0x91a6, 0x417a, {0x82, 0x92, 0xb2, 0x2e, 0xb1, 0x0d, 0xa8, 0x6c}}
class History : public IHistory
, public mozIAsyncHistory
, public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IHISTORY
NS_DECL_MOZIASYNCHISTORY
NS_DECL_NSIOBSERVER
History();
@ -80,6 +85,31 @@ public:
*/
mozIStorageAsyncStatement* GetIsVisitedStatement();
/**
* Adds an entry in moz_places with the data in aVisitData.
*
* @param aVisitData
* The visit data to use to populate a new row in moz_places.
*/
nsresult InsertPlace(const VisitData& aVisitData);
/**
* Updates an entry in moz_places with the data in aVisitData.
*
* @param aVisitData
* The visit data to use to update the existing row in moz_places.
*/
nsresult UpdatePlace(const VisitData& aVisitData);
/**
* Loads information about the page into _place from moz_places.
*
* @param _place
* The VisitData for the place we need to know information about.
* @return true if the page was recorded in moz_places, false otherwise.
*/
bool FetchPageInfo(VisitData& _place);
/**
* Obtains a pointer to this service.
*/

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

@ -43,6 +43,9 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
// Seconds between maintenance runs.
const MAINTENANCE_INTERVAL_SECONDS = 7 * 86400;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
/**
@ -61,9 +64,16 @@ PlacesCategoriesStarter.prototype = {
{
switch (aTopic) {
case "idle-daily":
// Run places.sqlite maintenance tasks.
Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
PlacesDBUtils.maintenanceOnIdle();
// Once a week run places.sqlite maintenance tasks.
let lastMaintenance = 0;
try {
lastMaintenance = Services.prefs.getIntPref("places.database.lastMaintenance");
} catch (ex) {}
let nowSeconds = parseInt(Date.now() / 1000);
if (lastMaintenance < nowSeconds - MAINTENANCE_INTERVAL_SECONDS) {
Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
PlacesDBUtils.maintenanceOnIdle();
}
break;
default:
throw new Error("Trying to handle an unknown category.");

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

@ -51,7 +51,7 @@ let EXPORTED_SYMBOLS = [ "PlacesDBUtils" ];
////////////////////////////////////////////////////////////////////////////////
//// Constants
const FINISHED_MAINTENANCE_NOTIFICATION_TOPIC = "places-maintenance-finished";
const FINISHED_MAINTENANCE_TOPIC = "places-maintenance-finished";
////////////////////////////////////////////////////////////////////////////////
//// Smart getters
@ -61,41 +61,244 @@ XPCOMUtils.defineLazyGetter(this, "DBConn", function() {
});
////////////////////////////////////////////////////////////////////////////////
//// nsPlacesDBUtils class
//// PlacesDBUtils
function nsPlacesDBUtils() {
}
let PlacesDBUtils = {
/**
* Executes a list of maintenance tasks.
* Once finished it will pass a array log to the callback attached to tasks,
* or print out to the error console if no callback is defined.
* FINISHED_MAINTENANCE_TOPIC is notified through observer service on finish.
*
* @param aTasks
* Tasks object to execute.
*/
_executeTasks: function PDBU__executeTasks(aTasks)
{
let task = aTasks.pop();
if (task) {
task.call(PlacesDBUtils, aTasks);
}
else {
if (aTasks.callback) {
let scope = aTasks.scope || Cu.getGlobalForObject(aTasks.callback);
aTasks.callback.call(scope, aTasks.messages);
}
else {
// Output to the error console.
let messages = aTasks.messages
.unshift("[ Places Maintenance ]");
try {
Services.console.logStringMessage(messages.join("\n"));
} catch(ex) {}
}
nsPlacesDBUtils.prototype = {
_statementsRunningCount: 0,
// Notify observers that maintenance finished.
Services.prefs.setIntPref("places.database.lastMaintenance", parseInt(Date.now() / 1000));
Services.obs.notifyObservers(null, FINISHED_MAINTENANCE_TOPIC, null);
}
},
//////////////////////////////////////////////////////////////////////////////
//// mozIStorageStatementCallback
/**
* Executes integrity check and common maintenance tasks.
*
* @param [optional] aCallback
* Callback to be invoked when done. The callback will get a array
* of log messages.
* @param [optional] aScope
* Scope for the callback.
*/
maintenanceOnIdle: function PDBU_maintenanceOnIdle(aCallback, aScope)
{
let tasks = new Tasks([
this.checkIntegrity
, this.checkCoherence
, this._refreshUI
]);
tasks.callback = aCallback;
tasks.scope = aScope;
this._executeTasks(tasks);
},
handleError: function PDBU_handleError(aError) {
/**
* Executes integrity check, common and advanced maintenance tasks (like
* expiration and vacuum). Will also collect statistics on the database.
*
* @param [optional] aCallback
* Callback to be invoked when done. The callback will get a array
* of log messages.
* @param [optional] aScope
* Scope for the callback.
*/
checkAndFixDatabase: function PDBU_checkAndFixDatabase(aCallback, aScope)
{
let tasks = new Tasks([
this.checkIntegrity
, this.checkCoherence
, this.expire
, this.vacuum
, this.stats
, this._refreshUI
]);
tasks.callback = aCallback;
tasks.scope = aScope;
this._executeTasks(tasks);
},
/**
* Forces a full refresh of Places views.
*
* @param [optional] aTasks
* Tasks object to execute.
*/
_refreshUI: function PDBU__refreshUI(aTasks)
{
let tasks = new Tasks(aTasks);
// Send batch update notifications to update the UI.
PlacesUtils.history.runInBatchMode({
runBatched: function (aUserData) {}
}, null);
PlacesDBUtils._executeTasks(tasks);
},
_handleError: function PDBU__handleError(aError)
{
Cu.reportError("Async statement execution returned with '" +
aError.result + "', '" + aError.message + "'");
},
handleCompletion: function PDBU_handleCompletion(aReason) {
// We serve only the last statement completion
if (--this._statementsRunningCount > 0)
return;
/**
* Tries to execute a REINDEX on the database.
*
* @param [optional] aTasks
* Tasks object to execute.
*/
reindex: function PDBU_reindex(aTasks)
{
let tasks = new Tasks(aTasks);
tasks.log("> Reindex");
// We finished executing all statements.
// Sending Begin/EndUpdateBatch notification will ensure that the UI
// is correctly refreshed.
PlacesUtils.history.runInBatchMode({runBatched: function(aUserData){}}, null);
PlacesUtils.bookmarks.runInBatchMode({runBatched: function(aUserData){}}, null);
// Notify observers that maintenance tasks are complete
Services.obs.notifyObservers(null, FINISHED_MAINTENANCE_NOTIFICATION_TOPIC, null);
let stmt = DBConn.createAsyncStatement("REINDEX");
stmt.executeAsync({
handleError: PlacesDBUtils._handleError,
handleResult: function () {},
handleCompletion: function (aReason)
{
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
tasks.log("+ The database has been reindexed");
}
else {
tasks.log("- Unable to reindex database");
}
PlacesDBUtils._executeTasks(tasks);
}
});
stmt.finalize();
},
//////////////////////////////////////////////////////////////////////////////
//// Tasks
/**
* Checks integrity but does not try to fix the database through a reindex.
*
* @param [optional] aTasks
* Tasks object to execute.
*/
_checkIntegritySkipReindex: function PDBU__checkIntegritySkipReindex(aTasks)
this.checkIntegrity(aTasks, true),
maintenanceOnIdle: function PDBU_maintenanceOnIdle() {
// bug 431558: preventive maintenance for Places database
/**
* Checks integrity and tries to fix the database through a reindex.
*
* @param [optional] aTasks
* Tasks object to execute.
* @param [optional] aSkipdReindex
* Whether to try to reindex database or not.
*/
checkIntegrity: function PDBU_checkIntegrity(aTasks, aSkipReindex)
{
let tasks = new Tasks(aTasks);
tasks.log("> Integrity check");
// Run a integrity check, but stop at the first error.
let stmt = DBConn.createAsyncStatement("PRAGMA integrity_check(1)");
stmt.executeAsync({
handleError: PlacesDBUtils._handleError,
_corrupt: false,
handleResult: function (aResultSet)
{
let row = aResultSet.getNextRow();
this._corrupt = row.getResultByIndex(0) != "ok";
},
handleCompletion: function (aReason)
{
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
if (this._corrupt) {
tasks.log("- The database is corrupt");
if (aSkipReindex) {
tasks.log("- Unable to fix corruption, database will be replaced on next startup");
Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
tasks.clear();
}
else {
// Try to reindex, this often fixed simple indices corruption.
// We insert from the top of the queue, they will run inverse.
tasks.push(PlacesDBUtils._checkIntegritySkipReindex);
tasks.push(PlacesDBUtils.reindex);
}
}
else {
tasks.log("+ The database is sane");
}
}
else {
tasks.log("- Unable to check database status");
tasks.clear();
}
PlacesDBUtils._executeTasks(tasks);
}
});
stmt.finalize();
},
/**
* Checks data coherence and tries to fix most common errors.
*
* @param [optional] aTasks
* Tasks object to execute.
*/
checkCoherence: function PDBU_checkCoherence(aTasks)
{
let tasks = new Tasks(aTasks);
tasks.log("> Coherence check");
let stmts = this._getBoundCoherenceStatements();
DBConn.executeAsync(stmts, stmts.length, {
handleError: PlacesDBUtils._handleError,
handleResult: function () {},
handleCompletion: function (aReason)
{
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
tasks.log("+ The database is coherent");
}
else {
tasks.log("- Unable to check database coherence");
tasks.clear();
}
PlacesDBUtils._executeTasks(tasks);
}
});
stmts.forEach(function (aStmt) aStmt.finalize());
},
_getBoundCoherenceStatements: function PDBU__getBoundCoherenceStatements()
{
let cleanupStatements = [];
// MOZ_ANNO_ATTRIBUTES
@ -473,168 +676,206 @@ nsPlacesDBUtils.prototype = {
// MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT!
// Used to keep track of last call to handleCompletion
this._statementsRunningCount = cleanupStatements.length;
// Statements are automatically queued-up by mozStorage
cleanupStatements.forEach(function (aStatement) {
aStatement.executeAsync(this);
aStatement.finalize();
}, this);
return cleanupStatements;
},
/**
* This method is only for support purposes, it will run sync and will take
* lot of time on big databases, but can be manually triggered to help
* debugging common issues.
* Tries to vacuum the database.
*
* @param [optional] aTasks
* Tasks object to execute.
*/
checkAndFixDatabase: function PDBU_checkAndFixDatabase(aLogCallback) {
let log = [];
let self = this;
let sep = "- - -";
vacuum: function PDBU_vacuum(aTasks)
{
let tasks = new Tasks(aTasks);
tasks.log("> Vacuum");
function integrity() {
let integrityCheckStmt =
DBConn.createStatement("PRAGMA integrity_check");
log.push("INTEGRITY");
let logIndex = log.length;
while (integrityCheckStmt.executeStep()) {
log.push(integrityCheckStmt.getString(0));
}
integrityCheckStmt.finalize();
log.push(sep);
return log[logIndex] == "ok";
}
let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
DBFile.append("places.sqlite");
tasks.log("Initial database size is " +
parseInt(DBFile.fileSize / 1024) + " KiB");
function vacuum(aVacuumCallback) {
log.push("VACUUM");
let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
let placesDBFile = dirSvc.get("ProfD", Ci.nsILocalFile);
placesDBFile.append("places.sqlite");
log.push("places.sqlite: " + placesDBFile.fileSize + " byte");
log.push(sep);
let stmt = DBConn.createStatement("VACUUM");
stmt.executeAsync({
handleResult: function() {},
handleError: function() {
Cu.reportError("Maintenance VACUUM failed");
},
handleCompletion: function(aReason) {
aVacuumCallback();
}
});
stmt.finalize();
}
let stmt = DBConn.createAsyncStatement("VACUUM");
stmt.executeAsync({
handleError: PlacesDBUtils._handleError,
handleResult: function () {},
function backup() {
log.push("BACKUP");
let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
let profD = dirSvc.get("ProfD", Ci.nsILocalFile);
let placesDBFile = profD.clone();
placesDBFile.append("places.sqlite");
let backupDBFile = profD.clone();
backupDBFile.append("places.sqlite.corrupt");
backupDBFile.createUnique(backupDBFile.NORMAL_FILE_TYPE, 0666);
let backupName = backupDBFile.leafName;
backupDBFile.remove(false);
placesDBFile.copyTo(profD, backupName);
log.push(backupName);
log.push(sep);
}
function reindex() {
log.push("REINDEX");
DBConn.executeSimpleSQL("REINDEX");
log.push(sep);
}
function cleanup() {
log.push("CLEANUP");
self.maintenanceOnIdle()
log.push(sep);
}
function expire() {
log.push("EXPIRE");
// Get expiration component. This will also ensure it has already been
// initialized.
let expiration = Cc["@mozilla.org/places/expiration;1"].
getService(Ci.nsIObserver);
// Get maximum number of unique URIs.
let prefs = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
let limitURIs = prefs.getIntPref(
"places.history.expiration.transient_current_max_pages");
if (limitURIs >= 0)
log.push("Current unique URIs limit: " + limitURIs);
// Force a full expiration step.
expiration.observe(null, "places-debug-start-expiration",
-1 /* expire all expirable entries */);
log.push(sep);
}
function stats() {
log.push("STATS");
let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
let placesDBFile = dirSvc.get("ProfD", Ci.nsILocalFile);
placesDBFile.append("places.sqlite");
log.push("places.sqlite: " + placesDBFile.fileSize + " byte");
let stmt = DBConn.createStatement(
"SELECT name FROM sqlite_master WHERE type = :DBType");
stmt.params["DBType"] = "table";
while (stmt.executeStep()) {
let tableName = stmt.getString(0);
let countStmt = DBConn.createStatement(
"SELECT count(*) FROM " + tableName);
countStmt.executeStep();
log.push(tableName + ": " + countStmt.getInt32(0));
countStmt.finalize();
}
stmt.finalize();
log.push(sep);
}
// First of all execute an integrity check.
let integrityIsGood = integrity();
// If integrity check did fail, we can try to fix the database through
// a reindex.
if (!integrityIsGood) {
// Backup current database.
backup();
// Execute a reindex.
reindex();
// Now check again the integrity.
integrityIsGood = integrity();
}
// If integrity is fine, let's force a maintenance, execute a vacuum and
// get some stats.
if (integrityIsGood) {
cleanup();
expire();
vacuum(function () {
stats();
if (aLogCallback) {
aLogCallback(log);
handleCompletion: function (aReason)
{
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
tasks.log("+ The database has been vacuumed");
let vacuumedDBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
vacuumedDBFile.append("places.sqlite");
tasks.log("Final database size is " +
parseInt(vacuumedDBFile.fileSize / 1024) + " KiB");
}
else {
try {
let console = Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService);
console.logStringMessage(log.join('\n'));
}
catch(ex) {}
tasks.log("- Unable to vacuum database");
tasks.clear();
}
});
}
}
PlacesDBUtils._executeTasks(tasks);
}
});
stmt.finalize();
},
/**
* Forces a full expiration on the database.
*
* @param [optional] aTasks
* Tasks object to execute.
*/
expire: function PDBU_expire(aTasks)
{
let tasks = new Tasks(aTasks);
tasks.log("> Orphans expiration");
let expiration = Cc["@mozilla.org/places/expiration;1"].
getService(Ci.nsIObserver);
Services.obs.addObserver(function (aSubject, aTopic, aData) {
Services.obs.removeObserver(arguments.callee, aTopic);
tasks.log("+ Database cleaned up");
PlacesDBUtils._executeTasks(tasks);
}, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
// Force a full expiration step.
expiration.observe(null, "places-debug-start-expiration", -1);
},
/**
* Collects statistical data on the database.
*
* @param [optional] aTasks
* Tasks object to execute.
*/
stats: function PDBU_stats(aTasks)
{
let tasks = new Tasks(aTasks);
tasks.log("> Statistics");
let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
DBFile.append("places.sqlite");
tasks.log("Database size is " + parseInt(DBFile.fileSize / 1024) + " KiB");
[ "user_version"
, "page_size"
, "cache_size"
, "journal_mode"
, "synchronous"
].forEach(function (aPragma) {
let stmt = DBConn.createStatement("PRAGMA " + aPragma);
stmt.executeStep();
tasks.log(aPragma + " is " + stmt.getString(0));
stmt.finalize();
});
// Get maximum number of unique URIs.
try {
let limitURIs = Services.prefs.getIntPref(
"places.history.expiration.transient_current_max_pages");
tasks.log("History can store a maximum of " + limitURIs + " unique pages");
} catch(ex) {}
let stmt = DBConn.createStatement(
"SELECT name FROM sqlite_master WHERE type = :type");
stmt.params.type = "table";
while (stmt.executeStep()) {
let tableName = stmt.getString(0);
let countStmt = DBConn.createStatement(
"SELECT count(*) FROM " + tableName);
countStmt.executeStep();
tasks.log("Table " + tableName + " has " + countStmt.getInt32(0) + " records");
countStmt.finalize();
}
stmt.reset();
stmt.params.type = "index";
while (stmt.executeStep()) {
tasks.log("Index " + stmt.getString(0));
}
stmt.reset();
stmt.params.type = "trigger";
while (stmt.executeStep()) {
tasks.log("Trigger " + stmt.getString(0));
}
stmt.finalize();
PlacesDBUtils._executeTasks(tasks);
}
};
__defineGetter__("PlacesDBUtils", function() {
delete this.PlacesDBUtils;
return this.PlacesDBUtils = new nsPlacesDBUtils;
});
/**
* LIFO tasks stack.
*
* @param [optional] aTasks
* Array of tasks or another Tasks object to clone.
*/
function Tasks(aTasks)
{
if (Array.isArray(aTasks)) {
this._list = aTasks.slice(0, aTasks.length);
}
else if ("list" in aTasks) {
this._list = aTasks.list;
this._log = aTasks.messages;
this.callback = aTasks.callback;
this.scope = aTasks.scope;
}
}
Tasks.prototype = {
_list: [],
_log: [],
callback: null,
scope: null,
/**
* Adds a task to the top of the list.
*
* @param aNewElt
* Task to be added.
*/
push: function T_push(aNewElt)
{
this._list.unshift(aNewElt);
},
/**
* Returns and consumes next task.
*
* @return next task or undefined if no task is left.
*/
pop: function T_pop() this._list.shift(),
/**
* Removes all tasks.
*/
clear: function T_clear()
{
this._list.length = 0;
},
/**
* Returns array of tasks ordered from the next to be run to the latest.
*/
get list() this._list.slice(0, this._list.length),
/**
* Adds a message to the log.
*
* @param aMsg
* String message to be added.
*/
log: function T_log(aMsg)
{
this._log.push(aMsg);
},
/**
* Returns array of log messages ordered from oldest to newest.
*/
get messages() this._log.slice(0, this._log.length),
}

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

@ -2699,15 +2699,15 @@ PlacesRemoveItemTransaction.prototype = {
txn.doTransaction();
}
else {
// Before removing the bookmark, save its tags.
let tags = this._uri ? PlacesUtils.tagging.getTagsForURI(this._uri) : null;
PlacesUtils.bookmarks.removeItem(this._id);
if (this._uri) {
// if this was the last bookmark (excluding tag-items and livemark
// children, see getMostRecentBookmarkForURI) for the bookmark's url,
// remove the url from tag containers as well.
if (PlacesUtils.getMostRecentBookmarkForURI(this._uri) == -1) {
this._tags = PlacesUtils.tagging.getTagsForURI(this._uri);
PlacesUtils.tagging.untagURI(this._uri, this._tags);
}
// If this was the last bookmark (excluding tag-items and livemark
// children) for this url, persist the tags.
if (tags && PlacesUtils.getMostRecentBookmarkForURI(this._uri) == -1) {
this._tags = tags;
}
}
},

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

@ -118,6 +118,45 @@ SearchBookmarkForKeyword(nsTrimInt64HashKey::KeyType aKey,
return PL_DHASH_NEXT;
}
template<typename Method, typename DataType>
class AsyncGetBookmarksForURI : public AsyncStatementCallback
{
public:
AsyncGetBookmarksForURI(nsNavBookmarks* aBookmarksSvc,
Method aCallback,
DataType aData)
: mBookmarksSvc(aBookmarksSvc)
, mCallback(aCallback)
, mData(aData)
{
nsCOMPtr<mozIStorageStatement> stmt =
aBookmarksSvc->GetStatementById(DB_GET_BOOKMARKS_FOR_URI);
if (stmt) {
(void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aData.uri);
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
(void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
}
}
NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet)
{
nsCOMPtr<mozIStorageRow> row;
while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
nsresult rv = row->GetInt64(0, &mData.itemId);
NS_ENSURE_SUCCESS(rv, rv);
if (mCallback) {
((*mBookmarksSvc).*mCallback)(mData);
}
}
return NS_OK;
}
private:
nsRefPtr<nsNavBookmarks> mBookmarksSvc;
Method mCallback;
DataType mData;
};
} // Anonymous namespace.
@ -2868,8 +2907,24 @@ nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
return mObservers.RemoveWeakElement(aObserver);
}
void
nsNavBookmarks::NotifyItemVisited(ItemVisitData aData)
{
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver,
OnItemVisited(aData.itemId, aData.visitId, aData.time));
}
// nsNavBookmarks::nsINavHistoryObserver
void
nsNavBookmarks::NotifyItemChanged(ItemChangeData aData)
{
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, nsINavBookmarkObserver,
OnItemChanged(aData.itemId, aData.property,
aData.isAnnotation, aData.newValue,
aData.lastModified, aData.itemType));
}
////////////////////////////////////////////////////////////////////////////////
//// nsNavBookmarks::nsINavHistoryObserver
NS_IMETHODIMP
nsNavBookmarks::OnBeginUpdateBatch()
@ -2895,27 +2950,18 @@ nsNavBookmarks::OnEndUpdateBatch()
NS_IMETHODIMP
nsNavBookmarks::OnVisit(nsIURI* aURI, PRInt64 aVisitID, PRTime aTime,
nsNavBookmarks::OnVisit(nsIURI* aURI, PRInt64 aVisitId, PRTime aTime,
PRInt64 aSessionID, PRInt64 aReferringID,
PRUint32 aTransitionType, PRUint32* aAdded)
{
// If the page is bookmarked, we need to notify observers
PRBool bookmarked = PR_FALSE;
IsBookmarked(aURI, &bookmarked);
if (bookmarked) {
// query for all bookmarks for that URI, notify for each
nsTArray<PRInt64> bookmarks;
// If the page is bookmarked, notify observers for each associated bookmark.
ItemVisitData visitData;
visitData.uri = aURI;
visitData.visitId = aVisitId;
visitData.time = aTime;
nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks);
NS_ENSURE_SUCCESS(rv, rv);
if (bookmarks.Length()) {
for (PRUint32 i = 0; i < bookmarks.Length(); i++)
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavBookmarkObserver,
OnItemVisited(bookmarks[i], aVisitID, aTime));
}
}
nsRefPtr< AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData> > notifier =
new AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData>(this, &nsNavBookmarks::NotifyItemVisited, visitData);
return NS_OK;
}
@ -2930,26 +2976,16 @@ nsNavBookmarks::OnBeforeDeleteURI(nsIURI* aURI)
NS_IMETHODIMP
nsNavBookmarks::OnDeleteURI(nsIURI* aURI)
{
// If the page is bookmarked, we need to notify observers
PRBool bookmarked = PR_FALSE;
IsBookmarked(aURI, &bookmarked);
if (bookmarked) {
// query for all bookmarks for that URI, notify for each
nsTArray<PRInt64> bookmarks;
// If the page is bookmarked, notify observers for each associated bookmark.
ItemChangeData changeData;
changeData.uri = aURI;
changeData.property = NS_LITERAL_CSTRING("cleartime");
changeData.isAnnotation = PR_FALSE;
changeData.lastModified = 0;
changeData.itemType = TYPE_BOOKMARK;
nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks);
NS_ENSURE_SUCCESS(rv, rv);
if (bookmarks.Length()) {
for (PRUint32 i = 0; i < bookmarks.Length(); i ++)
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavBookmarkObserver,
OnItemChanged(bookmarks[i],
NS_LITERAL_CSTRING("cleartime"),
PR_FALSE, EmptyCString(), 0,
TYPE_BOOKMARK));
}
}
nsRefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
return NS_OK;
}
@ -2977,6 +3013,14 @@ nsNavBookmarks::OnPageChanged(nsIURI* aURI, PRUint32 aWhat,
{
nsresult rv;
if (aWhat == nsINavHistoryObserver::ATTRIBUTE_FAVICON) {
ItemChangeData changeData;
changeData.uri = aURI;
changeData.property = NS_LITERAL_CSTRING("favicon");
changeData.isAnnotation = PR_FALSE;
changeData.newValue = NS_ConvertUTF16toUTF8(aValue);
changeData.lastModified = 0;
changeData.itemType = TYPE_BOOKMARK;
// Favicons may be set to either pure URIs or to folder URIs
PRBool isPlaceURI;
rv = aURI->SchemeIs("place", &isPlaceURI);
@ -2994,33 +3038,14 @@ nsNavBookmarks::OnPageChanged(nsIURI* aURI, PRUint32 aWhat,
rv = history->QueryStringToQueryArray(spec, &queries, getter_AddRefs(options));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_STATE(queries.Count() == 1);
NS_ENSURE_STATE(queries[0]->Folders().Length() == 1);
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavBookmarkObserver,
OnItemChanged(queries[0]->Folders()[0],
NS_LITERAL_CSTRING("favicon"),
PR_FALSE,
NS_ConvertUTF16toUTF8(aValue),
0, TYPE_BOOKMARK));
if (queries.Count() == 1 && queries[0]->Folders().Length() == 1) {
changeData.itemId = queries[0]->Folders()[0];
NotifyItemChanged(changeData);
}
}
else {
// query for all bookmarks for that URI, notify for each
nsTArray<PRInt64> bookmarks;
rv = GetBookmarkIdsForURITArray(aURI, bookmarks);
NS_ENSURE_SUCCESS(rv, rv);
if (bookmarks.Length()) {
for (PRUint32 i = 0; i < bookmarks.Length(); i ++)
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
nsINavBookmarkObserver,
OnItemChanged(bookmarks[i],
NS_LITERAL_CSTRING("favicon"),
PR_FALSE,
NS_ConvertUTF16toUTF8(aValue),
0, TYPE_BOOKMARK));
}
nsRefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
}
}
return NS_OK;

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

@ -54,8 +54,29 @@ namespace places {
enum BookmarkStatementId {
DB_FIND_REDIRECTED_BOOKMARK = 0
, DB_GET_BOOKMARKS_FOR_URI
};
struct ItemVisitData {
PRInt64 itemId;
nsCOMPtr<nsIURI> uri;
PRInt64 visitId;
PRTime time;
};
struct ItemChangeData {
PRInt64 itemId;
nsCOMPtr<nsIURI> uri;
nsCString property;
PRBool isAnnotation;
nsCString newValue;
PRTime lastModified;
PRUint16 itemType;
};
typedef void (nsNavBookmarks::*ItemVisitMethod)(ItemVisitData);
typedef void (nsNavBookmarks::*ItemChangeMethod)(ItemChangeData);
} // namespace places
} // namespace mozilla
@ -176,10 +197,32 @@ public:
switch(aStatementId) {
case DB_FIND_REDIRECTED_BOOKMARK:
return GetStatement(mDBFindRedirectedBookmark);
case DB_GET_BOOKMARKS_FOR_URI:
return GetStatement(mDBFindURIBookmarks);
}
return nsnull;
}
/**
* Notifies that a bookmark has been visited.
*
* @param aItemId
* The visited item id.
* @param aData
* Details about the new visit.
*/
void NotifyItemVisited(mozilla::places::ItemVisitData aData);
/**
* Notifies that a bookmark has changed.
*
* @param aItemId
* The changed item id.
* @param aData
* Details about the change.
*/
void NotifyItemChanged(mozilla::places::ItemChangeData aData);
private:
static nsNavBookmarks* gBookmarksService;

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

@ -126,6 +126,8 @@ using namespace mozilla::places;
#define PREF_CACHE_TO_MEMORY_PERCENTAGE "database.cache_to_memory_percentage"
#define PREF_FORCE_DATABASE_REPLACEMENT "database.replaceOnStartup"
// Default integer value for PREF_CACHE_TO_MEMORY_PERCENTAGE.
// This is 6% of machine memory, giving 15MB for a user with 256MB of memory.
// Out of this cache, SQLite will use at most the size of the database file.
@ -574,11 +576,9 @@ nsNavHistory::Init()
nsresult
nsNavHistory::InitDBFile(PRBool aForceInit)
{
if (aForceInit) {
NS_ASSERTION(mDBConn,
"When forcing initialization, a database connection must exist!");
NS_ASSERTION(mDBService,
"When forcing initialization, the database service must exist!");
if (!mDBService) {
mDBService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
NS_ENSURE_STATE(mDBService);
}
// Get database file handle.
@ -606,10 +606,12 @@ nsNavHistory::InitDBFile(PRBool aForceInit)
}
// Close database connection if open.
// If there's any not finalized statement or this fails for any reason
// we won't be able to remove the database.
rv = mDBConn->Close();
NS_ENSURE_SUCCESS(rv, rv);
if (mDBConn) {
// If there's any not finalized statement or this fails for any reason
// we won't be able to remove the database.
rv = mDBConn->Close();
NS_ENSURE_SUCCESS(rv, rv);
}
// Remove the broken database.
rv = mDBFile->Remove(PR_FALSE);
@ -636,14 +638,28 @@ nsNavHistory::InitDBFile(PRBool aForceInit)
rv = mDBFile->Exists(&dbExists);
NS_ENSURE_SUCCESS(rv, rv);
// If the database didn't previously exist, we create it.
if (!dbExists)
if (!dbExists) {
mDatabaseStatus = DATABASE_STATUS_CREATE;
}
else {
// Check if maintenance required a database replacement.
PRBool forceDatabaseReplacement;
if (NS_SUCCEEDED(mPrefBranch->GetBoolPref(PREF_FORCE_DATABASE_REPLACEMENT,
&forceDatabaseReplacement)) &&
forceDatabaseReplacement) {
// Be sure to clear the pref to avoid handling it more than once.
rv = mPrefBranch->ClearUserPref(PREF_FORCE_DATABASE_REPLACEMENT);
NS_ENSURE_SUCCESS(rv, rv);
// Re-enter this same method, forcing the replacement.
rv = InitDBFile(PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
}
}
// Open the database file. If it does not exist a new one will be created.
mDBService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Open un unshared connection, both for safety and speed.
// Use a unshared connection, both for safety and performance.
rv = mDBService->OpenUnsharedDatabase(mDBFile, getter_AddRefs(mDBConn));
if (rv == NS_ERROR_FILE_CORRUPTED) {
// The database is corrupt, try to create a new one.
@ -2710,24 +2726,16 @@ nsNavHistory::AddVisit(nsIURI* aURI, PRTime aTime, nsIURI* aReferringURI,
stmt->Reset();
scoper.Abandon();
// embedded links and redirects will be hidden, but don't hide pages that
// are already unhidden.
//
// Note that we test the redirect flag and not for the redirect transition
// type. The transition type refers to how we got here, and whether a page
// is shown does not depend on whether you got to it through a redirect.
// Rather, we want to hide pages that redirect themselves somewhere
// else, which is what the redirect flag means.
//
// note, we want to unhide any hidden pages that the user explicitly types
// (aTransitionType == TRANSITION_TYPED) so that they will appear in
// the history UI (sidebar, history menu, url bar autocomplete, etc)
// Note that we want to unhide any hidden pages that the user explicitly
// types (aTransitionType == TRANSITION_TYPED) so that they will appear in
// the history UI (sidebar, history menu, url bar autocomplete, etc).
// Additionally, we don't want to hide any pages that are already unhidden.
hidden = oldHiddenState;
if (hidden == 1 &&
(!aIsRedirect || aTransitionType == TRANSITION_TYPED) &&
aTransitionType != TRANSITION_EMBED &&
aTransitionType != TRANSITION_FRAMED_LINK)
(!GetHiddenState(aIsRedirect, aTransitionType) ||
aTransitionType == TRANSITION_TYPED)) {
hidden = 0; // unhide
}
typed = (PRInt32)(oldTypedState == 1 || (aTransitionType == TRANSITION_TYPED));

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

@ -0,0 +1,110 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* This test checks that bookmarks service is correctly forwarding async
* events like visit or favicon additions. */
const NOW = Date.now() * 1000;
const ICON_URI = NetUtil.newURI(do_get_file("../unit/favicon-normal32.png"));
let observer = {
bookmarks: [],
observedBookmarks: 0,
visitId: 0,
reset: function ()
{
this.observedBookmarks = 0;
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onItemAdded: function () {},
onBeforeItemRemoved: function () {},
onItemRemoved: function () {},
onItemMoved: function () {},
onItemChanged: function(aItemId, aProperty, aIsAnnotation, aNewValue,
aLastModified, aItemType)
{
do_log_info("Check that we got the correct change information.");
do_check_neq(this.bookmarks.indexOf(aItemId), -1);
if (aProperty == "favicon") {
do_check_false(aIsAnnotation);
do_check_eq(aNewValue, ICON_URI.spec);
do_check_eq(aLastModified, 0);
do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
}
else if (aProperty == "cleartime") {
do_check_false(aIsAnnotation);
do_check_eq(aNewValue, "");
do_check_eq(aLastModified, 0);
do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
}
else {
do_throw("Unexpected property change " + aProperty);
}
if (++this.observedBookmarks == this.bookmarks.length) {
run_next_test();
}
},
onItemVisited: function(aItemId, aVisitId, aTime)
{
do_log_info("Check that we got the correct visit information.");
do_check_neq(this.bookmarks.indexOf(aItemId), -1);
do_check_eq(aVisitId, this.visitId);
do_check_eq(aTime, NOW);
if (++this.observedBookmarks == this.bookmarks.length) {
run_next_test();
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavBookmarkObserver,
])
};
PlacesUtils.bookmarks.addObserver(observer, false);
let gTests = [
function add_visit_test()
{
observer.reset();
// Add a visit to the bookmark and wait for the observer.
observer.visitId =
PlacesUtils.history.addVisit(NetUtil.newURI("http://book.ma.rk/"), NOW, null,
PlacesUtils.history.TRANSITION_TYPED, false, 0);
},
function add_icon_test()
{
observer.reset();
PlacesUtils.favicons.setAndLoadFaviconForPage(NetUtil.newURI("http://book.ma.rk/"),
ICON_URI, true);
},
function remove_page_test()
{
observer.reset();
PlacesUtils.history.removePage(NetUtil.newURI("http://book.ma.rk/"));
},
function cleanup()
{
PlacesUtils.bookmarks.removeObserver(observer, false);
run_next_test();
},
];
function run_test()
{
// Add multiple bookmarks to the same uri.
observer.bookmarks.push(
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
NetUtil.newURI("http://book.ma.rk/"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"Bookmark")
);
observer.bookmarks.push(
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
NetUtil.newURI("http://book.ma.rk/"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"Bookmark")
);
run_next_test();
}

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

@ -50,6 +50,10 @@ const TRANSITION_REDIRECT_PERMANENT = Ci.nsINavHistoryService.TRANSITION_REDIREC
const TRANSITION_REDIRECT_TEMPORARY = Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY;
const TRANSITION_DOWNLOAD = Ci.nsINavHistoryService.TRANSITION_DOWNLOAD;
// This error icon must stay in sync with FAVICON_ERRORPAGE_URL in
// nsIFaviconService.idl, aboutCertError.xhtml and netError.xhtml.
const FAVICON_ERRORPAGE_URL = "chrome://global/skin/icons/warning-16.png";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "Services", function() {
@ -495,8 +499,8 @@ function waitForFrecency(aURI, aValidator, aCallback, aCbScope, aCbArguments) {
/**
* Returns the frecency of a url.
*
* @param aURI
* The URI or spec to get frecency for.
* @param aURI
* The URI or spec to get frecency for.
* @return the frecency value.
*/
function frecencyForUrl(aURI)
@ -507,13 +511,34 @@ function frecencyForUrl(aURI)
);
stmt.bindUTF8StringParameter(0, url);
if (!stmt.executeStep())
throw "No result for frecency.";
throw new Error("No result for frecency.");
let frecency = stmt.getInt32(0);
stmt.finalize();
return frecency;
}
/**
* Returns the hidden status of a url.
*
* @param aURI
* The URI or spec to get hidden for.
* @return @return true if the url is hidden, false otherwise.
*/
function isUrlHidden(aURI)
{
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let stmt = DBConn().createStatement(
"SELECT hidden FROM moz_places WHERE url = ?1"
);
stmt.bindUTF8StringParameter(0, url);
if (!stmt.executeStep())
throw new Error("No result for hidden.");
let hidden = stmt.getInt32(0);
stmt.finalize();
return !!hidden;
}
/**
* Compares two times in usecs, considering eventual platform timers skews.
@ -607,3 +632,37 @@ function do_log_info(aMessage)
{
print("TEST-INFO | " + _TEST_FILE + " | " + aMessage);
}
/**
* Runs the next test in the gTests array. gTests should be a array defined in
* each test file.
*/
let gRunningTest = null;
let gTestIndex = 0; // The index of the currently running test.
function run_next_test()
{
if (gRunningTest !== null) {
// Close the previous test do_test_pending call.
do_test_finished();
}
function _run_next_test()
{
if (gTestIndex < gTests.length) {
do_test_pending();
gRunningTest = gTests[gTestIndex++];
print("TEST-INFO | " + _TEST_FILE + " | Starting " +
gRunningTest.name);
// Exceptions do not kill asynchronous tests, so they'll time out.
try {
gRunningTest();
}
catch (e) {
do_throw(e);
}
}
}
// For sane stacks during failures, we execute this code soon, but not now.
do_execute_soon(_run_next_test);
}

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

@ -305,7 +305,7 @@ function test_final_state()
////////////////////////////////////////////////////////////////////////////////
//// Test Runner
let tests = [
let gTests = [
test_initial_state,
test_moz_bookmarks_guid_exists,
test_bookmark_guids_non_null,
@ -317,34 +317,9 @@ let tests = [
test_place_guid_annotation_removed,
test_final_state,
];
let index = 0;
function run_next_test()
{
function _run_next_test() {
if (index < tests.length) {
do_test_pending();
print("TEST-INFO | " + _TEST_FILE + " | Starting " + tests[index].name);
// Exceptions do not kill asynchronous tests, so they'll time out.
try {
tests[index++]();
}
catch (e) {
do_throw(e);
}
}
do_test_finished();
}
// For sane stacks during failures, we execute this code soon, but not now.
do_execute_soon(_run_next_test);
}
function run_test()
{
setPlacesDatabase("places_v10.sqlite");
do_test_pending();
run_next_test();
}

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

@ -118,40 +118,15 @@ function test_final_state()
////////////////////////////////////////////////////////////////////////////////
//// Test Runner
let tests = [
let gTests = [
test_initial_state,
test_bookmark_guids_non_null,
test_place_guids_non_null,
test_final_state,
];
let index = 0;
function run_next_test()
{
function _run_next_test() {
if (index < tests.length) {
do_test_pending();
print("TEST-INFO | " + _TEST_FILE + " | Starting " + tests[index].name);
// Exceptions do not kill asynchronous tests, so they'll time out.
try {
tests[index++]();
}
catch (e) {
do_throw(e);
}
}
do_test_finished();
}
// For sane stacks during failures, we execute this code soon, but not now.
do_execute_soon(_run_next_test);
}
function run_test()
{
setPlacesDatabase("places_v10_from_v11.sqlite");
do_test_pending();
run_next_test();
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Двоичные данные
toolkit/components/places/tests/unit/default.sqlite Normal file

Двоичный файл не отображается.

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

@ -0,0 +1,28 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests the async history API exposed by mozIAsyncHistory.
*/
////////////////////////////////////////////////////////////////////////////////
//// Test Functions
function test_interface_exists()
{
let history = Cc["@mozilla.org/browser/history;1"].getService(Ci.nsISupports);
do_check_true(history instanceof Ci.mozIAsyncHistory);
run_next_test();
}
////////////////////////////////////////////////////////////////////////////////
//// Test Runner
let gTests = [
test_interface_exists,
];
function run_test()
{
run_next_test();
}

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

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that history initialization correctly handles a request to forcibly
// replace the current database.
function run_test() {
// Ensure that our database doesn't already exist.
let (dbFile = gProfD.clone()) {
dbFile.append("places.sqlite");
do_check_false(dbFile.exists());
}
let (dbFile = gProfD.clone()) {
dbFile.append("places.sqlite.corrupt");
do_check_false(dbFile.exists());
}
let file = do_get_file("default.sqlite");
file.copyTo(gProfD, "places.sqlite");
// Create some unique stuff to check later.
let db = Services.storage.openUnsharedDatabase(file);
db.executeSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)");
db.close();
Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
do_check_eq(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_CORRUPT);
let (dbFile = gProfD.clone()) {
dbFile.append("places.sqlite");
do_check_true(dbFile.exists());
// Check the new database is really a new one.
let db = Services.storage.openUnsharedDatabase(file);
try {
db.executeSimpleSQL("DELETE * FROM test");
do_throw("The new database should not have our unique content");
} catch(ex) {}
db.close();
}
let (dbFile = gProfD.clone()) {
dbFile.append("places.sqlite.corrupt");
do_check_true(dbFile.exists());
}
};

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

@ -78,6 +78,8 @@ var tests = [
},
check: function check1() {
checkAddSucceeded(this.pageURI, this.favicon.mimetype, this.favicon.data);
do_log_info("Check that the added page is marked as hidden.");
do_check_true(isUrlHidden(this.pageURI));
}
},
@ -164,10 +166,13 @@ var historyObserver = {
if (pageURI.equals(tests[currentTestIndex].pageURI)) {
tests[currentTestIndex].check();
currentTestIndex++;
if (currentTestIndex == tests.length)
if (currentTestIndex == tests.length) {
do_test_finished();
else
}
else {
do_log_info(tests[currentTestIndex].desc);
tests[currentTestIndex].go();
}
}
else
do_throw("Received PageChanged for a non-current test!");
@ -192,5 +197,6 @@ function run_test() {
PlacesUtils.history.addObserver(historyObserver, false);
// Start the tests
do_log_info(tests[currentTestIndex].desc);
tests[currentTestIndex].go();
};

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

@ -50,13 +50,13 @@ XPCOMUtils.defineLazyServiceGetter(this, "pb",
let favicons = [
{
uri: uri(do_get_file("favicon-normal16.png")),
uri: NetUtil.newURI(do_get_file("favicon-normal16.png")),
data: readFileData(do_get_file("favicon-normal16.png")),
mimetype: "image/png",
size: 286
},
{
uri: uri(do_get_file("favicon-normal32.png")),
uri: NetUtil.newURI(do_get_file("favicon-normal32.png")),
data: readFileData(do_get_file("favicon-normal32.png")),
mimetype: "image/png",
size: 344
@ -67,7 +67,7 @@ let tests = [
{
desc: "test setAndLoadFaviconForPage for about: URIs",
pageURI: uri("about:test1"),
pageURI: NetUtil.newURI("about:test1"),
go: function go1() {
PlacesUtils.favicons.setAndLoadFaviconForPage(this.pageURI, favicons[0].uri, true);
@ -77,7 +77,7 @@ let tests = [
{
desc: "test setAndLoadFaviconForPage with history disabled",
pageURI: uri("http://test2.bar/"),
pageURI: NetUtil.newURI("http://test2.bar/"),
go: function go2() {
// Temporarily disable history.
Services.prefs.setBoolPref("places.history.enabled", false);
@ -91,7 +91,7 @@ let tests = [
{
desc: "test setAndLoadFaviconForPage in PB mode for non-bookmarked URI",
pageURI: uri("http://test3.bar/"),
pageURI: NetUtil.newURI("http://test3.bar/"),
go: function go3() {
if (!("@mozilla.org/privatebrowsing;1" in Cc))
return;
@ -110,15 +110,27 @@ let tests = [
}
},
{ // This is a valid icon set test, that will cause the closing notification.
desc: "test setAndLoadFaviconForPage for valid history uri",
pageURI: uri("http://test4.bar/"),
{
desc: "test setAndLoadFaviconForPage with error icon",
pageURI: NetUtil.newURI("http://test4.bar/"),
go: function go4() {
PlacesUtils.favicons.setAndLoadFaviconForPage(this.pageURI, favicons[1].uri, true);
PlacesUtils.favicons.setAndLoadFaviconForPage(
this.pageURI, NetUtil.newURI(FAVICON_ERRORPAGE_URL), true
);
},
clean: function clean4() {}
},
{ // This is a valid icon set test, that will cause the closing notification.
desc: "test setAndLoadFaviconForPage for valid history uri",
pageURI: NetUtil.newURI("http://testfinal.bar/"),
go: function goFinal() {
PlacesUtils.favicons.setAndLoadFaviconForPage(this.pageURI, favicons[1].uri, true);
},
clean: function cleanFinal() {}
},
];
let historyObserver = {
@ -140,7 +152,7 @@ let historyObserver = {
//dump_table("moz_favicons");
// Ensure we have been called by the last test.
do_check_true(pageURI.equals(uri("http://test4.bar/")));
do_check_true(pageURI.equals(NetUtil.newURI("http://testfinal.bar/")));
// Ensure there is only one entry in favicons table.
let stmt = DBConn().createStatement(
@ -189,7 +201,7 @@ function runNextTest() {
if (tests.length) {
currentTest = tests.shift();
print(currentTest.desc);
do_log_info(currentTest.desc);
currentTest.go();
// Wait some time before calling the next test, this is needed to avoid
// invoking clean() too early. The first async step should run at least.

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

@ -45,27 +45,15 @@
// Include PlacesDBUtils module
Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
const FINISHED_MAINTANANCE_NOTIFICATION_TOPIC = "places-maintenance-finished";
const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
const FINISHED_MAINTENANCE_NOTIFICATION_TOPIC = "places-maintenance-finished";
// Get services and database connection
let os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
let bh = hs.QueryInterface(Ci.nsIBrowserHistory);
let bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
let ts = Cc["@mozilla.org/browser/tagging-service;1"].
getService(Ci.nsITaggingService);
let as = Cc["@mozilla.org/browser/annotation-service;1"].
getService(Ci.nsIAnnotationService);
let fs = Cc["@mozilla.org/browser/favicon-service;1"].
getService(Ci.nsIFaviconService);
let bundle = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService).
createBundle(PLACES_STRING_BUNDLE_URI);
let hs = PlacesUtils.history;
let bh = PlacesUtils.bhistory;
let bs = PlacesUtils.bookmarks;
let ts = PlacesUtils.tagging;
let as = PlacesUtils.annotations;
let fs = PlacesUtils.favicons;
let mDBConn = hs.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
@ -321,13 +309,13 @@ tests.push({
// Ensure all roots titles are correct.
do_check_eq(bs.getItemTitle(bs.placesRoot), "");
do_check_eq(bs.getItemTitle(bs.bookmarksMenuFolder),
bundle.GetStringFromName("BookmarksMenuFolderTitle"));
PlacesUtils.getString("BookmarksMenuFolderTitle"));
do_check_eq(bs.getItemTitle(bs.tagsFolder),
bundle.GetStringFromName("TagsFolderTitle"));
PlacesUtils.getString("TagsFolderTitle"));
do_check_eq(bs.getItemTitle(bs.unfiledBookmarksFolder),
bundle.GetStringFromName("UnsortedBookmarksFolderTitle"));
PlacesUtils.getString("UnsortedBookmarksFolderTitle"));
do_check_eq(bs.getItemTitle(bs.toolbarFolder),
bundle.GetStringFromName("BookmarksToolbarFolderTitle"));
PlacesUtils.getString("BookmarksToolbarFolderTitle"));
}
});
@ -1178,10 +1166,15 @@ tests.push({
let observer = {
observe: function(aSubject, aTopic, aData) {
if (aTopic == FINISHED_MAINTANANCE_NOTIFICATION_TOPIC) {
if (aTopic == FINISHED_MAINTENANCE_NOTIFICATION_TOPIC) {
// Check the lastMaintenance time has been saved.
do_check_neq(Services.prefs.getIntPref("places.database.lastMaintenance"), null);
try {current_test.check();}
catch (ex){ do_throw(ex);}
cleanDatabase();
if (tests.length) {
current_test = tests.shift();
dump("\nExecuting test: " + current_test.name + "\n" + "*** " + current_test.desc + "\n");
@ -1189,7 +1182,7 @@ let observer = {
PlacesDBUtils.maintenanceOnIdle();
}
else {
os.removeObserver(this, FINISHED_MAINTANANCE_NOTIFICATION_TOPIC);
Services.obs.removeObserver(this, FINISHED_MAINTENANCE_NOTIFICATION_TOPIC);
// Sanity check: all roots should be intact
do_check_eq(bs.getFolderIdForItem(bs.placesRoot), 0);
do_check_eq(bs.getFolderIdForItem(bs.bookmarksMenuFolder), bs.placesRoot);
@ -1201,7 +1194,7 @@ let observer = {
}
}
}
os.addObserver(observer, FINISHED_MAINTANANCE_NOTIFICATION_TOPIC, false);
Services.obs.addObserver(observer, FINISHED_MAINTENANCE_NOTIFICATION_TOPIC, false);
// main

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

@ -46,23 +46,36 @@ Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
function run_test() {
do_test_pending();
PlacesDBUtils.checkAndFixDatabase(function(aLog) {
// Integrity should be good.
do_check_eq(aLog[1], "ok");
let sections = [];
let positives = [];
let negatives = [];
let infos = [];
// Check we are not wrongly executing bad integrity tasks.
const goodMsgs = ["INTEGRITY", "EXPIRE", "VACUUM", "CLEANUP", "STATS"];
const badMsgs = ["BACKUP", "REINDEX"];
aLog.forEach(function (aLogMsg) {
print (aLogMsg);
do_check_eq(badMsgs.indexOf(aLogMsg), -1);
let index = goodMsgs.indexOf(aLogMsg);
if (index != -1)
goodMsgs.splice(index, 1);
aLog.forEach(function (aMsg) {
print (aMsg);
switch (aMsg.substr(0, 1)) {
case "+":
positives.push(aMsg);
break;
case "-":
negatives.push(aMsg);
break;
case ">":
sections.push(aMsg);
break;
default:
infos.push(aMsg);
}
});
// Check we have run all tasks.
do_check_eq(goodMsgs.length, 0);
print("Check that we have run all sections.");
do_check_eq(sections.length, 5);
print("Check that we have no negatives.");
do_check_false(!!negatives.length);
print("Check that we have positives.");
do_check_true(!!positives.length);
print("Check that we have info.");
do_check_true(!!infos.length);
do_test_finished();
});

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

@ -14,7 +14,7 @@
*/
function check_invariants(aGuid)
{
print("TEST-INFO | " + tests[index - 1].name + " | Checking guid '" +
print("TEST-INFO | " + gRunningTest.name + " | Checking guid '" +
aGuid + "'");
do_check_valid_places_guid(aGuid);
@ -98,37 +98,12 @@ function test_guid_on_background()
////////////////////////////////////////////////////////////////////////////////
//// Test Runner
let tests = [
let gTests = [
test_guid_invariants,
test_guid_on_background,
];
let index = 0;
function run_next_test()
{
function _run_next_test() {
if (index < tests.length) {
do_test_pending();
print("TEST-INFO | " + _TEST_FILE + " | Starting " + tests[index].name);
// Exceptions do not kill asynchronous tests, so they'll time out.
try {
tests[index++]();
}
catch (e) {
do_throw(e);
}
}
do_test_finished();
}
// For sane stacks during failures, we execute this code soon, but not now.
do_execute_soon(_run_next_test);
}
function run_test()
{
do_test_pending();
run_next_test();
}