Bug 1269398: Add visitType, visitId, and fromVisitId fields to history visit query results. r=mak
authorKris Maglione <maglione.k@gmail.com>
Fri, 13 May 2016 13:47:24 -0700
changeset 299116 def937e0dda7f3e9409e3f5bd70a659c4232782f
parent 299115 6a6155e3afa6148ef93255e6d9e126c2974f0319
child 299117 75bb440d7525ee56a201664945a5c907d9a40a67
push id30290
push userryanvm@gmail.com
push dateFri, 27 May 2016 01:44:20 +0000
treeherdermozilla-central@34ac60e5b29c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1269398
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1269398: Add visitType, visitId, and fromVisitId fields to history visit query results. r=mak MozReview-Commit-ID: BCsztVsCH98
toolkit/components/places/nsINavHistoryService.idl
toolkit/components/places/nsNavBookmarks.cpp
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsNavHistory.h
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/nsNavHistoryResult.h
toolkit/components/places/tests/unit/test_resultsAsVisit_details.js
toolkit/components/places/tests/unit/xpcshell.ini
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -162,16 +162,35 @@ interface nsINavHistoryResultNode : nsIS
   readonly attribute ACString pageGuid;
 
   /**
    * The unique ID associated with the bookmark. It returns an empty string
    * if the result node is not associated with a bookmark, a folder or a
    * separator.
    */
   readonly attribute ACString bookmarkGuid;
+
+  /**
+   * The unique ID associated with the history visit. For node types other than
+   * history visit nodes, this value is -1.
+   */
+  readonly attribute long long visitId;
+
+  /**
+   * The unique ID associated with visit node which was the referrer of this
+   * history visit. For node types other than history visit nodes, or visits
+   * without any known referrer, this value is -1.
+   */
+  readonly attribute long long fromVisitId;
+
+  /**
+   * The transition type associated with this visit. For node types other than
+   * history visit nodes, this value is 0.
+   */
+  readonly attribute unsigned long visitType;
 };
 
 
 /**
  * Base class for container results. This includes all types of groupings.
  * Bookmark folders and places queries will be QueryResultNodes which extends
  * these items.
  */
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -17,20 +17,20 @@
 #include "prprf.h"
 #include "mozilla/storage.h"
 
 #include "GeckoProfiler.h"
 
 using namespace mozilla;
 
 // These columns sit to the right of the kGetInfoIndex_* columns.
-const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 15;
-const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 16;
-const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 17;
-const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 18;
+const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 18;
+const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 19;
+const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 20;
+const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
 
 using namespace mozilla::places;
 
 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
 
 #define BOOKMARKS_ANNO_PREFIX "bookmarks/"
 #define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
 #define FEED_URI_ANNO NS_LITERAL_CSTRING("livemark/feedURI")
@@ -957,18 +957,18 @@ nsNavBookmarks::GetDescendantChildren(in
     // Select all children of a given folder, sorted by position.
     // This is a LEFT JOIN because not all bookmarks types have a place.
     // We construct a result where the first columns exactly match
     // kGetInfoIndex_* order, and additionally contains columns for position,
     // item_child, and folder_child from moz_bookmarks.
     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
       "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
              "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
-             "b.parent, null, h.frecency, h.hidden, h.guid, b.guid, "
-             "b.position, b.type, b.fk "
+             "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
+             "b.guid, b.position, b.type, b.fk "
       "FROM moz_bookmarks b "
       "LEFT JOIN moz_places h ON b.fk = h.id "
       "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
       "WHERE b.parent = :parent "
       "ORDER BY b.position ASC"
     );
     NS_ENSURE_STATE(stmt);
     mozStorageStatementScoper scoper(stmt);
@@ -1630,18 +1630,18 @@ nsNavBookmarks::QueryFolderChildren(
   // Select all children of a given folder, sorted by position.
   // This is a LEFT JOIN because not all bookmarks types have a place.
   // We construct a result where the first columns exactly match those returned
   // by mDBGetURLPageInfo, and additionally contains columns for position,
   // item_child, and folder_child from moz_bookmarks.
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
     "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
            "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
-           "b.parent, null, h.frecency, h.hidden, h.guid, b.guid, "
-           "b.position, b.type, b.fk "
+           "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
+           "b.guid, b.position, b.type, b.fk "
     "FROM moz_bookmarks b "
     "LEFT JOIN moz_places h ON b.fk = h.id "
     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
     "WHERE b.parent = :parent "
     "ORDER BY b.position ASC"
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
@@ -1768,18 +1768,18 @@ nsNavBookmarks::QueryFolderChildrenAsync
   // Select all children of a given folder, sorted by position.
   // This is a LEFT JOIN because not all bookmarks types have a place.
   // We construct a result where the first columns exactly match those returned
   // by mDBGetURLPageInfo, and additionally contains columns for position,
   // item_child, and folder_child from moz_bookmarks.
   nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
     "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
            "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
-           "b.parent, null, h.frecency, h.hidden, h.guid, b.guid, "
-           "b.position, b.type, b.fk "
+           "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
+           "b.guid, b.position, b.type, b.fk "
     "FROM moz_bookmarks b "
     "LEFT JOIN moz_places h ON b.fk = h.id "
     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
     "WHERE b.parent = :parent "
     "ORDER BY b.position ASC"
   );
   NS_ENSURE_STATE(stmt);
 
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -243,16 +243,25 @@ const int32_t nsNavHistory::kGetInfoInde
 const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7;
 const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8;
 const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9;
 const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10;
 const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11;
 const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12;
 const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13;
 const int32_t nsNavHistory::kGetInfoIndex_Guid = 14;
+const int32_t nsNavHistory::kGetInfoIndex_VisitId = 15;
+const int32_t nsNavHistory::kGetInfoIndex_FromVisitId = 16;
+const int32_t nsNavHistory::kGetInfoIndex_VisitType = 17;
+// These columns are followed by corresponding constants in nsNavBookmarks.cpp,
+// which must be kept in sync:
+// nsNavBookmarks::kGetChildrenIndex_Guid = 18;
+// nsNavBookmarks::kGetChildrenIndex_Position = 19;
+// nsNavBookmarks::kGetChildrenIndex_Type = 20;
+// nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
 
 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService)
 
 
 nsNavHistory::nsNavHistory()
   : mBatchLevel(0)
   , mBatchDBTransaction(nullptr)
   , mCachedNow(0)
@@ -1479,17 +1488,18 @@ PlacesSQLQueryBuilder::SelectAsURI()
       GetTagsSqlFragment(history->GetTagsFolder(),
                          NS_LITERAL_CSTRING("h.id"),
                          mHasSearchTerms,
                          tagsSqlFragment);
 
       mQueryString = NS_LITERAL_CSTRING(
         "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
         "h.last_visit_date, f.url, null, null, null, null, ") +
-        tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
+        tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+        "null, null, null "
         "FROM moz_places h "
         "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
         // WHERE 1 is a no-op since additonal conditions will start with AND.
         "WHERE 1 "
           "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
           "{ADDITIONAL_CONDITIONS} ");
       break;
 
@@ -1506,17 +1516,17 @@ PlacesSQLQueryBuilder::SelectAsURI()
                            mHasSearchTerms,
                            tagsSqlFragment);
 
         mQueryString = NS_LITERAL_CSTRING(
           "SELECT b2.fk, h.url, COALESCE(b2.title, h.title) AS page_title, "
             "h.rev_host, h.visit_count, h.last_visit_date, f.url, b2.id, "
             "b2.dateAdded, b2.lastModified, b2.parent, ") +
             tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
-            "b2.guid, b2.position, b2.type, b2.fk "
+            "null, null, null, b2.guid, b2.position, b2.type, b2.fk "
           "FROM moz_bookmarks b2 "
           "JOIN (SELECT b.fk "
                 "FROM moz_bookmarks b "
                 // ADDITIONAL_CONDITIONS will filter on parent.
                 "WHERE b.type = 1 {ADDITIONAL_CONDITIONS} "
                 ") AS seed ON b2.fk = seed.fk "
           "JOIN moz_places h ON h.id = b2.fk "
           "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
@@ -1531,17 +1541,17 @@ PlacesSQLQueryBuilder::SelectAsURI()
                            NS_LITERAL_CSTRING("b.fk"),
                            mHasSearchTerms,
                            tagsSqlFragment);
         mQueryString = NS_LITERAL_CSTRING(
           "SELECT b.fk, h.url, COALESCE(b.title, h.title) AS page_title, "
             "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
             "b.dateAdded, b.lastModified, b.parent, ") +
             tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid,"
-            "b.guid, b.position, b.type, b.fk "
+            "null, null, null, b.guid, b.position, b.type, b.fk "
           "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 "
           "WHERE NOT EXISTS "
               "(SELECT id FROM moz_bookmarks "
                 "WHERE id = b.parent AND parent = ") +
                   nsPrintfCString("%lld", history->GetTagsFolder()) +
               NS_LITERAL_CSTRING(") "
@@ -1563,17 +1573,18 @@ PlacesSQLQueryBuilder::SelectAsVisit()
   nsAutoCString tagsSqlFragment;
   GetTagsSqlFragment(history->GetTagsFolder(),
                      NS_LITERAL_CSTRING("h.id"),
                      mHasSearchTerms,
                      tagsSqlFragment);
   mQueryString = NS_LITERAL_CSTRING(
     "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
       "v.visit_date, f.url, null, null, null, null, ") +
-      tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
+      tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+      "v.id, v.from_visit, v.visit_type "
     "FROM moz_places h "
     "JOIN moz_historyvisits v ON h.id = v.place_id "
     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
     // WHERE 1 is a no-op since additonal conditions will start with AND.
     "WHERE 1 "
       "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
       "{ADDITIONAL_CONDITIONS} ");
 
@@ -1598,17 +1609,18 @@ PlacesSQLQueryBuilder::SelectAsDay()
       (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
 
   // beginTime will become the node's time property, we don't use endTime
   // because it could overlap, and we use time to sort containers and find
   // insert position in a result.
   mQueryString = nsPrintfCString(
      "SELECT null, "
        "'place:type=%ld&sort=%ld&beginTime='||beginTime||'&endTime='||endTime, "
-      "dayTitle, null, null, beginTime, null, null, null, null, null, null "
+      "dayTitle, null, null, beginTime, null, null, null, null, null, null, "
+      "null, null, null "
      "FROM (", // TOUTER BEGIN
      resultType,
      sortingMode);
 
   nsNavHistory *history = nsNavHistory::GetHistoryService();
   NS_ENSURE_STATE(history);
 
   int32_t daysOfHistory = history->GetDaysOfHistory();
@@ -1801,30 +1813,32 @@ PlacesSQLQueryBuilder::SelectAsSite()
                                        "{QUERY_OPTIONS_PLACES} "
                                        "{ADDITIONAL_CONDITIONS} ");
     timeConstraints.AssignLiteral("||'&beginTime='||:begin_time||"
                                     "'&endTime='||:end_time");
   }
 
   mQueryString = nsPrintfCString(
     "SELECT null, 'place:type=%ld&sort=%ld&domain=&domainIsHost=true'%s, "
-           ":localhost, :localhost, null, null, null, null, null, null, null "
+           ":localhost, :localhost, null, null, null, null, null, null, null, "
+           "null, null, null "
     "WHERE EXISTS ( "
       "SELECT h.id FROM moz_places h "
       "%s "
       "WHERE h.hidden = 0 "
         "AND h.visit_count > 0 "
         "AND h.url BETWEEN 'file://' AND 'file:/~' "
       "%s "
       "LIMIT 1 "
     ") "
     "UNION ALL "
     "SELECT null, "
            "'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true'%s, "
-           "host, host, null, null, null, null, null, null, null "
+           "host, host, null, null, null, null, null, null, null, "
+           "null, null, null "
     "FROM ( "
       "SELECT get_unreversed_host(h.rev_host) AS host "
       "FROM moz_places h "
       "%s "
       "WHERE h.hidden = 0 "
         "AND h.rev_host <> '.' "
         "AND h.visit_count > 0 "
         "%s "
@@ -1854,17 +1868,17 @@ PlacesSQLQueryBuilder::SelectAsTag()
 
   // This allows sorting by date fields what is not possible with
   // other history queries.
   mHasDateColumns = true;
 
   mQueryString = nsPrintfCString(
     "SELECT null, 'place:folder=' || id || '&queryType=%d&type=%ld', "
            "title, null, null, null, null, null, dateAdded, "
-           "lastModified, null, null, null "
+           "lastModified, null, null, null, null, null, null "
     "FROM moz_bookmarks "
     "WHERE parent = %lld",
     nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS,
     nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS,
     history->GetTagsFolder()
   );
 
   return NS_OK;
@@ -2088,17 +2102,18 @@ nsNavHistory::ConstructQueryString(
         nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) ||
       IsOptimizableHistoryQuery(aQueries, aOptions,
         nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) {
     // Generate an optimized query for the history menu and most visited
     // smart bookmark.
     queryString = NS_LITERAL_CSTRING(
       "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, h.last_visit_date, "
           "f.url, null, null, null, null, ") +
-          tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
+          tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+          "null, null, null "
         "FROM moz_places h "
         "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
         "WHERE h.hidden = 0 "
           "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id "
                        "AND visit_type NOT IN ") +
                        nsPrintfCString("(0,%d,%d) ",
                                        nsINavHistoryService::TRANSITION_EMBED,
                                        nsINavHistoryService::TRANSITION_FRAMED_LINK) +
@@ -3940,16 +3955,29 @@ nsNavHistory::RowToResult(mozIStorageVal
     nsAutoString tags;
     rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
     if (!tags.IsVoid())
       resultNode->mTags.Assign(tags);
 
     rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    rv = aRow->GetInt64(kGetInfoIndex_VisitId, &resultNode->mVisitId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    int64_t fromVisitId;
+    rv = aRow->GetInt64(kGetInfoIndex_FromVisitId, &fromVisitId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (fromVisitId > 0) {
+      resultNode->mFromVisitId = fromVisitId;
+    }
+
+    resultNode->mTransitionType = aRow->AsInt32(kGetInfoIndex_VisitType);
+
     resultNode.forget(aResult);
     return NS_OK;
   }
 
   return NS_ERROR_FAILURE;
 }
 
 
@@ -4048,31 +4076,33 @@ nsNavHistory::VisitIdToResultNode(int64_
   {
     case nsNavHistoryQueryOptions::RESULTS_AS_VISIT:
     case nsNavHistoryQueryOptions::RESULTS_AS_FULL_VISIT:
       // visit query - want exact visit time
       // Should match kGetInfoIndex_* (see GetQueryResults)
       statement = mDB->GetStatement(NS_LITERAL_CSTRING(
         "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
                "v.visit_date, f.url, null, null, null, null, "
-               ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
+               ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+              "v.id, v.from_visit, v.visit_type "
         "FROM moz_places h "
         "JOIN moz_historyvisits v ON h.id = v.place_id "
         "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
         "WHERE v.id = :visit_id ")
       );
       break;
 
     case nsNavHistoryQueryOptions::RESULTS_AS_URI:
       // URL results - want last visit time
       // Should match kGetInfoIndex_* (see GetQueryResults)
       statement = mDB->GetStatement(NS_LITERAL_CSTRING(
         "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
                "h.last_visit_date, f.url, null, null, null, null, "
-               ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
+               ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+              "null, null, null "
         "FROM moz_places h "
         "JOIN moz_historyvisits v ON h.id = v.place_id "
         "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
         "WHERE v.id = :visit_id ")
       );
       break;
 
     default:
@@ -4109,17 +4139,17 @@ nsNavHistory::BookmarkIdToResultNode(int
   GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
                      true, tagsFragment);
   // Should match kGetInfoIndex_*
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
       "SELECT b.fk, h.url, COALESCE(b.title, h.title), "
              "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
              "b.dateAdded, b.lastModified, b.parent, "
              ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
-             "b.guid, b.position, b.type, b.fk "
+             "null, null, null, b.guid, b.position, b.type, b.fk "
       "FROM moz_bookmarks b "
       "JOIN moz_places h ON b.fk = h.id "
       "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
       "WHERE b.id = :item_id ")
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
@@ -4150,17 +4180,17 @@ nsNavHistory::URIToResultNode(nsIURI* aU
   GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
                      true, tagsFragment);
   // Should match kGetInfoIndex_*
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
     "SELECT h.id, :page_url, COALESCE(b.title, h.title), "
            "h.rev_host, h.visit_count, h.last_visit_date, f.url, "
            "b.id, b.dateAdded, b.lastModified, b.parent, "
            ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
-           "b.guid, b.position, b.type, b.fk "
+           "null, null, null, b.guid, b.position, b.type, b.fk "
     "FROM moz_places h "
     "LEFT JOIN moz_bookmarks b ON b.fk = h.id "
     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
     "WHERE h.url = :page_url ")
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -231,16 +231,19 @@ public:
   static const int32_t kGetInfoIndex_ItemId;
   static const int32_t kGetInfoIndex_ItemDateAdded;
   static const int32_t kGetInfoIndex_ItemLastModified;
   static const int32_t kGetInfoIndex_ItemParentId;
   static const int32_t kGetInfoIndex_ItemTags;
   static const int32_t kGetInfoIndex_Frecency;
   static const int32_t kGetInfoIndex_Hidden;
   static const int32_t kGetInfoIndex_Guid;
+  static const int32_t kGetInfoIndex_VisitId;
+  static const int32_t kGetInfoIndex_FromVisitId;
+  static const int32_t kGetInfoIndex_VisitType;
 
   int64_t GetTagsFolder();
 
   // this actually executes a query and gives you results, it is used by
   // nsNavHistoryQueryResultNode
   nsresult GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
                            const nsCOMArray<nsNavHistoryQuery>& aQueries,
                            nsNavHistoryQueryOptions *aOptions,
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -95,16 +95,18 @@ nsNavHistoryResultNode::nsNavHistoryResu
   mTitle(aTitle),
   mAreTagsSorted(false),
   mAccessCount(aAccessCount),
   mTime(aTime),
   mFaviconURI(aIconURI),
   mBookmarkIndex(-1),
   mItemId(-1),
   mFolderId(-1),
+  mVisitId(-1),
+  mFromVisitId(-1),
   mDateAdded(0),
   mLastModified(0),
   mIndentLevel(-1),
   mFrecency(0),
   mHidden(false),
   mTransitionType(0)
 {
   mTags.SetIsVoid(true);
@@ -235,16 +237,37 @@ nsNavHistoryResultNode::GetPageGuid(nsAC
 
 NS_IMETHODIMP
 nsNavHistoryResultNode::GetBookmarkGuid(nsACString& aBookmarkGuid) {
   aBookmarkGuid = mBookmarkGuid;
   return NS_OK;
 }
 
 
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetVisitId(int64_t* aVisitId) {
+  *aVisitId = mVisitId;
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetFromVisitId(int64_t* aFromVisitId) {
+  *aFromVisitId = mFromVisitId;
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetVisitType(uint32_t* aVisitType) {
+  *aVisitType = mTransitionType;
+  return NS_OK;
+}
+
+
 void
 nsNavHistoryResultNode::OnRemoving()
 {
   mParent = nullptr;
 }
 
 
 /**
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -238,17 +238,23 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHisto
     { return nsNavHistoryResultNode::GetParent(aParent); } \
   NS_IMETHOD GetParentResult(nsINavHistoryResult** aResult) override \
     { return nsNavHistoryResultNode::GetParentResult(aResult); } \
   NS_IMETHOD GetTags(nsAString& aTags) override \
     { return nsNavHistoryResultNode::GetTags(aTags); } \
   NS_IMETHOD GetPageGuid(nsACString& aPageGuid) override \
     { return nsNavHistoryResultNode::GetPageGuid(aPageGuid); } \
   NS_IMETHOD GetBookmarkGuid(nsACString& aBookmarkGuid) override \
-    { return nsNavHistoryResultNode::GetBookmarkGuid(aBookmarkGuid); }
+    { return nsNavHistoryResultNode::GetBookmarkGuid(aBookmarkGuid); } \
+  NS_IMETHOD GetVisitId(int64_t* aVisitId) override \
+    { return nsNavHistoryResultNode::GetVisitId(aVisitId); } \
+  NS_IMETHOD GetFromVisitId(int64_t* aFromVisitId) override \
+    { return nsNavHistoryResultNode::GetFromVisitId(aFromVisitId); } \
+  NS_IMETHOD GetVisitType(uint32_t* aVisitType) override \
+    { return nsNavHistoryResultNode::GetVisitType(aVisitType); }
 
 class nsNavHistoryResultNode : public nsINavHistoryResultNode
 {
 public:
   nsNavHistoryResultNode(const nsACString& aURI, const nsACString& aTitle,
                          uint32_t aAccessCount, PRTime aTime,
                          const nsACString& aIconURI);
 
@@ -263,16 +269,19 @@ public:
   NS_IMETHOD GetParentResult(nsINavHistoryResult** aResult) override;
   NS_IMETHOD GetType(uint32_t* type) override
     { *type = nsNavHistoryResultNode::RESULT_TYPE_URI; return NS_OK; }
   NS_IMETHOD GetUri(nsACString& aURI) override
     { aURI = mURI; return NS_OK; }
   NS_IMETHOD GetTags(nsAString& aTags) override;
   NS_IMETHOD GetPageGuid(nsACString& aPageGuid) override;
   NS_IMETHOD GetBookmarkGuid(nsACString& aBookmarkGuid) override;
+  NS_IMETHOD GetVisitId(int64_t* aVisitId) override;
+  NS_IMETHOD GetFromVisitId(int64_t* aFromVisitId) override;
+  NS_IMETHOD GetVisitType(uint32_t* aVisitType) override;
 
   virtual void OnRemoving();
 
   // Called from result's onItemChanged, see also bookmark observer declaration in
   // nsNavHistoryFolderResultNode
   NS_IMETHOD OnItemChanged(int64_t aItemId,
                            const nsACString &aProperty,
                            bool aIsAnnotationProperty,
@@ -354,16 +363,18 @@ public:
   nsString mTags;
   bool mAreTagsSorted;
   uint32_t mAccessCount;
   int64_t mTime;
   nsCString mFaviconURI;
   int32_t mBookmarkIndex;
   int64_t mItemId;
   int64_t mFolderId;
+  int64_t mVisitId;
+  int64_t mFromVisitId;
   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.
   int32_t mIndentLevel;
 
   // Frecency of the page.  Valid only for URI nodes.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_resultsAsVisit_details.js
@@ -0,0 +1,85 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+
+const {bookmarks, history} = PlacesUtils
+
+add_task(function* test_addVisitCheckFields() {
+  let uri = NetUtil.newURI("http://test4.com/");
+  yield PlacesTestUtils.addVisits([
+    { uri },
+    { uri, referrer: uri },
+    { uri, transition: history.TRANSITION_TYPED },
+  ]);
+
+
+  let options = history.getNewQueryOptions();
+  let query = history.getNewQuery();
+
+  query.uri = uri;
+
+
+  // Check RESULTS_AS_VISIT node.
+  options.resultType = options.RESULTS_AS_VISIT;
+
+  let root = history.executeQuery(query, options).root;
+  root.containerOpen = true;
+
+  equal(root.childCount, 3);
+
+  let child = root.getChild(0);
+  equal(child.visitType, history.TRANSITION_LINK, "Visit type should be TRANSITION_LINK");
+  equal(child.visitId, 1, "Visit ID should be 1");
+  equal(child.fromVisitId, -1, "Should have no referrer visit ID");
+
+  child = root.getChild(1);
+  equal(child.visitType, history.TRANSITION_LINK, "Visit type should be TRANSITION_LINK");
+  equal(child.visitId, 2, "Visit ID should be 2");
+  equal(child.fromVisitId, 1, "First visit should be the referring visit");
+
+  child = root.getChild(2);
+  equal(child.visitType, history.TRANSITION_TYPED, "Visit type should be TRANSITION_TYPED");
+  equal(child.visitId, 3, "Visit ID should be 3");
+  equal(child.fromVisitId, -1, "Should have no referrer visit ID");
+
+  root.containerOpen = false;
+
+
+  // Check RESULTS_AS_URI node.
+  options.resultType = options.RESULTS_AS_URI;
+
+  root = history.executeQuery(query, options).root;
+  root.containerOpen = true;
+
+  equal(root.childCount, 1);
+
+  child = root.getChild(0);
+  equal(child.visitType, 0, "Visit type should be 0");
+  equal(child.visitId, -1, "Visit ID should be -1");
+  equal(child.fromVisitId, -1, "Referrer visit id should be -1");
+
+  root.containerOpen = false;
+
+  yield PlacesTestUtils.clearHistory();
+});
+
+add_task(function* test_bookmarkFields() {
+  let folder = bookmarks.createFolder(bookmarks.placesRoot, "test folder", bookmarks.DEFAULT_INDEX);
+  let bm = bookmarks.insertBookmark(folder, uri("http://test4.com/"),
+                                    bookmarks.DEFAULT_INDEX, "test4 title");
+
+  let root = PlacesUtils.getFolderContents(folder).root;
+  equal(root.childCount, 1);
+
+  equal(root.visitType, 0, "Visit type should be 0");
+  equal(root.visitId, -1, "Visit ID should be -1");
+  equal(root.fromVisitId, -1, "Referrer visit id should be -1");
+
+  let child = root.getChild(0);
+  equal(child.visitType, 0, "Visit type should be 0");
+  equal(child.visitId, -1, "Visit ID should be -1");
+  equal(child.fromVisitId, -1, "Referrer visit id should be -1");
+
+  root.containerOpen = false;
+
+  yield bookmarks.eraseEverything();
+});
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -134,16 +134,17 @@ skip-if = os == "android"
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_preventive_maintenance_runTasks.js]
 [test_promiseBookmarksTree.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_resolveNullBookmarkTitles.js]
 [test_result_sort.js]
+[test_resultsAsVisit_details.js]
 [test_sql_guid_functions.js]
 [test_svg_favicon.js]
 [test_tag_autocomplete_search.js]
 [test_tagging.js]
 [test_telemetry.js]
 [test_update_frecency_after_delete.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"