diff --git a/browser/components/places/content/places.xul b/browser/components/places/content/places.xul
index 2ef7b958ce5..157c6d42eb9 100755
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -398,10 +398,10 @@
-
-
+
+
+
+
diff --git a/browser/components/places/content/treeView.js b/browser/components/places/content/treeView.js
index 8507545bb9e..d2e1558c38c 100644
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -387,7 +387,35 @@ PlacesTreeView.prototype = {
aShowThisOne.value = aTop.time < aNext.time;
return true;
},
+
+ _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) {
+ var timeInMilliseconds = aTime / 1000; // PRTime is in microseconds
+ var timeObj = new Date(timeInMilliseconds);
+ // Check if it is today and only display the time. Only bother
+ // checking for today if it's within the last 24 hours, since
+ // computing midnight is not really cheap. Sometimes we may get dates
+ // in the future, so always show those.
+ var ago = new Date(Date.now() - timeInMilliseconds);
+ var dateFormat = Ci.nsIScriptableDateFormat.dateFormatShort;
+ if (ago > -10000 && ago < (1000 * 24 * 60 * 60)) {
+ var midnight = new Date(timeInMilliseconds);
+ midnight.setHours(0);
+ midnight.setMinutes(0);
+ midnight.setSeconds(0);
+ midnight.setMilliseconds(0);
+
+ if (timeInMilliseconds > midnight.getTime())
+ dateFormat = Ci.nsIScriptableDateFormat.dateFormatNone;
+ }
+
+ return (this._dateService.FormatDateTime("", dateFormat,
+ Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
+ timeObj.getFullYear(), timeObj.getMonth() + 1,
+ timeObj.getDate(), timeObj.getHours(),
+ timeObj.getMinutes(), timeObj.getSeconds()));
+ },
+
COLUMN_TYPE_UNKNOWN: 0,
COLUMN_TYPE_TITLE: 1,
COLUMN_TYPE_URI: 2,
@@ -395,6 +423,8 @@ PlacesTreeView.prototype = {
COLUMN_TYPE_VISITCOUNT: 4,
COLUMN_TYPE_KEYWORD: 5,
COLUMN_TYPE_DESCRIPTION: 6,
+ COLUMN_TYPE_DATEADDED: 7,
+ COLUMN_TYPE_LASTMODIFIED: 8,
_getColumnType: function PTV__getColumnType(aColumn) {
switch (aColumn.id) {
@@ -410,6 +440,10 @@ PlacesTreeView.prototype = {
return this.COLUMN_TYPE_KEYWORD;
case "description":
return this.COLUMN_TYPE_DESCRIPTION;
+ case "dateAdded":
+ return this.COLUMN_TYPE_DATEADDED;
+ case "lastModified":
+ return this.COLUMN_TYPE_LASTMODIFIED;
}
return this.COLUMN_TYPE_UNKNOWN;
},
@@ -421,9 +455,9 @@ PlacesTreeView.prototype = {
case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING:
return [this.COLUMN_TYPE_TITLE, true];
case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING:
- return [this.COLUMN_TYPE_DATA, false];
+ return [this.COLUMN_TYPE_DATE, false];
case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING:
- return [this.COLUMN_TYPE_DATA, true];
+ return [this.COLUMN_TYPE_DATE, true];
case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
return [this.COLUMN_TYPE_URI, false];
case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
@@ -443,6 +477,14 @@ PlacesTreeView.prototype = {
case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING:
if (this._result.sortingAnnotation == DESCRIPTION_ANNO)
return [this.COLUMN_TYPE_DESCRIPTION, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING:
+ return [this.COLUMN_TYPE_DATEADDED, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING:
+ return [this.COLUMN_TYPE_DATEADDED, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING:
+ return [this.COLUMN_TYPE_LASTMODIFIED, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING:
+ return [this.COLUMN_TYPE_LASTMODIFIED, true];
}
return [this.COLUMN_TYPE_UNKNOWN, false];
},
@@ -976,7 +1018,6 @@ PlacesTreeView.prototype = {
case this.COLUMN_TYPE_URI:
if (PlacesUtils.nodeIsURI(node))
return node.uri;
-
return "";
case this.COLUMN_TYPE_DATE:
if (node.time == 0 || !PlacesUtils.nodeIsURI(node)) {
@@ -987,33 +1028,8 @@ PlacesTreeView.prototype = {
// Only show this for URI-based items.
return "";
}
- if (this._getRowSessionStatus(aRow) != this.SESSION_STATUS_CONTINUE) {
- var nodeTime = node.time / 1000; // PRTime is in microseconds
- var nodeTimeObj = new Date(nodeTime);
-
- // Check if it is today and only display the time. Only bother
- // checking for today if it's within the last 24 hours, since
- // computing midnight is not really cheap. Sometimes we may get dates
- // in the future, so always show those.
- var ago = new Date(Date.now() - nodeTime);
- var dateFormat = Ci.nsIScriptableDateFormat.dateFormatShort;
- if (ago > -10000 && ago < (1000 * 24 * 60 * 60)) {
- var midnight = new Date(nodeTime);
- midnight.setHours(0);
- midnight.setMinutes(0);
- midnight.setSeconds(0);
- midnight.setMilliseconds(0);
-
- if (nodeTime > midnight.getTime())
- dateFormat = Ci.nsIScriptableDateFormat.dateFormatNone;
- }
-
- return (this._dateService.FormatDateTime("", dateFormat,
- Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
- nodeTimeObj.getFullYear(), nodeTimeObj.getMonth() + 1,
- nodeTimeObj.getDate(), nodeTimeObj.getHours(),
- nodeTimeObj.getMinutes(), nodeTimeObj.getSeconds()));
- }
+ if (this._getRowSessionStatus(aRow) != this.SESSION_STATUS_CONTINUE)
+ return this._convertPRTimeToString(node.time);
return "";
case this.COLUMN_TYPE_VISITCOUNT:
return node.accessCount;
@@ -1025,7 +1041,14 @@ PlacesTreeView.prototype = {
const annos = PlacesUtils.annotations;
if (annos.itemHasAnnotation(node.itemId, DESCRIPTION_ANNO))
return annos.getItemAnnotationString(node.itemId, DESCRIPTION_ANNO)
-
+ return "";
+ case this.COLUMN_TYPE_DATEADDED:
+ if (node.dateAdded)
+ return this._convertPRTimeToString(node.dateAdded);
+ return "";
+ case this.COLUMN_TYPE_LASTMODIFIED:
+ if (node.lastModified)
+ return this._convertPRTimeToString(node.lastModified);
return "";
}
return "";
@@ -1161,6 +1184,26 @@ PlacesTreeView.prototype = {
newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING;
newSortingAnnotation = DESCRIPTION_ANNO;
}
+ break;
+ case this.COLUMN_TYPE_DATEADDED:
+ if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING)
+ newSort = NHQO.SORT_BY_DATEADDED_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_DATEADDED_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_LASTMODIFIED:
+ if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING)
+ newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
+
break;
default:
throw Cr.NS_ERROR_INVALID_ARG;
diff --git a/browser/components/places/src/nsPlacesImportExportService.cpp b/browser/components/places/src/nsPlacesImportExportService.cpp
index f53da48567c..c34c374fd5f 100644
--- a/browser/components/places/src/nsPlacesImportExportService.cpp
+++ b/browser/components/places/src/nsPlacesImportExportService.cpp
@@ -63,7 +63,7 @@
* ICON_URI is new for places bookmarks.html, it refers to the original
* URI of the favicon so we don't have to make up favicon URLs.
* Text of the container is the name of the bookmark
- * Ignored: ADD_DATE, LAST_VISIT, LAST_MODIFIED, ID
+ * Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2)
* Bookmark comment := dd
* This affects the previosly added bookmark
* Separator := hr
@@ -116,10 +116,12 @@ static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
#define KEY_ICON_URI_LOWER "icon_uri"
#define KEY_SHORTCUTURL_LOWER "shortcuturl"
#define KEY_POST_DATA_LOWER "post_data"
-#define KEY_ID_LOWER "id"
+#define KEY_ITEM_ID_LOWER "item_id"
#define KEY_NAME_LOWER "name"
#define KEY_MICSUM_GEN_URI_LOWER "micsum_gen_uri"
-#define KEY_GENERATED_TITLE "generated_title"
+#define KEY_GENERATED_TITLE_LOWER "generated_title"
+#define KEY_DATE_ADDED_LOWER "add_date"
+#define KEY_LAST_MODIFIED_LOWER "last_modified"
#define LOAD_IN_SIDEBAR_ANNO NS_LITERAL_CSTRING("bookmarkProperties/loadInSidebar")
#define DESCRIPTION_ANNO NS_LITERAL_CSTRING("bookmarkProperties/description")
@@ -149,7 +151,10 @@ public:
mContainerID(aID),
mContainerNesting(0),
mLastContainerType(Container_Normal),
- mInDescription(PR_FALSE)
+ mInDescription(PR_FALSE),
+ mPreviousId(0),
+ mPreviousDateAdded(0),
+ mPreviousLastModifiedDate(0)
{
}
@@ -217,6 +222,11 @@ public:
// Contains the id of an imported, or newly created bookmark.
PRInt64 mPreviousId;
+
+ // Contains the date-added and last-modified-date of an imported item.
+ // Used to override the values set by insertItem, createFolder, etc.
+ PRTime mPreviousDateAdded;
+ PRTime mPreviousLastModifiedDate;
};
/**
@@ -392,6 +402,11 @@ protected:
NS_ASSERTION(mFrames.Length() > 0, "Asking for frame when there are none!");
return mFrames[mFrames.Length() - 1];
}
+ BookmarkImportFrame& PreviousFrame()
+ {
+ NS_ASSERTION(mFrames.Length() > 1, "Asking for frame when there are not enough!");
+ return mFrames[mFrames.Length() - 2];
+ }
nsresult NewFrame();
nsresult PopFrame();
@@ -400,6 +415,7 @@ protected:
nsresult SetFaviconForFolder(PRInt64 aFolder, const nsACString& aFavicon);
PRInt64 ConvertImportedIdToInternalId(const nsCString& aId);
+ PRTime ConvertImportedDateToInternalDate(const nsACString& aDate);
#ifdef DEBUG_IMPORT
// prints spaces for indenting to the current frame depth
@@ -493,13 +509,15 @@ BookmarkContentSink::OpenContainer(const nsIParserNode& aNode)
NS_IMETHODIMP
BookmarkContentSink::CloseContainer(const nsHTMLTag aTag)
{
+ BookmarkImportFrame& frame = CurFrame();
+
// see the comment for the definition of mInDescription. Basically, we commit
// any text in mPreviousText to the description of the node/folder if there
// is any.
- BookmarkImportFrame& frame = CurFrame();
if (frame.mInDescription) {
frame.mPreviousText.Trim(kWhitespace); // important!
if (!frame.mPreviousText.IsEmpty()) {
+
PRInt64 itemId = !frame.mPreviousLink ?
frame.mContainerID : frame.mPreviousId;
@@ -513,6 +531,29 @@ BookmarkContentSink::CloseContainer(const nsHTMLTag aTag)
nsIAnnotationService::EXPIRE_NEVER);
}
frame.mPreviousText.Truncate();
+
+ // Set last-modified a 2nd time for all items with descriptions
+ // we need to set last-modified as the *last* step in processing
+ // any item type in the bookmarks.html file, so that we do
+ // not overwrite the imported value. for items without descriptions,
+ // setting this value after setting the item title is that
+ // last point at which we can save this value before it gets reset.
+ // for items with descriptions, it must set after that point.
+ // however, at the point at which we set the title, there's no way
+ // to determine if there will be a description following,
+ // so we need to set the last-modified-date at both places.
+
+ PRTime lastModified;
+ if (!frame.mPreviousLink) {
+ lastModified = PreviousFrame().mPreviousLastModifiedDate;
+ } else {
+ lastModified = frame.mPreviousLastModifiedDate;
+ }
+
+ if (itemId > 0 && lastModified > 0) {
+ rv = mBookmarksService->SetItemLastModified(itemId, lastModified);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemLastModified failed");
+ }
}
frame.mInDescription = PR_FALSE;
}
@@ -693,9 +734,15 @@ BookmarkContentSink::HandleHeadBegin(const nsIParserNode& node)
} else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) {
frame.mLastContainerType = BookmarkImportFrame::Container_Places;
break;
- } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_ID_LOWER)) {
+ } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_ITEM_ID_LOWER)) {
frame.mLastContainerId =
ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(node.GetValueAt(i)));
+ } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_DATE_ADDED_LOWER)) {
+ frame.mPreviousDateAdded =
+ ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i)));
+ } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_LAST_MODIFIED_LOWER)) {
+ frame.mPreviousLastModifiedDate =
+ ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i)));
}
}
}
@@ -737,7 +784,7 @@ BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
// mPreviousText will hold our link text, clear it so that can be appended to
frame.mPreviousText.Truncate();
-
+
// get the attributes we care about
nsAutoString href;
nsAutoString feedUrl;
@@ -747,9 +794,12 @@ BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
nsAutoString keyword;
nsAutoString postData;
nsAutoString webPanel;
- nsAutoString id;
+ nsAutoString itemId;
nsAutoString micsumGenURI;
nsAutoString generatedTitle;
+ nsAutoString dateAdded;
+ nsAutoString lastModified;
+
PRInt32 attrCount = node.GetAttributeCount();
for (PRInt32 i = 0; i < attrCount; i ++) {
const nsAString& key = node.GetKeyAt(i);
@@ -769,12 +819,16 @@ BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
postData = node.GetValueAt(i);
} else if (key.LowerCaseEqualsLiteral(KEY_WEB_PANEL_LOWER)) {
webPanel = node.GetValueAt(i);
- } else if (key.LowerCaseEqualsLiteral(KEY_ID_LOWER)) {
- id = node.GetValueAt(i);
+ } else if (key.LowerCaseEqualsLiteral(KEY_ITEM_ID_LOWER)) {
+ itemId = node.GetValueAt(i);
} else if (key.LowerCaseEqualsLiteral(KEY_MICSUM_GEN_URI_LOWER)) {
micsumGenURI = node.GetValueAt(i);
- } else if (key.LowerCaseEqualsLiteral(KEY_GENERATED_TITLE)) {
+ } else if (key.LowerCaseEqualsLiteral(KEY_GENERATED_TITLE_LOWER)) {
generatedTitle = node.GetValueAt(i);
+ } else if (key.LowerCaseEqualsLiteral(KEY_DATE_ADDED_LOWER)) {
+ dateAdded = node.GetValueAt(i);
+ } else if (key.LowerCaseEqualsLiteral(KEY_LAST_MODIFIED_LOWER)) {
+ lastModified = node.GetValueAt(i);
}
}
href.Trim(kWhitespace);
@@ -785,9 +839,11 @@ BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
keyword.Trim(kWhitespace);
postData.Trim(kWhitespace);
webPanel.Trim(kWhitespace);
- id.Trim(kWhitespace);
+ itemId.Trim(kWhitespace);
micsumGenURI.Trim(kWhitespace);
generatedTitle.Trim(kWhitespace);
+ dateAdded.Trim(kWhitespace);
+ lastModified.Trim(kWhitespace);
// For feeds, get the feed URL. If it is invalid, it will leave mPreviousFeed
// NULL and we'll continue trying to create it as a normal bookmark.
@@ -816,8 +872,13 @@ BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
}
}
- // if there's a pre-existing Places bookmark id, use it
- frame.mPreviousId = ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(id));
+ // if there's a pre-existing Places bookmark ITEM_ID, use it
+ frame.mPreviousId = ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(itemId));
+
+ // Save last-modified-date, for setting after all the bookmark properties have been set.
+ if (!lastModified.IsEmpty()) {
+ frame.mPreviousLastModifiedDate = ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(lastModified));
+ }
// if there is a feedURL, this is a livemark, which is a special case
// that we handle in HandleLinkEnd(): don't create normal bookmarks
@@ -838,6 +899,17 @@ BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
rv = mBookmarksService->InsertItem(frame.mContainerID, frame.mPreviousLink,
mBookmarksService->DEFAULT_INDEX, &frame.mPreviousId);
NS_ASSERTION(NS_SUCCEEDED(rv), "InsertItem failed");
+
+ // set the date added value, if we have it
+ // important: this has to happen after InsertItem
+ // so that we set the imported value
+ if (!dateAdded.IsEmpty()) {
+ PRTime convertedDateAdded = ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(dateAdded));
+ if (convertedDateAdded) {
+ rv = mBookmarksService->SetItemDateAdded(frame.mPreviousId, convertedDateAdded);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemDateAdded failed");
+ }
+ }
}
// save the favicon, ignore errors
@@ -940,11 +1012,6 @@ BookmarkContentSink::HandleLinkEnd()
}
if (!isLivemark) {
-#ifdef DEBUG_IMPORT
- PrintNesting();
- printf("Creating livemark '%s' %lld\n",
- NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId);
-#endif
if (mIsImportDefaults) {
rv = mLivemarkService->CreateLivemarkFolderOnly(mBookmarksService,
frame.mContainerID,
@@ -963,16 +1030,32 @@ BookmarkContentSink::HandleLinkEnd()
&frame.mPreviousId);
NS_ASSERTION(NS_SUCCEEDED(rv), "CreateLivemark failed!");
}
+#ifdef DEBUG_IMPORT
+ PrintNesting();
+ printf("Creating livemark '%s' %lld\n",
+ NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId);
+#endif
}
}
else if (frame.mPreviousLink) {
#ifdef DEBUG_IMPORT
PrintNesting();
- printf("Creating bookmark '%s'\n",
- NS_ConvertUTF16toUTF8(frame.mPreviousText).get());
+ printf("Creating bookmark '%s' %lld\n",
+ NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId);
#endif
mBookmarksService->SetItemTitle(frame.mPreviousId, frame.mPreviousText);
}
+
+ // Set last-modified-date for bookmarks and livemarks here so that the
+ // imported date overrides the date from the call to that sets the description
+ // that we made above.
+ if (frame.mPreviousId > 0 && frame.mPreviousLastModifiedDate > 0) {
+ rv = mBookmarksService->SetItemLastModified(frame.mPreviousId, frame.mPreviousLastModifiedDate);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemLastModified failed");
+ // Note: don't clear mPreviousLastModifiedDate, because if this item has a
+ // description, we'll need to set it again.
+ }
+
frame.mPreviousText.Truncate();
}
@@ -987,17 +1070,17 @@ BookmarkContentSink::HandleSeparator(const nsIParserNode& aNode)
// check for pre-existing id
PRInt64 id = 0;
- nsAutoString idAttr;
+ nsAutoString itemIdAttr;
PRInt32 attrCount = aNode.GetAttributeCount();
for (PRInt32 i = 0; i < attrCount; i ++) {
const nsAString& key = aNode.GetKeyAt(i);
- if (key.LowerCaseEqualsLiteral(KEY_ID_LOWER)) {
- idAttr = aNode.GetValueAt(i);
+ if (key.LowerCaseEqualsLiteral(KEY_ITEM_ID_LOWER)) {
+ itemIdAttr = aNode.GetValueAt(i);
}
}
- idAttr.Trim(kWhitespace);
- id = ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(idAttr));
+ itemIdAttr.Trim(kWhitespace);
+ id = ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(itemIdAttr));
// check id validity
if (id > 0) {
@@ -1040,6 +1123,11 @@ BookmarkContentSink::HandleSeparator(const nsIParserNode& aNode)
if (!name.IsEmpty())
mBookmarksService->SetItemTitle(itemId, name);
+
+ // Note: we do not need to import ADD_DATE or LAST_MODIFIED for separators
+ // because pre-Places bookmarks does not support them.
+ // and we can't write them out because attributes other than NAME make Firefox 2.x
+ // crash/hang - see bug #381129
}
}
@@ -1142,6 +1230,18 @@ BookmarkContentSink::NewFrame()
printf("\n");
#endif
+ BookmarkImportFrame& frame = CurFrame();
+ if (frame.mPreviousDateAdded > 0) {
+ nsresult rv = mBookmarksService->SetItemDateAdded(ourID, frame.mPreviousDateAdded);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemDateAdded failed");
+ frame.mPreviousDateAdded = 0;
+ }
+ if (frame.mPreviousLastModifiedDate > 0) {
+ nsresult rv = mBookmarksService->SetItemLastModified(ourID, frame.mPreviousLastModifiedDate);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemLastModified failed");
+ // don't clear last-modified, in case there's a description
+ }
+
if (!mFrames.AppendElement(BookmarkImportFrame(ourID)))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
@@ -1211,7 +1311,7 @@ BookmarkContentSink::SetFaviconForURI(nsIURI* aPageURI, nsIURI* aIconURI,
faviconSpec.AssignLiteral("http://www.mozilla.org/2005/made-up-favicon/");
faviconSpec.AppendInt(serialNumber);
faviconSpec.AppendLiteral("-");
- faviconSpec.AppendInt(PR_Now());
+ faviconSpec.AppendInt(PR_Now()); // casting from PRInt64 -> PRInt32, data loss, fix me
rv = NS_NewURI(getter_AddRefs(faviconURI), faviconSpec);
NS_ENSURE_SUCCESS(rv, rv);
serialNumber ++;
@@ -1295,11 +1395,11 @@ BookmarkContentSink::SetFaviconForFolder(PRInt64 aFolder,
return faviconService->SetFaviconUrlForPage(folderURI, faviconURI);
}
-// Converts a string id (legacy rdf or contemporary) into an int id
+// Converts a string id (ITEM_ID) into an int id
PRInt64
BookmarkContentSink::ConvertImportedIdToInternalId(const nsCString& aId) {
PRInt64 intId = 0;
- if (aId.IsEmpty() || Substring(aId, 0, 4).Equals(NS_LITERAL_CSTRING("rdf:"), CaseInsensitiveCompare))
+ if (aId.IsEmpty())
return intId;
nsresult rv;
intId = aId.ToInteger(&rv);
@@ -1308,6 +1408,22 @@ BookmarkContentSink::ConvertImportedIdToInternalId(const nsCString& aId) {
return intId;
}
+// Converts a string date in seconds to an int date in microseconds
+PRTime
+BookmarkContentSink::ConvertImportedDateToInternalDate(const nsACString& aDate) {
+ PRTime convertedDate = 0;
+ if (!aDate.IsEmpty()) {
+ nsresult rv;
+ convertedDate = aDate.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ convertedDate *= 1000000; // in bookmarks.html this value is in seconds, not microseconds
+ }
+ else {
+ convertedDate = 0;
+ }
+ }
+ return convertedDate;
+}
// SyncChannelStatus
//
@@ -1363,9 +1479,11 @@ static const char kFeedURIAttribute[] = " FEEDURL=\"";
static const char kWebPanelAttribute[] = " WEB_PANEL=\"true\"";
static const char kKeywordAttribute[] = " SHORTCUTURL=\"";
static const char kPostDataAttribute[] = " POST_DATA=\"";
-static const char kIdAttribute[] = " ID=\"";
+static const char kItemIdAttribute[] = " ITEM_ID=\"";
static const char kNameAttribute[] = " NAME=\"";
-static const char kMicsumGenURIEquals[] = " MICSUM_GEN_URI=\"";
+static const char kMicsumGenURIAttribute[] = " MICSUM_GEN_URI=\"";
+static const char kDateAddedAttribute[] = " ADD_DATE=\"";
+static const char kLastModifiedAttribute[] = " LAST_MODIFIED=\"";
// WriteContainerPrologue
//
@@ -1491,6 +1609,27 @@ WriteFaviconAttribute(const nsACString& aURI, nsIOutputStream* aOutput)
return NS_OK;
}
+// WriteDateAttribute
+//
+// This writes the '{attr value=}"{time in seconds}"' attribute for
+// an item.
+
+static nsresult
+WriteDateAttribute(const char aAttributeStart[], PRInt32 aLength, PRTime aAttributeValue, nsIOutputStream* aOutput)
+{
+ // write attribute start
+ PRUint32 dummy;
+ nsresult rv = aOutput->Write(aAttributeStart, aLength, &dummy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // write attribute value
+ nsCAutoString dateInSeconds;
+ aAttributeValue/= 1000000; // in bookmarks.html this value is in seconds, not microseconds
+ dateInSeconds.AppendInt(aAttributeValue); // casting from PRInt64 -> PRInt32, data loss, fix me
+ rv = aOutput->Write(dateInSeconds.get(), dateInSeconds.Length(), &dummy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
+}
+
// nsPlacesImportExportService::WriteContainer
//
@@ -1537,6 +1676,26 @@ nsPlacesImportExportService::WriteContainerHeader(PRInt64 aFolder, const nsACStr
rv = aOutput->Write(kContainerIntro, sizeof(kContainerIntro)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
+ // write ADD_DATE
+ PRTime dateAdded = 0;
+ rv = mBookmarksService->GetItemDateAdded(aFolder, &dateAdded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dateAdded) {
+ rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // write LAST_MODIFIED
+ PRTime lastModified = 0;
+ rv = mBookmarksService->GetItemLastModified(aFolder, &lastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (lastModified) {
+ rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
PRInt64 placesRoot;
rv = mBookmarksService->GetPlacesRoot(&placesRoot);
NS_ENSURE_SUCCESS(rv,rv);
@@ -1560,13 +1719,13 @@ nsPlacesImportExportService::WriteContainerHeader(PRInt64 aFolder, const nsACStr
rv = aOutput->Write(kToolbarFolderAttribute, sizeof(kToolbarFolderAttribute)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
}
-
- // id
- rv = aOutput->Write(kIdAttribute, sizeof(kIdAttribute)-1, &dummy);
+
+ // write id as ITEM_ID as non-rdf IDs can confuse Firefox 2
+ rv = aOutput->Write(kItemIdAttribute, sizeof(kItemIdAttribute)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
- nsCAutoString id;
- id.AppendInt(aFolder);
- rv = aOutput->Write(id.get(), id.Length(), &dummy);
+ nsCAutoString itemIdAttr;
+ itemIdAttr.AppendInt(aFolder); // casting from PRInt64 -> PRInt32, data loss, fix me
+ rv = aOutput->Write(itemIdAttr.get(), itemIdAttr.Length(), &dummy);
NS_ENSURE_SUCCESS(rv, rv);
rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1692,6 +1851,26 @@ nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem,
rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
+ // write ADD_DATE
+ PRTime dateAdded = 0;
+ rv = aItem->GetDateAdded(&dateAdded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dateAdded) {
+ rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // write LAST_MODIFIED
+ PRTime lastModified = 0;
+ rv = aItem->GetLastModified(&lastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (lastModified) {
+ rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
// ' ICON="..."'
rv = WriteFaviconAttribute(uri, aOutput);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1701,12 +1880,12 @@ nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem,
rv = aItem->GetItemId(&itemId);
NS_ENSURE_SUCCESS(rv, rv);
- // write id
- rv = aOutput->Write(kIdAttribute, sizeof(kIdAttribute)-1, &dummy);
+ // write id as ITEM_ID as non-rdf IDs can confuse Firefox 2
+ rv = aOutput->Write(kItemIdAttribute, sizeof(kItemIdAttribute)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
- nsCAutoString id;
- id.AppendInt(itemId);
- rv = aOutput->Write(id.get(), id.Length(), &dummy);
+ nsCAutoString itemIdAttr;
+ itemIdAttr.AppendInt(itemId); // casting from PRInt64 -> PRInt32, data loss, fix me
+ rv = aOutput->Write(itemIdAttr.get(), itemIdAttr.Length(), &dummy);
NS_ENSURE_SUCCESS(rv, rv);
rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1775,7 +1954,7 @@ nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem,
NS_ENSURE_SUCCESS(rv, rv);
// write out generator URI
- rv = aOutput->Write(kMicsumGenURIEquals, sizeof(kMicsumGenURIEquals)-1, &dummy);
+ rv = aOutput->Write(kMicsumGenURIAttribute, sizeof(kMicsumGenURIAttribute)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
rv = WriteEscapedUrl(spec, aOutput);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1868,12 +2047,12 @@ nsPlacesImportExportService::WriteLivemark(PRInt64 aFolderId, const nsACString&
NS_ENSURE_SUCCESS(rv, rv);
}
- // write id
- rv = aOutput->Write(kIdAttribute, sizeof(kIdAttribute)-1, &dummy);
+ // write id as ITEM_ID as non-rdf IDs can confuse Firefox 2
+ rv = aOutput->Write(kItemIdAttribute, sizeof(kItemIdAttribute)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
- nsCAutoString id;
- id.AppendInt(aFolderId);
- rv = aOutput->Write(id.get(), id.Length(), &dummy);
+ nsCAutoString itemIdAttr;
+ itemIdAttr.AppendInt(aFolderId); // casting from PRInt64 -> PRInt32, data loss, fix me
+ rv = aOutput->Write(itemIdAttr.get(), itemIdAttr.Length(), &dummy);
NS_ENSURE_SUCCESS(rv, rv);
rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1925,7 +2104,7 @@ nsPlacesImportExportService::WriteSeparator(nsINavHistoryResultNode* aItem,
NS_ENSURE_SUCCESS(rv, rv);
// Note: we can't write the separator ID or anything else other than NAME
- // because it makes Firefox 2.x crash/hang - see bug 381129
+ // because it makes Firefox 2.x crash/hang - see bug #381129
nsAutoString title;
rv = mBookmarksService->GetItemTitle(itemId, title);
diff --git a/browser/components/places/tests/unit/bookmarks.preplaces.html b/browser/components/places/tests/unit/bookmarks.preplaces.html
index 89f28779e0b..8c2109fdc68 100644
--- a/browser/components/places/tests/unit/bookmarks.preplaces.html
+++ b/browser/components/places/tests/unit/bookmarks.preplaces.html
@@ -24,7 +24,7 @@
Get Involved
About Us
-
test
+ test
folder test comment
- test post keyword
diff --git a/browser/components/places/tests/unit/test_bookmarks_html.js b/browser/components/places/tests/unit/test_bookmarks_html.js
index ea1c6b8ed3f..d2cdc24e392 100644
--- a/browser/components/places/tests/unit/test_bookmarks_html.js
+++ b/browser/components/places/tests/unit/test_bookmarks_html.js
@@ -196,7 +196,6 @@ function testCanonicalBookmarks(aFolder) {
toolbar.containerOpen = true;
do_check_eq(toolbar.childCount, 2);
-
// livemark
var livemark = toolbar.getChild(1);
// title
@@ -216,6 +215,12 @@ function testCanonicalBookmarks(aFolder) {
var testFolder = rootNode.getChild(5);
do_check_eq(testFolder.type, testFolder.RESULT_TYPE_FOLDER);
do_check_eq(testFolder.title, "test");
+
+ // add date
+ do_check_eq(bmsvc.getItemDateAdded(testFolder.itemId)/1000000, 1177541020);
+ // last modified
+ do_check_eq(bmsvc.getItemLastModified(testFolder.itemId)/1000000, 1177541050);
+
testFolder = testFolder.QueryInterface(Ci.nsINavHistoryQueryResultNode);
do_check_eq(testFolder.hasChildren, true);
// folder description
@@ -243,7 +248,11 @@ function testCanonicalBookmarks(aFolder) {
do_check_true(annosvc.itemHasAnnotation(testBookmark1.itemId,
LOAD_IN_SIDEBAR_ANNO));
// add date
+ do_check_eq(testBookmark1.dateAdded/1000000, 1177375336);
+
// last modified
+ do_check_eq(testBookmark1.lastModified/1000000, 1177375423);
+
// post data
var pageURI = iosvc.newURI(testBookmark1.uri, "", null);
do_check_true(annosvc.pageHasAnnotation(pageURI, POST_DATA_ANNO));
diff --git a/browser/locales/en-US/chrome/browser/places/places.dtd b/browser/locales/en-US/chrome/browser/places/places.dtd
index 2563f7f9c3a..852eaf8fd01 100644
--- a/browser/locales/en-US/chrome/browser/places/places.dtd
+++ b/browser/locales/en-US/chrome/browser/places/places.dtd
@@ -207,6 +207,10 @@
"Keyword">
+
+
diff --git a/toolkit/components/places/public/nsINavBookmarksService.idl b/toolkit/components/places/public/nsINavBookmarksService.idl
index 2acffb2378e..4bb042a9387 100644
--- a/toolkit/components/places/public/nsINavBookmarksService.idl
+++ b/toolkit/components/places/public/nsINavBookmarksService.idl
@@ -170,7 +170,7 @@ interface nsINavBookmarkObserver : nsISupports
* folders. A URI in history can be contained in one or more such folders.
*/
-[scriptable, uuid(d0dcc4b9-3f41-4489-aec5-117f8ed26a68)]
+[scriptable, uuid(5e44a4e2-5db6-412a-b153-c732c9c62fd5)]
interface nsINavBookmarksService : nsISupports
{
/**
@@ -354,6 +354,24 @@ interface nsINavBookmarksService : nsISupports
*/
AString getItemTitle(in long long aItemId);
+ /**
+ * Set the date added time for an item.
+ */
+ void setItemDateAdded(in long long aItemId, in PRTime aDateAdded);
+ /**
+ * Get the date added time for an item.
+ */
+ PRTime getItemDateAdded(in long long aItemId);
+
+ /**
+ * Set the last modified time for an item.
+ */
+ void setItemLastModified(in long long aItemId, in PRTime aLastModified);
+ /**
+ * Get the last modified time for an item.
+ */
+ PRTime getItemLastModified(in long long aItemId);
+
/**
* Get the URI for a bookmark item.
*/
diff --git a/toolkit/components/places/public/nsINavHistoryService.idl b/toolkit/components/places/public/nsINavHistoryService.idl
index ea89ff21ef3..34a2da2ea46 100644
--- a/toolkit/components/places/public/nsINavHistoryService.idl
+++ b/toolkit/components/places/public/nsINavHistoryService.idl
@@ -49,7 +49,7 @@ interface nsINavHistoryResult;
interface nsITreeColumn;
interface nsIWritablePropertyBag;
-[scriptable, uuid(2cce631e-d7dd-4162-a05b-e8ecfbc34367)]
+[scriptable, uuid(ff6f0168-f3f6-4f3a-9dfb-810a8eb4e545)]
interface nsINavHistoryResultNode : nsISupports
{
/**
@@ -163,6 +163,18 @@ interface nsINavHistoryResultNode : nsISupports
* set to -1.
*/
readonly attribute PRInt64 itemId;
+
+ /**
+ * If the node is an item (bookmark, folder or a separator) this value is the
+ * time that the item was created. For other nodes, this value is 0.
+ */
+ readonly attribute PRTime dateAdded;
+
+ /**
+ * If the node is an item (bookmark, folder or a separator) this value is the
+ * time that the item was last modified. For other nodes, this value is 0.
+ */
+ readonly attribute PRTime lastModified;
};
@@ -990,8 +1002,13 @@ interface nsINavHistoryQueryOptions : nsISupports
const unsigned short SORT_BY_VISITCOUNT_DESCENDING = 8;
const unsigned short SORT_BY_KEYWORD_ASCENDING = 9;
const unsigned short SORT_BY_KEYWORD_DESCENDING = 10;
- const unsigned short SORT_BY_ANNOTATION_ASCENDING = 11;
- const unsigned short SORT_BY_ANNOTATION_DESCENDING = 12;
+ const unsigned short SORT_BY_DATEADDED_ASCENDING = 11;
+ const unsigned short SORT_BY_DATEADDED_DESCENDING = 12;
+ const unsigned short SORT_BY_LASTMODIFIED_ASCENDING = 13;
+ const unsigned short SORT_BY_LASTMODIFIED_DESCENDING = 14;
+ const unsigned short SORT_BY_ANNOTATION_ASCENDING = 15;
+ const unsigned short SORT_BY_ANNOTATION_DESCENDING = 16;
+
/**
* "URI" results, one for each URI visited in the range. Individual result
diff --git a/toolkit/components/places/src/nsNavBookmarks.cpp b/toolkit/components/places/src/nsNavBookmarks.cpp
index 06d55b733ba..bd03aa24529 100644
--- a/toolkit/components/places/src/nsNavBookmarks.cpp
+++ b/toolkit/components/places/src/nsNavBookmarks.cpp
@@ -56,9 +56,9 @@ const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Position = 4;
const PRInt32 nsNavBookmarks::kFindBookmarksIndex_Title = 5;
// These columns sit to the right of the kGetInfoIndex_* columns.
-const PRInt32 nsNavBookmarks::kGetChildrenIndex_Position = 9;
-const PRInt32 nsNavBookmarks::kGetChildrenIndex_Type = 10;
-const PRInt32 nsNavBookmarks::kGetChildrenIndex_ForeignKey = 11;
+const PRInt32 nsNavBookmarks::kGetChildrenIndex_Position = 11;
+const PRInt32 nsNavBookmarks::kGetChildrenIndex_Type = 12;
+const PRInt32 nsNavBookmarks::kGetChildrenIndex_ForeignKey = 13;
const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_ID = 0;
const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_URI = 1;
@@ -68,6 +68,8 @@ const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_PlaceID = 4;
const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Parent = 5;
const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_Type = 6;
const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_FolderType = 7;
+const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_DateAdded = 8;
+const PRInt32 nsNavBookmarks::kGetItemPropertiesIndex_LastModified = 9;
nsNavBookmarks* nsNavBookmarks::sInstance = nsnull;
@@ -144,7 +146,9 @@ nsNavBookmarks::Init()
NS_LITERAL_CSTRING("SELECT h.id, h.url, a.title, "
"h.rev_host, h.visit_count, "
"(SELECT MAX(visit_date) FROM moz_historyvisits WHERE place_id = h.id), "
- "f.url, null, a.id, a.position, a.type, a.fk "
+ "f.url, null, a.id, "
+ "a.dateAdded, a.lastModified, "
+ "a.position, a.type, a.fk "
"FROM moz_bookmarks a "
"LEFT JOIN moz_places h ON a.fk = h.id "
"LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
@@ -171,7 +175,7 @@ nsNavBookmarks::Init()
// get bookmark/folder/separator properties
rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
- "SELECT b.id, (SELECT url from moz_places WHERE id = b.fk), b.title, b.position, b.fk, b.parent, b.type, b.folder_type "
+ "SELECT b.id, (SELECT url from moz_places WHERE id = b.fk), b.title, b.position, b.fk, b.parent, b.type, b.folder_type, b.dateAdded, b.lastModified "
"FROM moz_bookmarks b "
"WHERE b.id = ?1"),
getter_AddRefs(mDBGetItemProperties));
@@ -243,7 +247,6 @@ nsNavBookmarks::Init()
return NS_OK;
}
-
// nsNavBookmarks::InitTables
//
// All commands that initialize the schema of the DB go in here. This is
@@ -267,7 +270,9 @@ nsNavBookmarks::InitTables(mozIStorageConnection* aDBConn)
"position INTEGER, "
"title LONGVARCHAR, "
"keyword_id INTEGER, "
- "folder_type TEXT)"));
+ "folder_type TEXT, "
+ "dateAdded INTEGER, "
+ "lastModified INTEGER)"));
NS_ENSURE_SUCCESS(rv, rv);
// this index will make it faster to determine if a given item is
@@ -869,7 +874,7 @@ nsNavBookmarks::InsertItem(PRInt64 aFolder, nsIURI *aItem, PRInt32 aIndex, PRInt
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString buffer;
- buffer.AssignLiteral("INSERT INTO moz_bookmarks (fk, type, parent, position) VALUES (");
+ buffer.AssignLiteral("INSERT INTO moz_bookmarks (fk, type, parent, position, dateAdded) VALUES (");
buffer.AppendInt(childID);
buffer.AppendLiteral(", ");
buffer.AppendInt(TYPE_BOOKMARK);
@@ -877,6 +882,8 @@ nsNavBookmarks::InsertItem(PRInt64 aFolder, nsIURI *aItem, PRInt32 aIndex, PRInt
buffer.AppendInt(aFolder);
buffer.AppendLiteral(", ");
buffer.AppendInt(index);
+ buffer.AppendLiteral(", ");
+ buffer.AppendInt(PR_Now());
buffer.AppendLiteral(")");
rv = dbConn->ExecuteSimpleSQL(buffer);
@@ -995,16 +1002,16 @@ nsNavBookmarks::CreateFolderWithID(PRInt64 aFolder, PRInt64 aParent,
nsCOMPtr statement;
if (aFolder == -1) {
- rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks (title, type, parent, position, folder_type) VALUES (?1, ?2, ?3, ?4, null)"),
+ rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks (title, type, parent, position, folder_type, dateAdded) VALUES (?1, ?2, ?3, ?4, null, ?5)"),
getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
}
else {
- rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks (id, title, type, parent, position, folder_type) VALUES (?5, ?1, ?2, ?3, ?4, null)"),
+ rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks (id, title, type, parent, position, folder_type, dateAdded) VALUES (?6, ?1, ?2, ?3, ?4, null, ?5)"),
getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
- rv = statement->BindInt64Parameter(4, aFolder);
+ rv = statement->BindInt64Parameter(5, aFolder);
NS_ENSURE_SUCCESS(rv, rv);
}
@@ -1016,6 +1023,8 @@ nsNavBookmarks::CreateFolderWithID(PRInt64 aFolder, PRInt64 aParent,
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt32Parameter(3, index);
NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64Parameter(4, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
rv = statement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
@@ -1094,7 +1103,7 @@ nsNavBookmarks::InsertSeparator(PRInt64 aParent, PRInt32 aIndex,
nsCOMPtr statement;
rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks "
- "(type, parent, position) VALUES (?1, ?2, ?3)"),
+ "(type, parent, position, dateAdded) VALUES (?1, ?2, ?3, ?4)"),
getter_AddRefs(statement));
NS_ENSURE_SUCCESS(rv, rv);
@@ -1104,6 +1113,8 @@ nsNavBookmarks::InsertSeparator(PRInt64 aParent, PRInt32 aIndex,
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt32Parameter(2, index);
NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64Parameter(3, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
rv = statement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
@@ -1522,6 +1533,98 @@ nsNavBookmarks::GetChildFolder(PRInt64 aFolder, const nsAString& aSubFolder,
return statement->GetInt64(0, _result);
}
+NS_IMETHODIMP
+nsNavBookmarks::SetItemDateAdded(PRInt64 aItemId, PRTime aDateAdded)
+{
+ mozIStorageConnection *dbConn = DBConn();
+ mozStorageTransaction transaction(dbConn, PR_FALSE);
+
+ nsCOMPtr statement;
+ nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("UPDATE moz_bookmarks SET dateAdded = ?1 WHERE id = ?2"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64Parameter(0, aDateAdded);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64Parameter(1, aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // note, we are not notifying the observers
+ // that the item has changed.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavBookmarks::GetItemDateAdded(PRInt64 aItemId, PRTime *aDateAdded)
+{
+ NS_ENSURE_ARG_POINTER(aDateAdded);
+
+ mozStorageStatementScoper scope(mDBGetItemProperties);
+ nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRBool results;
+ rv = mDBGetItemProperties->ExecuteStep(&results);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!results)
+ return NS_ERROR_INVALID_ARG; // invalid item id
+
+ return mDBGetItemProperties->GetInt64(kGetItemPropertiesIndex_DateAdded, aDateAdded);
+}
+
+NS_IMETHODIMP
+nsNavBookmarks::SetItemLastModified(PRInt64 aItemId, PRTime aLastModified)
+{
+ mozIStorageConnection *dbConn = DBConn();
+ mozStorageTransaction transaction(dbConn, PR_FALSE);
+
+ nsCOMPtr statement;
+ nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING("UPDATE moz_bookmarks SET lastModified = ?1 WHERE id = ?2"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64Parameter(0, aLastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64Parameter(1, aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // note, we are not notifying the observers
+ // that the item has changed.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavBookmarks::GetItemLastModified(PRInt64 aItemId, PRTime *aLastModified)
+{
+ NS_ENSURE_ARG_POINTER(aLastModified);
+
+ mozStorageStatementScoper scope(mDBGetItemProperties);
+ nsresult rv = mDBGetItemProperties->BindInt64Parameter(0, aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRBool results;
+ rv = mDBGetItemProperties->ExecuteStep(&results);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!results)
+ return NS_ERROR_INVALID_ARG; // invalid item id
+
+ return mDBGetItemProperties->GetInt64(kGetItemPropertiesIndex_LastModified, aLastModified);
+}
+
NS_IMETHODIMP
nsNavBookmarks::SetItemTitle(PRInt64 aItemId, const nsAString &aTitle)
{
@@ -1543,10 +1646,12 @@ nsNavBookmarks::SetItemTitle(PRInt64 aItemId, const nsAString &aTitle)
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetItemLastModified(aItemId, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver,
OnItemChanged(aItemId, NS_LITERAL_CSTRING("title"),
PR_FALSE, NS_ConvertUTF16toUTF8(aTitle)));
-
return NS_OK;
}
@@ -1904,6 +2009,9 @@ nsNavBookmarks::ChangeBookmarkURI(PRInt64 aBookmarkId, nsIURI *aNewURI)
rv = aNewURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetItemLastModified(aBookmarkId, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
// Pass the new URI to OnItemChanged.
ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver,
OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("uri"), PR_FALSE, spec))
@@ -2100,11 +2208,15 @@ nsNavBookmarks::SetKeywordForBookmark(PRInt64 aBookmarkId, const nsAString& aKey
rv = updateKeywordStmnt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
transaction.Commit();
+
+ rv = SetItemLastModified(aBookmarkId, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
// Pass the new keyword to OnItemChanged.
ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver,
OnItemChanged(aBookmarkId, NS_LITERAL_CSTRING("keyword"),
PR_FALSE, NS_ConvertUTF16toUTF8(aKeyword)))
+
return NS_OK;
}
@@ -2354,8 +2466,12 @@ nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName)
NS_IMETHODIMP
nsNavBookmarks::OnItemAnnotationSet(PRInt64 aItemId, const nsACString& aName)
{
+ nsresult rv = SetItemLastModified(aItemId, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver,
OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString()));
+
return NS_OK;
}
@@ -2368,8 +2484,12 @@ nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
NS_IMETHODIMP
nsNavBookmarks::OnItemAnnotationRemoved(PRInt64 aItemId, const nsACString& aName)
-{
+{
+ nsresult rv = SetItemLastModified(aItemId, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
ENUMERATE_WEAKARRAY(mObservers, nsINavBookmarkObserver,
OnItemChanged(aItemId, aName, PR_TRUE, EmptyCString()));
+
return NS_OK;
}
diff --git a/toolkit/components/places/src/nsNavBookmarks.h b/toolkit/components/places/src/nsNavBookmarks.h
index 9033f4ab481..0f9fdecc0a5 100644
--- a/toolkit/components/places/src/nsNavBookmarks.h
+++ b/toolkit/components/places/src/nsNavBookmarks.h
@@ -190,6 +190,8 @@ private:
static const PRInt32 kGetItemPropertiesIndex_Parent;
static const PRInt32 kGetItemPropertiesIndex_Type;
static const PRInt32 kGetItemPropertiesIndex_FolderType;
+ static const PRInt32 kGetItemPropertiesIndex_DateAdded;
+ static const PRInt32 kGetItemPropertiesIndex_LastModified;
nsCOMPtr mDBGetRedirectDestinations;
diff --git a/toolkit/components/places/src/nsNavHistory.cpp b/toolkit/components/places/src/nsNavHistory.cpp
index e379b543831..86b6c73611a 100644
--- a/toolkit/components/places/src/nsNavHistory.cpp
+++ b/toolkit/components/places/src/nsNavHistory.cpp
@@ -208,6 +208,8 @@ const PRInt32 nsNavHistory::kGetInfoIndex_VisitDate = 5;
const PRInt32 nsNavHistory::kGetInfoIndex_FaviconURL = 6;
const PRInt32 nsNavHistory::kGetInfoIndex_SessionId = 7;
const PRInt32 nsNavHistory::kGetInfoIndex_ItemId = 8;
+const PRInt32 nsNavHistory::kGetInfoIndex_ItemDateAdded = 9;
+const PRInt32 nsNavHistory::kGetInfoIndex_ItemLastModified = 10;
const PRInt32 nsNavHistory::kAutoCompleteIndex_URL = 0;
const PRInt32 nsNavHistory::kAutoCompleteIndex_Title = 1;
@@ -385,7 +387,7 @@ nsNavHistory::Init()
//
-#define PLACES_SCHEMA_VERSION 4
+#define PLACES_SCHEMA_VERSION 5
nsresult
nsNavHistory::InitDB(PRBool *aDoImport)
@@ -488,13 +490,13 @@ nsNavHistory::InitDB(PRBool *aDoImport)
NS_ENSURE_SUCCESS(rv, rv);
}
- // Migrate bookmarks tables up to V4
- if (DBSchemaVersion < 4) {
+ // Migrate bookmarks tables up to V5
+ if (DBSchemaVersion < 5) {
rv = ForceMigrateBookmarksDB(mDBConn);
NS_ENSURE_SUCCESS(rv, rv);
}
- // XXX Upgrades >V4 must add migration code here.
+ // XXX Upgrades >V5 must add migration code here.
} else {
// Downgrading
@@ -502,9 +504,9 @@ nsNavHistory::InitDB(PRBool *aDoImport)
// XXX Need to prompt user or otherwise notify of
// potential dataloss when downgrading.
- // XXX Downgrades from >V4 must add migration code here.
+ // XXX Downgrades from >V5 must add migration code here.
- // Downgrade v1,2,4
+ // Downgrade v1,2,4,5
// V3 had no backwards incompatible changes.
if (DBSchemaVersion > 2) {
// perform downgrade to v2
@@ -780,7 +782,7 @@ nsNavHistory::InitStatements()
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT b.fk, h.url, b.title, h.rev_host, h.visit_count, "
"(SELECT MAX(visit_date) FROM moz_historyvisits WHERE place_id = b.fk), "
- "f.url, null, null "
+ "f.url, null, null, b.dateAdded, b.lastModified "
"FROM moz_bookmarks b "
"JOIN moz_places h ON b.fk = h.id "
"LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
@@ -2067,7 +2069,7 @@ nsNavHistory::GetQueryResults(const nsCOMArray& aQueries,
queryString = NS_LITERAL_CSTRING(
"SELECT b.fk, h.url, b.title, h.rev_host, h.visit_count, "
"(SELECT MAX(visit_date) FROM moz_historyvisits WHERE place_id = b.fk), "
- "f.url, null, b.id "
+ "f.url, null, b.id, b.dateAdded, b.lastModified "
"FROM moz_bookmarks b "
"JOIN moz_places h ON b.fk = h.id "
"LEFT OUTER JOIN moz_historyvisits v ON b.fk = v.place_id "
@@ -2133,13 +2135,13 @@ nsNavHistory::GetQueryResults(const nsCOMArray& aQueries,
// a sort by date here (see the IDL definition for maxResults). We'll
// still do the official sort by title later.
if (aOptions->MaxResults() > 0)
- queryString += NS_LITERAL_CSTRING(" ORDER BY 6 DESC"); // v.vist_date
+ queryString += NS_LITERAL_CSTRING(" ORDER BY 6 DESC"); // v.visit_date
break;
case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
- queryString += NS_LITERAL_CSTRING(" ORDER BY 6 ASC"); // v.vist_date
+ queryString += NS_LITERAL_CSTRING(" ORDER BY 6 ASC"); // v.visit_date
break;
case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
- queryString += NS_LITERAL_CSTRING(" ORDER BY 6 DESC"); // v.vist_date
+ queryString += NS_LITERAL_CSTRING(" ORDER BY 6 DESC"); // v.visit_date
break;
case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
queryString += NS_LITERAL_CSTRING(" ORDER BY 2 ASC"); // h.url
@@ -3931,6 +3933,8 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
if (NS_SUCCEEDED(aRow->GetIsNull(kGetInfoIndex_ItemId, &isNull)) &&
!isNull) {
(*aResult)->mItemId = aRow->AsInt64(kGetInfoIndex_ItemId);
+ (*aResult)->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
+ (*aResult)->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
}
NS_ADDREF(*aResult);
return NS_OK;
diff --git a/toolkit/components/places/src/nsNavHistory.h b/toolkit/components/places/src/nsNavHistory.h
index 6970356f8f5..e26a8be1189 100644
--- a/toolkit/components/places/src/nsNavHistory.h
+++ b/toolkit/components/places/src/nsNavHistory.h
@@ -221,6 +221,8 @@ public:
static const PRInt32 kGetInfoIndex_RevHost;
static const PRInt32 kGetInfoIndex_VisitCount;
static const PRInt32 kGetInfoIndex_ItemId;
+ static const PRInt32 kGetInfoIndex_ItemDateAdded;
+ static const PRInt32 kGetInfoIndex_ItemLastModified;
// select a history row by URL, with visit date info (extra work)
mozIStorageStatement* DBGetURLPageInfoFull()
diff --git a/toolkit/components/places/src/nsNavHistoryResult.cpp b/toolkit/components/places/src/nsNavHistoryResult.cpp
index 82f6db318b6..5010d34f842 100644
--- a/toolkit/components/places/src/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/src/nsNavHistoryResult.cpp
@@ -116,6 +116,8 @@ nsNavHistoryResultNode::nsNavHistoryResultNode(
mFaviconURI(aIconURI),
mBookmarkIndex(-1),
mItemId(-1),
+ mDateAdded(0),
+ mLastModified(0),
mIndentLevel(-1),
mViewIndex(-1)
{
@@ -595,6 +597,14 @@ nsNavHistoryContainerResultNode::GetSortingComparator(PRUint16 aSortType)
return &SortComparison_AnnotationLess;
case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING:
return &SortComparison_AnnotationGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
+ return &SortComparison_DateAddedLess;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
+ return &SortComparison_DateAddedGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
+ return &SortComparison_LastModifiedLess;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
+ return &SortComparison_LastModifiedGreater;
default:
NS_NOTREACHED("Bad sorting type");
return nsnull;
@@ -789,6 +799,48 @@ PRInt32 PR_CALLBACK nsNavHistoryContainerResultNode::SortComparison_DateGreater(
return -nsNavHistoryContainerResultNode::SortComparison_DateLess(a, b, closure);
}
+// nsNavHistoryContainerResultNode::SortComparison_DateAdded*
+//
+
+PRInt32 PR_CALLBACK nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ PRInt32 value = ComparePRTime(a->mDateAdded, b->mDateAdded);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+PRInt32 PR_CALLBACK nsNavHistoryContainerResultNode::SortComparison_DateAddedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(a, b, closure);
+}
+
+
+// nsNavHistoryContainerResultNode::SortComparison_LastModified*
+//
+
+PRInt32 PR_CALLBACK nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ PRInt32 value = ComparePRTime(a->mLastModified, b->mLastModified);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+PRInt32 PR_CALLBACK nsNavHistoryContainerResultNode::SortComparison_LastModifiedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(a, b, closure);
+}
// nsNavHistoryContainerResultNode::SortComparison_URI*
//
@@ -2515,6 +2567,7 @@ nsNavHistoryQueryResultNode::OnItemChanged(PRInt64 aItemId,
NS_NOTREACHED("Everything observers should not get OnItemChanged, but should get the corresponding history notifications instead");
return NS_OK;
}
+
NS_IMETHODIMP
nsNavHistoryQueryResultNode::OnItemVisited(PRInt64 aItemId,
PRInt64 aVisitId, PRTime aTime)
@@ -3121,6 +3174,27 @@ nsNavHistoryFolderResultNode::OnItemChanged(PRInt64 aItemId,
NS_NOTREACHED("Unknown bookmark property changing.");
}
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_UNEXPECTED);
+
+ PRTime lastModified;
+ nsresult rv = bookmarks->GetItemLastModified(aItemId, &lastModified);
+ if (NS_SUCCEEDED(rv)) {
+ node->mLastModified = lastModified;
+ }
+ else {
+ node->mLastModified = 0;
+ }
+
+ PRTime dateAdded;
+ rv = bookmarks->GetItemDateAdded(aItemId, &dateAdded);
+ if (NS_SUCCEEDED(rv)) {
+ node->mDateAdded = dateAdded;
+ }
+ else {
+ node->mDateAdded = 0;
+ }
+
nsNavHistoryResult* result = GetResult();
NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
@@ -3144,7 +3218,6 @@ nsNavHistoryFolderResultNode::OnItemChanged(PRInt64 aItemId,
return NS_OK;
}
-
// nsNavHistoryFolderResultNode::OnItemVisited (nsINavBookmarkObserver)
//
// Update visit count and last visit time and refresh.
@@ -3738,7 +3811,6 @@ nsNavHistoryResult::OnItemChanged(PRInt64 aItemId,
return NS_OK;
}
-
// nsNavHistoryResult::OnItemVisited (nsINavBookmarkObserver)
NS_IMETHODIMP
diff --git a/toolkit/components/places/src/nsNavHistoryResult.h b/toolkit/components/places/src/nsNavHistoryResult.h
index 8df2957abae..0192167b619 100644
--- a/toolkit/components/places/src/nsNavHistoryResult.h
+++ b/toolkit/components/places/src/nsNavHistoryResult.h
@@ -225,7 +225,12 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHistoryResult, NS_NAVHISTORYRESULT_IID)
NS_IMETHOD GetBookmarkIndex(PRInt32* aIndex) \
{ *aIndex = mBookmarkIndex; return NS_OK; } \
NS_IMETHOD GetItemId(PRInt64* aId) \
- { *aId= mItemId; return NS_OK; }
+ { *aId= mItemId; return NS_OK; } \
+ NS_IMETHOD GetDateAdded(PRTime* aDateAdded) \
+ { *aDateAdded = mDateAdded; return NS_OK; } \
+ NS_IMETHOD GetLastModified(PRTime* aLastModified) \
+ { *aLastModified = mLastModified; return NS_OK; }
+
// This is used by the base classes instead of
// NS_FORWARD_NSINAVHISTORYRESULTNODE(nsNavHistoryResultNode) because they
@@ -365,6 +370,8 @@ public:
nsCString mFaviconURI;
PRInt32 mBookmarkIndex;
PRInt64 mItemId;
+ PRTime mDateAdded;
+ PRTime mLastModified;
// The indent level of this node. The root node will have a value of -1. The
// root's children will have a value of 0, and so on.
@@ -578,6 +585,14 @@ public:
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure);
PR_STATIC_CALLBACK(int) SortComparison_AnnotationGreater(
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure);
+ PR_STATIC_CALLBACK(int) SortComparison_DateAddedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure);
+ PR_STATIC_CALLBACK(int) SortComparison_DateAddedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure);
+ PR_STATIC_CALLBACK(int) SortComparison_LastModifiedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure);
+ PR_STATIC_CALLBACK(int) SortComparison_LastModifiedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure);
// finding children: THESE DO NOT ADDREF
nsNavHistoryResultNode* FindChildURI(nsIURI* aURI, PRUint32* aNodeIndex)
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks.js b/toolkit/components/places/tests/bookmarks/test_bookmarks.js
index 9b58b623332..4fa67ea257c 100644
--- a/toolkit/components/places/tests/bookmarks/test_bookmarks.js
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks.js
@@ -136,18 +136,44 @@ function run_test() {
do_check_eq(bmsvc.getItemType(testRoot), bmsvc.TYPE_FOLDER);
// insert a bookmark
+ // the time before we insert, in microseconds
+ var beforeInsert = Date.now() * 1000;
+ do_check_true(beforeInsert > 0);
+
var newId = bmsvc.insertItem(testRoot, uri("http://google.com/"), bmsvc.DEFAULT_INDEX);
do_check_eq(observer._itemAddedId, newId);
do_check_eq(observer._itemAddedParent, testRoot);
do_check_eq(observer._itemAddedIndex, testStartIndex);
do_check_eq(bmsvc.getBookmarkURI(newId).spec, "http://google.com/");
+ var dateAdded = bmsvc.getItemDateAdded(newId);
+ // dateAdded can equal beforeInsert
+ do_check_true(dateAdded >= beforeInsert);
+
+ // after just inserting, modified should not be set
+ var lastModified = bmsvc.getItemLastModified(newId);
+ do_check_eq(lastModified, 0);
+
+ // the time before we set the title, in microseconds
+ var beforeSetTitle = Date.now() * 1000;
+ do_check_true(beforeSetTitle >= beforeInsert);
+
// set bookmark title
bmsvc.setItemTitle(newId, "Google");
do_check_eq(observer._itemChangedId, newId);
do_check_eq(observer._itemChangedProperty, "title");
do_check_eq(observer._itemChangedValue, "Google");
+ // check that dateAdded hasn't changed
+ var dateAdded2 = bmsvc.getItemDateAdded(newId);
+ do_check_eq(dateAdded2, dateAdded);
+
+ // check lastModified after we set the title
+ var lastModified2 = bmsvc.getItemLastModified(newId);
+ do_check_true(lastModified2 > lastModified);
+ do_check_true(lastModified2 >= dateAdded);
+ do_check_true(lastModified2 >= beforeSetTitle);
+
// get item title
var title = bmsvc.getItemTitle(newId);
do_check_eq(title, "Google");
@@ -298,14 +324,24 @@ function run_test() {
var tmpFolder = bmsvc.createFolder(testRoot, "tmp", 2);
do_check_eq(bmsvc.getItemIndex(tmpFolder), 2);
- // test setKeywordForURI
+ // test setKeywordForBookmark
var kwTestItemId = bmsvc.insertItem(testRoot, uri("http://keywordtest.com"), bmsvc.DEFAULT_INDEX);
try {
+ var dateAdded = bmsvc.getItemDateAdded(kwTestItemId);
+ // after just inserting, modified should not be set
+ var lastModified = bmsvc.getItemLastModified(kwTestItemId);
+ do_check_eq(lastModified, 0);
+
bmsvc.setKeywordForBookmark(kwTestItemId, "bar");
+
+ var lastModified2 = bmsvc.getItemLastModified(kwTestItemId);
+ do_check_true(lastModified2 > lastModified);
+ do_check_true(lastModified2 >= dateAdded);
} catch(ex) {
do_throw("setKeywordForBookmark: " + ex);
}
+ var lastModified3 = bmsvc.getItemLastModified(kwTestItemId);
// test getKeywordForBookmark
var k = bmsvc.getKeywordForBookmark(kwTestItemId);
do_check_eq("bar", k);
@@ -411,7 +447,18 @@ function run_test() {
// test change bookmark uri
var newId10 = bmsvc.insertItem(testRoot, uri("http://foo10.com/"), bmsvc.DEFAULT_INDEX);
+ var dateAdded = bmsvc.getItemDateAdded(newId10);
+ // after just inserting, modified should not be set
+ var lastModified = bmsvc.getItemLastModified(newId10);
+ do_check_eq(lastModified, 0);
+
bmsvc.changeBookmarkURI(newId10, uri("http://foo11.com/"));
+
+ // check that lastModified is set after we change the bookmark uri
+ var lastModified2 = bmsvc.getItemLastModified(newId10);
+ do_check_true(lastModified2 > lastModified);
+ do_check_true(lastModified2 >= dateAdded);
+
do_check_eq(observer._itemChangedId, newId10);
do_check_eq(observer._itemChangedProperty, "uri");
do_check_eq(observer._itemChangedValue, "http://foo11.com/");
@@ -486,6 +533,76 @@ function run_test() {
do_throw("bookmarks query: " + ex);
}
+ // test dateAdded and lastModified properties
+ // for a search query
+ try {
+ var options = histsvc.getNewQueryOptions();
+ options.excludeQueries = 1;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+ var query = histsvc.getNewQuery();
+ query.onlyBookmarked = true;
+ query.searchTerms = "ZZZXXXYYY";
+ var result = histsvc.executeQuery(query, options);
+ var rootNode = result.root;
+ rootNode.containerOpen = true;
+ var cc = rootNode.childCount;
+ do_check_eq(cc, 1);
+ var node = rootNode.getChild(0);
+
+ do_check_eq(typeof node.dateAdded, "number");
+ do_check_true(node.dateAdded > 0);
+
+ do_check_eq(typeof node.lastModified, "number");
+ do_check_true(node.lastModified > 0);
+
+ rootNode.containerOpen = false;
+ }
+ catch(ex) {
+ do_throw("bookmarks query: " + ex);
+ }
+
+ // test dateAdded and lastModified properties
+ // for a folder query
+ try {
+ var options = histsvc.getNewQueryOptions();
+ options.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER], 1);
+ var query = histsvc.getNewQuery();
+ query.setFolders([testRoot], 1);
+ var result = histsvc.executeQuery(query, options);
+ var rootNode = result.root;
+ rootNode.containerOpen = true;
+ var cc = rootNode.childCount;
+ for (var i = 0; i < cc; i++) {
+ var node = rootNode.getChild(i);
+
+ if (node.type == node.RESULT_TYPE_URI) {
+ do_check_eq(typeof node.dateAdded, "number");
+ do_check_true(node.dateAdded > 0);
+
+ do_check_eq(typeof node.lastModified, "number");
+ do_check_true(node.lastModified > 0);
+ break;
+ }
+ }
+ rootNode.containerOpen = false;
+ }
+ catch(ex) {
+ do_throw("bookmarks query: " + ex);
+ }
+
+ // check setItemLastModified() and setItemDateAdded()
+ var newId14 = bmsvc.insertItem(testRoot, uri("http://bar.tld/"), bmsvc.DEFAULT_INDEX);
+ var dateAdded = bmsvc.getItemDateAdded(newId14);
+ var lastModified = bmsvc.getItemLastModified(newId14);
+ do_check_eq(lastModified, 0);
+ do_check_true(dateAdded > lastModified);
+ bmsvc.setItemLastModified(newId14, 1234);
+ var fakeLastModified = bmsvc.getItemLastModified(newId14);
+ do_check_eq(fakeLastModified, 1234);
+ bmsvc.setItemDateAdded(newId14, 4321);
+ var fakeDateAdded = bmsvc.getItemDateAdded(newId14);
+ do_check_eq(fakeDateAdded, 4321);
+
// ensure that removing an item removes its annotations
do_check_true(annosvc.itemHasAnnotation(newId3, "test-annotation"));
bmsvc.removeItem(newId3);
@@ -500,8 +617,30 @@ function run_test() {
}
function testSimpleFolderResult() {
+ // the time before we create a folder, in microseconds
+ var beforeCreate = Date.now() * 1000;
+ do_check_true(beforeCreate > 0);
+
+ // create a folder
var parent = bmsvc.createFolder(root, "test", bmsvc.DEFAULT_INDEX);
+
+ var dateCreated = bmsvc.getItemDateAdded(parent);
+ do_check_true(dateCreated > 0);
+ // dateCreated can equal beforeCreate
+ do_check_true(dateCreated >= beforeCreate);
+
+ // the time before we insert, in microseconds
+ var beforeInsert = Date.now() * 1000;
+ do_check_true(beforeInsert > 0);
+
+ // insert a separator
var sep = bmsvc.insertSeparator(parent, bmsvc.DEFAULT_INDEX);
+
+ var dateAdded = bmsvc.getItemDateAdded(sep);
+ do_check_true(dateAdded > 0);
+ // dateAdded can equal beforeInsert
+ do_check_true(dateAdded >= beforeInsert);
+
var item = bmsvc.insertItem(parent, uri("about:blank"), bmsvc.DEFAULT_INDEX);
bmsvc.setItemTitle(item, "test bookmark");
var folder = bmsvc.createFolder(parent, "test folder", bmsvc.DEFAULT_INDEX);
diff --git a/toolkit/components/places/tests/unit/test_annotations.js b/toolkit/components/places/tests/unit/test_annotations.js
index 4c8dc1c1f8a..9b0bdbc5eb1 100644
--- a/toolkit/components/places/tests/unit/test_annotations.js
+++ b/toolkit/components/places/tests/unit/test_annotations.js
@@ -115,13 +115,28 @@ function run_test() {
// string item-annotation
try {
+ var lastModified = bmsvc.getItemLastModified(testItemId);
+ // verify that lastModified is 0 before we set the annotation
+ do_check_eq(lastModified, 0);
annosvc.setItemAnnotationString(testItemId, testAnnoName, testAnnoVal, 0, 0);
+ var lastModified2 = bmsvc.getItemLastModified(testItemId);
+ // verify that setting the annotation updates the last modified time
+ do_check_true(lastModified2 > lastModified);
} catch(ex) {
do_throw("unable to add item annotation");
}
do_check_eq(annoObserver.ITEM_lastSet_Id, testItemId);
do_check_eq(annoObserver.ITEM_lastSet_AnnoName, testAnnoName);
+ try {
+ var lastModified = bmsvc.getItemLastModified(testItemId);
+ var annoVal = annosvc.getItemAnnotationString(testItemId, testAnnoName);
+ // verify the anno value
+ do_check_eq(testAnnoVal, annoVal);
+ } catch(ex) {
+ do_throw("unable to get item annotation");
+ }
+
// test getPagesWithAnnotation
var uri2 = uri("http://www.tests.tld");
annosvc.setPageAnnotationString(uri2, testAnnoName, testAnnoVal, 0, 0);
@@ -318,7 +333,14 @@ function run_test() {
// test annotation removal
annosvc.removePageAnnotation(testURI, int32Key);
+
+ annosvc.setItemAnnotationString(testItemId, testAnnoName, testAnnoVal, 0, 0);
+ // verify that removing an annotation updates the last modified date
+ var lastModified3 = bmsvc.getItemLastModified(testItemId);
annosvc.removeItemAnnotation(testItemId, int32Key);
+ var lastModified4 = bmsvc.getItemLastModified(testItemId);
+ do_check_true(lastModified4 >= lastModified3);
+
do_check_eq(annoObserver.PAGE_lastRemoved_URI, testURI.spec);
do_check_eq(annoObserver.PAGE_lastRemoved_AnnoName, int32Key);
do_check_eq(annoObserver.ITEM_lastRemoved_Id, testItemId);
diff --git a/toolkit/components/places/tests/unit/test_result_sort.js b/toolkit/components/places/tests/unit/test_result_sort.js
index d35707b95c8..0d0f4622d43 100644
--- a/toolkit/components/places/tests/unit/test_result_sort.js
+++ b/toolkit/components/places/tests/unit/test_result_sort.js
@@ -145,6 +145,9 @@ function run_test() {
// id1 precedes id2 per title-descending fallback
checkOrder(id3, id1, id2);
+ // XXXtodo: test dateAdded sort
+ // XXXtodo: test lastModified sort
+
// test live update
annosvc.setItemAnnotationString(id1, "testAnno", "c", 0, 0);
checkOrder(id1, id3, id2);