From: csagan5 <32685696+csagan5@users.noreply.github.com>
Date: Thu, 18 Feb 2021 21:22:52 +0100
Subject: Add menu item to bookmark all tabs

License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
---
 chrome/android/java/res/menu/main_menu.xml    |  7 +++
 .../chrome/browser/ChromeTabbedActivity.java  | 24 ++++++++
 .../browser/bookmarks/BookmarkBridge.java     | 61 +++++++++++++++++++
 .../browser/bookmarks/BookmarkUtils.java      |  4 ++
 .../bookmarks/android/bookmark_bridge.cc      | 17 ++++++
 .../bookmarks/android/bookmark_bridge.h       |  4 ++
 .../browser/bookmarks/bookmark_html_writer.cc |  8 ++-
 .../bookmarks/chrome_bookmark_client.cc       |  2 +
 .../dialogs/DownloadLocationCustomView.java   |  4 +-
 .../strings/android_chrome_strings.grd        |  3 +
 components/bookmark_bar_strings.grdp          |  6 ++
 .../bookmarks/browser/bookmark_codec.cc       | 24 ++++++--
 components/bookmarks/browser/bookmark_codec.h |  7 ++-
 .../browser/bookmark_load_details.cc          |  4 ++
 .../bookmarks/browser/bookmark_load_details.h |  2 +
 .../bookmarks/browser/bookmark_model.cc       |  3 +-
 components/bookmarks/browser/bookmark_model.h |  7 +++
 components/bookmarks/browser/bookmark_node.cc | 15 +++++
 components/bookmarks/browser/bookmark_node.h  |  5 ++
 components/bookmarks/browser/model_loader.cc  |  3 +-
 .../bookmark_specifics_conversions.cc         |  1 +
 21 files changed, 201 insertions(+), 10 deletions(-)

diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml
--- a/chrome/android/java/res/menu/main_menu.xml
+++ b/chrome/android/java/res/menu/main_menu.xml
@@ -66,6 +66,10 @@ found in the LICENSE file.
         <item android:id="@+id/all_bookmarks_menu_id"
             android:title="@string/menu_bookmarks"
             android:icon="@drawable/btn_star_filled" />
+        <item android:id="@+id/bookmark_all_tabs_menu_id"
+            android:title="@string/menu_bookmark_all_tabs"
+            android:titleCondensed="@string/menu_bookmark_all_tabs"
+            android:icon="@drawable/ic_folder_blue_24dp" />
         <item android:id="@+id/recent_tabs_menu_id"
             android:title="@string/menu_recent_tabs"
             android:icon="@drawable/devices_black_24dp" />
@@ -184,6 +188,9 @@ found in the LICENSE file.
         <item android:id="@+id/menu_group_tabs"
             android:title="@string/menu_group_tabs"
             android:icon="@drawable/ic_widgets" />
+        <item android:id="@+id/bookmark_all_tabs_menu_id"
+            android:title="@string/menu_bookmark_all_tabs"
+            android:icon="@drawable/ic_folder_blue_24dp" />
         <item android:id="@+id/menu_select_tabs"
             android:title="@string/menu_select_tabs"
             android:icon="@drawable/ic_select_tabs" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -77,6 +77,7 @@ import org.chromium.chrome.browser.back_press.MinimizeAppAndCloseTabBackPressHan
 import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
 import org.chromium.chrome.browser.browserservices.intents.WebappConstants;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
+import org.chromium.chrome.browser.bookmarks.BookmarkModel;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone;
@@ -2203,6 +2204,8 @@ public class ChromeTabbedActivity extends ChromeActivity<ChromeActivityComponent
             CloseAllTabsDialog.show(this, getModalDialogManagerSupplier(),
                     () -> getTabModelSelector().closeAllTabs(), /*isIncognito=*/false);
             RecordUserAction.record("MobileMenuCloseAllTabs");
+        } else if (id == R.id.bookmark_all_tabs_menu_id) {
+            bookmarkAllTabs();
         } else if (id == R.id.close_all_incognito_tabs_menu_id) {
             // Close only incognito tabs
             CloseAllTabsDialog.show(this, getModalDialogManagerSupplier(),
@@ -2255,6 +2258,27 @@ public class ChromeTabbedActivity extends ChromeActivity<ChromeActivityComponent
         mTabModalHandler.onOmniboxFocusChanged(hasFocus);
     }
 
+    private void bookmarkAllTabs() {
+        TabModel tabModel = getTabModelSelector().getCurrentModel();
+        int count = tabModel.getCount();
+        Log.i(TAG, "bookmarkAllTabs(): %d tabs to bookmark", count);
+        if (count == 0) {
+            return;
+        }
+
+        final BookmarkModel bookmarkModel = mBookmarkModelSupplier.get();
+        bookmarkModel.finishLoadingBookmarkModel(() -> {
+            for (int i = 0; i < tabModel.getCount(); i++) {
+                Tab tab = tabModel.getTabAt(i);
+                if (tab.isNativePage()) {
+                    continue;
+                }
+                bookmarkModel.addToTabsCollection(this, tab);
+            }
+            bookmarkModel.finishedAddingToTabsCollection(this, getSnackbarManager());
+        });
+    }
+
     private void recordLauncherShortcutAction(boolean isIncognito) {
         if (isIncognito) {
             RecordUserAction.record("Android.LauncherShortcut.NewIncognitoTab");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
@@ -29,6 +29,7 @@ import com.google.common.primitives.UnsignedLongs;
 import com.google.protobuf.InvalidProtocolBufferException;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
@@ -40,6 +41,10 @@ import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.read_later.ReadingListUtils;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarController;
+import org.chromium.chrome.R;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.bookmarks.BookmarkItem;
 import org.chromium.components.bookmarks.BookmarkType;
@@ -86,6 +91,8 @@ import java.io.File;
  * bookmark model stored in native.
  */
 class BookmarkBridge {
+    private static final String TAG = "BookmarkBridge";
+
     private final Profile mProfile;
     private boolean mIsDestroyed;
     private boolean mIsDoingExtensiveChanges;
@@ -393,6 +400,16 @@ class BookmarkBridge {
                 mNativeBookmarkBridge, BookmarkBridge.this);
     }
 
+    /**
+     * @return The BookmarkId for the Tabs collecction folder node
+     */
+    public BookmarkId getTabsCollectionFolderId() {
+        ThreadUtils.assertOnUiThread();
+        assert mIsNativeBookmarkModelLoaded;
+        return BookmarkBridgeJni.get().getTabsCollectionFolderId(
+                mNativeBookmarkBridge, BookmarkBridge.this);
+    }
+
     /**
      * @return Id representing the special "other" folder from bookmark model.
      */
@@ -1030,6 +1047,49 @@ class BookmarkBridge {
                 mNativeBookmarkBridge, BookmarkBridge.this, title, url);
     }
 
+    // Used to bookmark all tabs in a specific folder, created if not existing
+    public BookmarkId addToTabsCollection(Context context, Tab tab) {
+        BookmarkId parent = getTabsCollectionFolderId();
+        BookmarkId existingId = getUserBookmarkIdForTab(tab);
+        if (existingId != null && existingId.getId() != BookmarkId.INVALID_ID) {
+            BookmarkId existingBookmarkId = new BookmarkId(existingId.getId(), BookmarkType.NORMAL);
+            BookmarkItem existingBookmark = getBookmarkById(existingBookmarkId);
+            if (parent.equals(existingBookmark.getParentId())) {
+                // bookmark already exists in the tabs collection folder
+                return existingBookmarkId;
+            }
+        }
+        BookmarkId bookmarkId =
+                addBookmark(parent, getChildCount(parent), tab.getTitle(), tab.getUrl());
+
+        if (bookmarkId == null) {
+            Log.e(TAG,
+                    "Failed to add bookmarks: parentTypeAndId %s", parent);
+        }
+        return bookmarkId;
+     }
+
+     public void finishedAddingToTabsCollection(Activity activity, SnackbarManager snackbarManager) {
+        BookmarkId parent = getTabsCollectionFolderId();
+
+        BookmarkItem bookmarkItem = getBookmarkById(parent);
+        String folderName;
+        if (bookmarkItem != null) {
+            folderName = bookmarkItem.getTitle();
+        } else {
+            folderName = "";
+        }
+        SnackbarController snackbarController = new SnackbarController() {
+            @Override
+            public void onAction(Object actionData) {
+            }
+        };
+        Snackbar snackbar = Snackbar.make(folderName, snackbarController, Snackbar.TYPE_ACTION,
+                        Snackbar.UMA_BOOKMARK_ADDED)
+                        .setTemplateText(activity.getString(R.string.bookmark_page_saved_folder));
+        snackbarManager.showSnackbar(snackbar);
+    }
+
     /**
      * @param url The URL of the reading list item.
      * @return The reading list item with the URL, or null if no such reading list item.
@@ -1327,6 +1387,7 @@ class BookmarkBridge {
         void getAllFoldersWithDepths(long nativeBookmarkBridge, BookmarkBridge caller,
                 List<BookmarkId> folderList, List<Integer> depthList);
         BookmarkId getRootFolderId(long nativeBookmarkBridge, BookmarkBridge caller);
+        BookmarkId getTabsCollectionFolderId(long nativeBookmarkBridge, BookmarkBridge caller);
         BookmarkId getMobileFolderId(long nativeBookmarkBridge, BookmarkBridge caller);
         BookmarkId getOtherFolderId(long nativeBookmarkBridge, BookmarkBridge caller);
         BookmarkId getDesktopFolderId(long nativeBookmarkBridge, BookmarkBridge caller);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
@@ -648,6 +648,7 @@ public class BookmarkUtils {
         List<BookmarkId> topLevelFolders = new ArrayList<>();
         BookmarkId desktopNodeId = bookmarkModel.getDesktopFolderId();
         BookmarkId mobileNodeId = bookmarkModel.getMobileFolderId();
+        BookmarkId tabsCollectionNodeId = bookmarkModel.getTabsCollectionFolderId();
         BookmarkId othersNodeId = bookmarkModel.getOtherFolderId();
 
         List<BookmarkId> specialFoldersIds =
@@ -673,6 +674,9 @@ public class BookmarkUtils {
         if (bookmarkModel.isFolderVisible(mobileNodeId)) {
             topLevelFolders.add(mobileNodeId);
         }
+        if (bookmarkModel.isFolderVisible(tabsCollectionNodeId)) {
+            topLevelFolders.add(tabsCollectionNodeId);
+        }
         if (bookmarkModel.isFolderVisible(desktopNodeId)) {
             topLevelFolders.add(desktopNodeId);
         }
diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.cc b/chrome/browser/bookmarks/android/bookmark_bridge.cc
--- a/chrome/browser/bookmarks/android/bookmark_bridge.cc
+++ b/chrome/browser/bookmarks/android/bookmark_bridge.cc
@@ -437,6 +437,11 @@ void BookmarkBridge::GetTopLevelFolderIDs(
         top_level_folders.push_back(node.get());
     }
 
+    for (const auto& node : bookmark_model_->tabs_collection_node()->children()) {
+      if (node->is_folder())
+        top_level_folders.push_back(node.get());
+    }
+
     for (const auto& node : bookmark_model_->bookmark_bar_node()->children()) {
       if (node->is_folder())
         top_level_folders.push_back(node.get());
@@ -484,6 +489,7 @@ void BookmarkBridge::GetAllFoldersWithDepths(
   // Vector to temporarily contain all child bookmarks at same level for sorting
   std::vector<const BookmarkNode*> bookmarks = {
       bookmark_model_->mobile_node(),
+      bookmark_model_->tabs_collection_node(),
       bookmark_model_->bookmark_bar_node(),
       bookmark_model_->other_node(),
   };
@@ -536,6 +542,17 @@ ScopedJavaLocalRef<jobject> BookmarkBridge::GetMobileFolderId(
   return folder_id_obj;
 }
 
+ScopedJavaLocalRef<jobject> BookmarkBridge::GetTabsCollectionFolderId(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  const BookmarkNode* tabs_collection_node = bookmark_model_->tabs_collection_node();
+  ScopedJavaLocalRef<jobject> folder_id_obj =
+      JavaBookmarkIdCreateBookmarkId(
+          env, tabs_collection_node->id(), GetBookmarkType(tabs_collection_node));
+  return folder_id_obj;
+}
+
 ScopedJavaLocalRef<jobject> BookmarkBridge::GetOtherFolderId(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) {
diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.h b/chrome/browser/bookmarks/android/bookmark_bridge.h
--- a/chrome/browser/bookmarks/android/bookmark_bridge.h
+++ b/chrome/browser/bookmarks/android/bookmark_bridge.h
@@ -121,6 +121,10 @@ class BookmarkBridge : public bookmarks::BaseBookmarkModelObserver,
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
 
+  base::android::ScopedJavaLocalRef<jobject> GetTabsCollectionFolderId(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+
   base::android::ScopedJavaLocalRef<jobject> GetOtherFolderId(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
diff --git a/chrome/browser/bookmarks/bookmark_html_writer.cc b/chrome/browser/bookmarks/bookmark_html_writer.cc
--- a/chrome/browser/bookmarks/bookmark_html_writer.cc
+++ b/chrome/browser/bookmarks/bookmark_html_writer.cc
@@ -190,6 +190,8 @@ class Writer : public base::RefCountedThreadSafe<Writer> {
         roots->FindDictKey(BookmarkCodec::kOtherBookmarkFolderNameKey);
     base::Value* mobile_folder_value =
         roots->FindDictKey(BookmarkCodec::kMobileBookmarkFolderNameKey);
+    base::Value* tabs_collection_value =
+        roots->FindDictKey(BookmarkCodec::kTabsBookmarkFolderNameKey);
     DCHECK(root_folder_value);
     DCHECK(other_folder_value);
     DCHECK(mobile_folder_value);
@@ -201,7 +203,9 @@ class Writer : public base::RefCountedThreadSafe<Writer> {
         !WriteNode(*static_cast<base::DictionaryValue*>(other_folder_value),
                    BookmarkNode::OTHER_NODE) ||
         !WriteNode(*static_cast<base::DictionaryValue*>(mobile_folder_value),
-                   BookmarkNode::MOBILE)) {
+                   BookmarkNode::MOBILE) ||
+        !WriteNode(*static_cast<base::DictionaryValue*>(tabs_collection_value),
+                   BookmarkNode::TABS_COLLECTION)) {
       NotifyOnFinish(BookmarksExportObserver::Result::kCouldNotWriteNodes);
       return;
     }
@@ -466,6 +470,8 @@ void BookmarkFaviconFetcher::ExportBookmarks() {
       BookmarkModelFactory::GetForBrowserContext(profile_)->other_node());
   ExtractUrls(
       BookmarkModelFactory::GetForBrowserContext(profile_)->mobile_node());
+  ExtractUrls(
+      BookmarkModelFactory::GetForBrowserContext(profile_)->tabs_collection_node());
   if (!bookmark_urls_.empty())
     FetchNextFavicon();
   else
diff --git a/chrome/browser/bookmarks/chrome_bookmark_client.cc b/chrome/browser/bookmarks/chrome_bookmark_client.cc
--- a/chrome/browser/bookmarks/chrome_bookmark_client.cc
+++ b/chrome/browser/bookmarks/chrome_bookmark_client.cc
@@ -144,6 +144,8 @@ bool ChromeBookmarkClient::IsPermanentNodeVisibleWhenEmpty(
       return !is_mobile;
     case bookmarks::BookmarkNode::MOBILE:
       return is_mobile;
+    case bookmarks::BookmarkNode::TABS_COLLECTION:
+      return is_mobile;
   }
 
   return false;
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
@@ -79,7 +79,7 @@ public class DownloadLocationCustomView
         mDirectoryAdapter.update();
     }
 
-    void setTitle(CharSequence title) {
+    public void setTitle(CharSequence title) {
         mTitle.setText(title);
     }
 
@@ -87,7 +87,7 @@ public class DownloadLocationCustomView
         mSubtitleView.setText(subtitle);
     }
 
-    void setFileName(CharSequence fileName) {
+    public void setFileName(CharSequence fileName) {
         mFileName.setText(fileName);
     }
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3606,6 +3606,9 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
       <message name="IDS_MENU_TRACK_PRICES" desc="Menu item for tracking prices on tabs. [CHAR_LIMIT=27]">
         Track prices
       </message>
+      <message name="IDS_MENU_BOOKMARK_ALL_TABS" desc="Menu item to bookmark all tabs. [CHAR-LIMIT=27]">
+        Bookmark all tabs
+      </message>
       <message name="IDS_MENU_GET_IMAGE_DESCRIPTIONS" desc="Menu item to start getting alt text accessibility image labels/descriptions [CHAR_LIMIT=27]">
         Get image descriptions
       </message>
diff --git a/components/bookmark_bar_strings.grdp b/components/bookmark_bar_strings.grdp
--- a/components/bookmark_bar_strings.grdp
+++ b/components/bookmark_bar_strings.grdp
@@ -20,6 +20,9 @@
     <message name="IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME" desc="Name shown in the tree for the mobile bookmarks folder">
       Mobile bookmarks
     </message>
+    <message name="IDS_BOOKMARK_BAR_TABS_COLLECTION_FOLDER_NAME" desc="Name shown in the tree for the tabs collection bookmarks folder">
+      Tabs collection
+    </message>
     <message name="IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME" desc="Name shown in the tree for the other bookmarks folder">
       Other bookmarks
     </message>
@@ -34,6 +37,9 @@
     <message name="IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME" desc="In Title Case: Name shown in the tree for the mobile bookmarks folder">
       Mobile Bookmarks
     </message>
+    <message name="IDS_BOOKMARK_BAR_TABS_COLLECTION_FOLDER_NAME" desc="Name shown in the tree for the tabs collection bookmarks folder">
+      Tabs Collection
+    </message>
     <message name="IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME" desc="In Title Case: Name shown in the tree for the other bookmarks folder">
       Other Bookmarks
     </message>
diff --git a/components/bookmarks/browser/bookmark_codec.cc b/components/bookmarks/browser/bookmark_codec.cc
--- a/components/bookmarks/browser/bookmark_codec.cc
+++ b/components/bookmarks/browser/bookmark_codec.cc
@@ -35,6 +35,7 @@ const char BookmarkCodec::kBookmarkBarFolderNameKey[] = "bookmark_bar";
 const char BookmarkCodec::kOtherBookmarkFolderNameKey[] = "other";
 // The value is left as 'synced' for historical reasons.
 const char BookmarkCodec::kMobileBookmarkFolderNameKey[] = "synced";
+const char BookmarkCodec::kTabsBookmarkFolderNameKey[] = "tabs";
 const char BookmarkCodec::kVersionKey[] = "version";
 const char BookmarkCodec::kChecksumKey[] = "checksum";
 const char BookmarkCodec::kIdKey[] = "id";
@@ -78,7 +79,8 @@ BookmarkCodec::~BookmarkCodec() = default;
 base::Value BookmarkCodec::Encode(BookmarkModel* model,
                                   std::string sync_metadata_str) {
   return Encode(model->bookmark_bar_node(), model->other_node(),
-                model->mobile_node(), model->root_node()->GetMetaInfoMap(),
+                model->mobile_node(), model->tabs_collection_node(),
+                model->root_node()->GetMetaInfoMap(),
                 model->root_node()->GetUnsyncedMetaInfoMap(),
                 std::move(sync_metadata_str));
 }
@@ -87,6 +89,7 @@ base::Value BookmarkCodec::Encode(
     const BookmarkNode* bookmark_bar_node,
     const BookmarkNode* other_folder_node,
     const BookmarkNode* mobile_folder_node,
+    const BookmarkNode* tabs_folder_node,
     const BookmarkNode::MetaInfoMap* model_meta_info_map,
     const BookmarkNode::MetaInfoMap* model_unsynced_meta_info_map,
     std::string sync_metadata_str) {
@@ -109,6 +112,7 @@ base::Value BookmarkCodec::Encode(
   roots.SetKey(kBookmarkBarFolderNameKey, EncodeNode(bookmark_bar_node));
   roots.SetKey(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node));
   roots.SetKey(kMobileBookmarkFolderNameKey, EncodeNode(mobile_folder_node));
+  roots.SetKey(kTabsBookmarkFolderNameKey, EncodeNode(tabs_folder_node));
   if (model_meta_info_map)
     roots.SetKey(kMetaInfo, EncodeMetaInfo(*model_meta_info_map));
   if (model_unsynced_meta_info_map) {
@@ -130,6 +134,7 @@ bool BookmarkCodec::Decode(const base::Value& value,
                            BookmarkNode* bb_node,
                            BookmarkNode* other_folder_node,
                            BookmarkNode* mobile_folder_node,
+                           BookmarkNode* tabs_folder_node,
                            int64_t* max_id,
                            std::string* sync_metadata_str) {
   ids_.clear();
@@ -137,7 +142,8 @@ bool BookmarkCodec::Decode(const base::Value& value,
             base::GUID::ParseLowercase(BookmarkNode::kBookmarkBarNodeGuid),
             base::GUID::ParseLowercase(BookmarkNode::kOtherBookmarksNodeGuid),
             base::GUID::ParseLowercase(BookmarkNode::kMobileBookmarksNodeGuid),
-            base::GUID::ParseLowercase(BookmarkNode::kManagedNodeGuid)};
+            base::GUID::ParseLowercase(BookmarkNode::kManagedNodeGuid),
+            base::GUID::ParseLowercase(BookmarkNode::kTabsCollectionBookmarksNodeGuid)};
   ids_reassigned_ = false;
   guids_reassigned_ = false;
   ids_valid_ = true;
@@ -145,12 +151,13 @@ bool BookmarkCodec::Decode(const base::Value& value,
   stored_checksum_.clear();
   InitializeChecksum();
   bool success = DecodeHelper(bb_node, other_folder_node, mobile_folder_node,
+                              tabs_folder_node,
                               value, sync_metadata_str);
   FinalizeChecksum();
   // If either the checksums differ or some IDs were missing/not unique,
   // reassign IDs.
   if (!ids_valid_ || computed_checksum() != stored_checksum())
-    ReassignIDs(bb_node, other_folder_node, mobile_folder_node);
+    ReassignIDs(bb_node, other_folder_node, mobile_folder_node, tabs_folder_node);
   *max_id = maximum_id_ + 1;
   return success;
 }
@@ -207,6 +214,7 @@ base::Value BookmarkCodec::EncodeMetaInfo(
 bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node,
                                  BookmarkNode* other_folder_node,
                                  BookmarkNode* mobile_folder_node,
+                                 BookmarkNode* tabs_folder_node,
                                  const base::Value& value,
                                  std::string* sync_metadata_str) {
   if (!value.is_dict())
@@ -238,6 +246,12 @@ bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node,
     return false;
 
   DecodeNode(*bb_value, nullptr, bb_node);
+  const base::DictionaryValue* tabs_folder_d_value = nullptr;
+  const base::Value* tabs_folder_value =
+      roots->FindDictKey(kTabsBookmarkFolderNameKey);
+  if (tabs_folder_value && tabs_folder_value->GetAsDictionary(&tabs_folder_d_value))
+    DecodeNode(*tabs_folder_d_value, nullptr, tabs_folder_node);
+
   DecodeNode(*other_folder_value, nullptr, other_folder_node);
   DecodeNode(*mobile_folder_value, nullptr, mobile_folder_node);
 
@@ -508,11 +522,13 @@ void BookmarkCodec::DecodeMetaInfoHelper(
 
 void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node,
                                 BookmarkNode* other_node,
-                                BookmarkNode* mobile_node) {
+                                BookmarkNode* mobile_node,
+                                BookmarkNode* tabs_folder_node) {
   maximum_id_ = 0;
   ReassignIDsHelper(bb_node);
   ReassignIDsHelper(other_node);
   ReassignIDsHelper(mobile_node);
+  ReassignIDsHelper(tabs_folder_node);
   ids_reassigned_ = true;
 }
 
diff --git a/components/bookmarks/browser/bookmark_codec.h b/components/bookmarks/browser/bookmark_codec.h
--- a/components/bookmarks/browser/bookmark_codec.h
+++ b/components/bookmarks/browser/bookmark_codec.h
@@ -48,6 +48,7 @@ class BookmarkCodec {
   base::Value Encode(
       const BookmarkNode* bookmark_bar_node,
       const BookmarkNode* other_folder_node,
+      const BookmarkNode* tabs_folder_node,
       const BookmarkNode* mobile_folder_node,
       const BookmarkNode::MetaInfoMap* model_meta_info_map,
       const BookmarkNode::MetaInfoMap* model_unsynced_meta_info_map,
@@ -62,6 +63,7 @@ class BookmarkCodec {
               BookmarkNode* bb_node,
               BookmarkNode* other_folder_node,
               BookmarkNode* mobile_folder_node,
+              BookmarkNode* tabs_folder_node,
               int64_t* max_node_id,
               std::string* sync_metadata_str);
 
@@ -113,6 +115,7 @@ class BookmarkCodec {
   // Allows the BookmarkClient to read and a write a string blob from the JSON
   // file. That string captures the bookmarks sync metadata.
   static const char kSyncMetadata[];
+  static const char kTabsBookmarkFolderNameKey[];
   static const char kDateLastUsed[];
 
   // Possible values for kTypeKey.
@@ -130,6 +133,7 @@ class BookmarkCodec {
   bool DecodeHelper(BookmarkNode* bb_node,
                     BookmarkNode* other_folder_node,
                     BookmarkNode* mobile_folder_node,
+                    BookmarkNode* tabs_folder_node,
                     const base::Value& value,
                     std::string* sync_metadata_str);
 
@@ -141,7 +145,8 @@ class BookmarkCodec {
   // Reassigns bookmark IDs for all nodes.
   void ReassignIDs(BookmarkNode* bb_node,
                    BookmarkNode* other_node,
-                   BookmarkNode* mobile_node);
+                   BookmarkNode* mobile_node,
+                   BookmarkNode* tabs_folder_node);
 
   // Helper to recursively reassign IDs.
   void ReassignIDsHelper(BookmarkNode* node);
diff --git a/components/bookmarks/browser/bookmark_load_details.cc b/components/bookmarks/browser/bookmark_load_details.cc
--- a/components/bookmarks/browser/bookmark_load_details.cc
+++ b/components/bookmarks/browser/bookmark_load_details.cc
@@ -37,6 +37,10 @@ BookmarkLoadDetails::BookmarkLoadDetails(BookmarkClient* client)
       root_node_->Add(BookmarkPermanentNode::CreateMobileBookmarks(
           max_id_++,
           client->IsPermanentNodeVisibleWhenEmpty(BookmarkNode::MOBILE))));
+  tabs_collection_folder_node_ = static_cast<BookmarkPermanentNode*>(
+      root_node_->Add(BookmarkPermanentNode::CreateTabsCollectionBookmarks(
+          max_id_++,
+          client->IsPermanentNodeVisibleWhenEmpty(BookmarkNode::TABS_COLLECTION))));
 }
 
 BookmarkLoadDetails::~BookmarkLoadDetails() = default;
diff --git a/components/bookmarks/browser/bookmark_load_details.h b/components/bookmarks/browser/bookmark_load_details.h
--- a/components/bookmarks/browser/bookmark_load_details.h
+++ b/components/bookmarks/browser/bookmark_load_details.h
@@ -47,6 +47,7 @@ class BookmarkLoadDetails {
   BookmarkPermanentNode* bb_node() { return bb_node_; }
   BookmarkPermanentNode* mobile_folder_node() { return mobile_folder_node_; }
   BookmarkPermanentNode* other_folder_node() { return other_folder_node_; }
+  BookmarkPermanentNode* tabs_collection_folder_node() { return tabs_collection_folder_node_; }
 
   TitledUrlIndex* index() { return index_.get(); }
   std::unique_ptr<TitledUrlIndex> owned_index() { return std::move(index_); }
@@ -110,6 +111,7 @@ class BookmarkLoadDetails {
   raw_ptr<BookmarkPermanentNode> bb_node_ = nullptr;
   raw_ptr<BookmarkPermanentNode> other_folder_node_ = nullptr;
   raw_ptr<BookmarkPermanentNode> mobile_folder_node_ = nullptr;
+  BookmarkPermanentNode* tabs_collection_folder_node_ = nullptr;
   LoadManagedNodeCallback load_managed_node_callback_;
   std::unique_ptr<TitledUrlIndex> index_;
   BookmarkNode::MetaInfoMap model_meta_info_map_;
diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc
--- a/components/bookmarks/browser/bookmark_model.cc
+++ b/components/bookmarks/browser/bookmark_model.cc
@@ -670,7 +670,7 @@ bool BookmarkModel::HasBookmarks() {
 bool BookmarkModel::HasNoUserCreatedBookmarksOrFolders() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return bookmark_bar_node_->children().empty() &&
-         other_node_->children().empty() && mobile_node_->children().empty();
+         other_node_->children().empty() && mobile_node_->children().empty() && tabs_collection_node_->children().empty();
 }
 
 bool BookmarkModel::IsBookmarked(const GURL& url) {
@@ -926,6 +926,7 @@ void BookmarkModel::DoneLoading(std::unique_ptr<BookmarkLoadDetails> details) {
   bookmark_bar_node_ = details->bb_node();
   other_node_ = details->other_folder_node();
   mobile_node_ = details->mobile_folder_node();
+  tabs_collection_node_ = details->tabs_collection_folder_node();
 
   titled_url_index_->SetNodeSorter(
       std::make_unique<TypedCountSorter>(client_.get()));
diff --git a/components/bookmarks/browser/bookmark_model.h b/components/bookmarks/browser/bookmark_model.h
--- a/components/bookmarks/browser/bookmark_model.h
+++ b/components/bookmarks/browser/bookmark_model.h
@@ -122,6 +122,12 @@ class BookmarkModel : public BookmarkUndoProvider,
     return mobile_node_;
   }
 
+  // Returns the 'mobile' node. This is NULL until loaded.
+  const BookmarkNode* tabs_collection_node() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return tabs_collection_node_;
+  }
+
   bool is_root_node(const BookmarkNode* node) const {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     return node == root_;
@@ -445,6 +451,7 @@ class BookmarkModel : public BookmarkUndoProvider,
   raw_ptr<BookmarkPermanentNode> bookmark_bar_node_ = nullptr;
   raw_ptr<BookmarkPermanentNode> other_node_ = nullptr;
   raw_ptr<BookmarkPermanentNode> mobile_node_ = nullptr;
+  raw_ptr<BookmarkPermanentNode> tabs_collection_node_ = nullptr;
 
   // The maximum ID assigned to the bookmark nodes in the model.
   int64_t next_node_id_ = 1;
diff --git a/components/bookmarks/browser/bookmark_node.cc b/components/bookmarks/browser/bookmark_node.cc
--- a/components/bookmarks/browser/bookmark_node.cc
+++ b/components/bookmarks/browser/bookmark_node.cc
@@ -60,6 +60,10 @@ const char BookmarkNode::kOtherBookmarksNodeGuid[] =
 const char BookmarkNode::kMobileBookmarksNodeGuid[] =
     "4cf2e351-0e85-532b-bb37-df045d8f8d0f";
 
+// static
+const char BookmarkNode::kTabsCollectionBookmarksNodeGuid[] =
+    "00000000-0000-4000-a000-000000000006";
+
 // static
 const char BookmarkNode::kManagedNodeGuid[] =
     "323123f4-9381-5aee-80e6-ea5fca2f7672";
@@ -275,6 +279,17 @@ BookmarkPermanentNode::CreateMobileBookmarks(int64_t id,
       visible_when_empty));
 }
 
+// static
+std::unique_ptr<BookmarkPermanentNode>
+BookmarkPermanentNode::CreateTabsCollectionBookmarks(int64_t id,
+                                             bool visible_when_empty) {
+  // base::WrapUnique() used because the constructor is private.
+  return base::WrapUnique(new BookmarkPermanentNode(
+      id, TABS_COLLECTION, base::GUID::ParseLowercase(kTabsCollectionBookmarksNodeGuid),
+      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_TABS_COLLECTION_FOLDER_NAME),
+      visible_when_empty));
+}
+
 BookmarkPermanentNode::BookmarkPermanentNode(int64_t id,
                                              Type type,
                                              const base::GUID& guid,
diff --git a/components/bookmarks/browser/bookmark_node.h b/components/bookmarks/browser/bookmark_node.h
--- a/components/bookmarks/browser/bookmark_node.h
+++ b/components/bookmarks/browser/bookmark_node.h
@@ -35,6 +35,7 @@ class BookmarkNode : public ui::TreeNode<BookmarkNode>, public TitledUrlNode {
     FOLDER,
     BOOKMARK_BAR,
     OTHER_NODE,
+    TABS_COLLECTION,
     MOBILE
   };
 
@@ -52,6 +53,7 @@ class BookmarkNode : public ui::TreeNode<BookmarkNode>, public TitledUrlNode {
   static const char kBookmarkBarNodeGuid[];
   static const char kOtherBookmarksNodeGuid[];
   static const char kMobileBookmarksNodeGuid[];
+  static const char kTabsCollectionBookmarksNodeGuid[];
   static const char kManagedNodeGuid[];
 
   // A bug in sync caused some problematic GUIDs to be produced.
@@ -270,6 +272,9 @@ class BookmarkPermanentNode : public BookmarkNode {
   static std::unique_ptr<BookmarkPermanentNode> CreateMobileBookmarks(
       int64_t id,
       bool visible_when_empty);
+  static std::unique_ptr<BookmarkPermanentNode> CreateTabsCollectionBookmarks(
+      int64_t id,
+      bool visible_when_empty);
 
   // Constructor is private to disallow the construction of permanent nodes
   // other than the well-known ones, see factory methods.
diff --git a/components/bookmarks/browser/model_loader.cc b/components/bookmarks/browser/model_loader.cc
--- a/components/bookmarks/browser/model_loader.cc
+++ b/components/bookmarks/browser/model_loader.cc
@@ -58,7 +58,8 @@ void LoadBookmarks(const base::FilePath& path,
       std::string sync_metadata_str;
       BookmarkCodec codec;
       codec.Decode(*root, details->bb_node(), details->other_folder_node(),
-                   details->mobile_folder_node(), &max_node_id,
+                   details->mobile_folder_node(),
+                   details->tabs_collection_folder_node(), &max_node_id,
                    &sync_metadata_str);
       details->set_sync_metadata_str(std::move(sync_metadata_str));
       details->set_max_id(std::max(max_node_id, details->max_id()));
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions.cc b/components/sync_bookmarks/bookmark_specifics_conversions.cc
--- a/components/sync_bookmarks/bookmark_specifics_conversions.cc
+++ b/components/sync_bookmarks/bookmark_specifics_conversions.cc
@@ -446,6 +446,7 @@ sync_pb::BookmarkSpecifics::Type GetProtoTypeFromBookmarkNode(
     case bookmarks::BookmarkNode::BOOKMARK_BAR:
     case bookmarks::BookmarkNode::OTHER_NODE:
     case bookmarks::BookmarkNode::MOBILE:
+    case bookmarks::BookmarkNode::TABS_COLLECTION:
       DCHECK(node->is_folder());
       return sync_pb::BookmarkSpecifics::FOLDER;
   }
--
2.25.1
