зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to Places.
This commit is contained in:
Коммит
5456e6f495
|
@ -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();
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Двоичный файл не отображается.
|
@ -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();
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче