// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "chrome/browser/sessions/chrome_tab_restore_service_client.h"
#include "chrome/browser/sessions/exit_type_service.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/sessions/session_service_utils.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/sessions/tab_restore_service_load_waiter.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/common/pref_names.h"
#include "components/os_crypt/async/browser/test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/sessions/content/content_live_tab.h"
#include "components/sessions/content/content_test_helper.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/sessions/core/serialized_navigation_entry_test_helper.h"
#include "components/sessions/core/session_types.h"
#include "components/sessions/core/tab_restore_service_client.h"
#include "components/sessions/core/tab_restore_service_impl.h"
#include "components/sessions/core/tab_restore_service_observer.h"
#include "components/split_tabs/split_tab_id.h"
#include "components/split_tabs/split_tab_visual_data.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/render_view_test.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
#include "ui/base/mojom/window_show_state.mojom.h"

typedef sessions::tab_restore::Entry Entry;
typedef sessions::tab_restore::Tab Tab;
typedef sessions::tab_restore::Window Window;
typedef std::map<std::string, std::string> ExtraData;

using content::NavigationEntry;
using content::WebContentsTester;
using sessions::ContentTestHelper;
using sessions::SerializedNavigationEntry;
using sessions::SerializedNavigationEntryTestHelper;

using ::testing::_;
using ::testing::Return;

class MockLiveTab : public sessions::LiveTab {
 public:
  MockLiveTab() = default;
  ~MockLiveTab() override = default;

  MOCK_METHOD0(IsInitialBlankNavigation, bool());
  MOCK_CONST_METHOD0(GetSessionID, SessionID());
  MOCK_METHOD0(GetCurrentEntryIndex, int());
  MOCK_METHOD0(GetPendingEntryIndex, int());
  MOCK_METHOD1(GetEntryAtIndex, sessions::SerializedNavigationEntry(int index));
  MOCK_METHOD0(GetPendingEntry, sessions::SerializedNavigationEntry());
  MOCK_METHOD0(GetEntryCount, int());
  MOCK_METHOD0(
      GetPlatformSpecificTabData,
      std::unique_ptr<sessions::tab_restore::PlatformSpecificTabData>());
  MOCK_METHOD0(GetUserAgentOverride, sessions::SerializedUserAgentOverride());
};

class MockLiveTabContext : public sessions::LiveTabContext {
 public:
  MockLiveTabContext() = default;
  ~MockLiveTabContext() override = default;

  MOCK_METHOD0(ShowBrowserWindow, void());
  MOCK_CONST_METHOD0(GetSessionID, SessionID());
  MOCK_CONST_METHOD0(GetWindowType, sessions::SessionWindow::WindowType());
  MOCK_CONST_METHOD0(GetTabCount, int());
  MOCK_CONST_METHOD0(GetSelectedIndex, int());
  MOCK_CONST_METHOD0(GetAppName, std::string());
  MOCK_CONST_METHOD0(GetUserTitle, std::string());
  MOCK_CONST_METHOD1(GetLiveTabAt, sessions::LiveTab*(int index));
  MOCK_CONST_METHOD0(GetActiveLiveTab, sessions::LiveTab*());
  MOCK_CONST_METHOD1(GetExtraDataForTab,
                     std::map<std::string, std::string>(int index));
  MOCK_CONST_METHOD0(GetExtraDataForWindow,
                     std::map<std::string, std::string>());
  MOCK_CONST_METHOD1(GetTabGroupForTab,
                     std::optional<tab_groups::TabGroupId>(int index));
  MOCK_CONST_METHOD1(GetSplitForTab,
                     std::optional<split_tabs::SplitTabId>(int index));
  MOCK_CONST_METHOD1(GetVisualDataForGroup,
                     const tab_groups::TabGroupVisualData*(
                         const tab_groups::TabGroupId& group));
  MOCK_CONST_METHOD1(GetVisualDataForSplit,
                     const split_tabs::SplitTabVisualData*(
                         const split_tabs::SplitTabId& split_id));
  MOCK_CONST_METHOD1(
      GetSavedTabGroupIdForGroup,
      const std::optional<base::Uuid>(const tab_groups::TabGroupId& group));
  MOCK_CONST_METHOD1(
      GetGroupIdForSavedGroup,
      const std::optional<tab_groups::TabGroupId>(const base::Uuid& saved));
  MOCK_CONST_METHOD1(IsTabPinned, bool(int index));
  MOCK_METHOD2(SetVisualDataForGroup,
               void(const tab_groups::TabGroupId& group,
                    const tab_groups::TabGroupVisualData& visual_data));
  MOCK_CONST_METHOD0(GetInitialFocusedTabGroup,
                     const std::optional<tab_groups::TabGroupId>());
  MOCK_METHOD1(SetFocusedTabGroup, void(const tab_groups::TabGroupId& group));
  MOCK_CONST_METHOD0(GetRestoredBounds, const gfx::Rect());
  MOCK_CONST_METHOD0(GetRestoredState, ui::mojom::WindowShowState());
  MOCK_CONST_METHOD0(GetWorkspace, std::string());
  MOCK_METHOD(sessions::LiveTab*,
              AddRestoredTab,
              ((const sessions::tab_restore::Tab&),
               int,
               bool,
               bool,
               sessions::tab_restore::Type),
              (override));
  MOCK_METHOD(sessions::LiveTab*,
              ReplaceRestoredTab,
              ((const sessions::tab_restore::Tab&)),
              (override));
  MOCK_METHOD4(ReconstructSplit,
               void(sessions::LiveTab* leading_tab,
                    sessions::LiveTab* trailing_tab,
                    split_tabs::SplitTabId split_id,
                    const split_tabs::SplitTabVisualData& visual_data));
  MOCK_METHOD0(CloseTab, void());
};

class MockTabRestoreServiceClient : public sessions::TabRestoreServiceClient {
 public:
  MockTabRestoreServiceClient() = default;
  ~MockTabRestoreServiceClient() override = default;

  MOCK_METHOD8(CreateLiveTabContext,
               sessions::LiveTabContext*(
                   sessions::LiveTabContext* existing_context,
                   sessions::SessionWindow::WindowType type,
                   const std::string& app_name,
                   const gfx::Rect& bounds,
                   ui::mojom::WindowShowState show_state,
                   const std::string& workspace,
                   const std::string& user_title,
                   const std::map<std::string, std::string>& extra_data));
  MOCK_METHOD1(FindLiveTabContextForTab,
               sessions::LiveTabContext*(const sessions::LiveTab* tab));
  MOCK_METHOD1(FindLiveTabContextWithID,
               sessions::LiveTabContext*(SessionID desired_id));
  MOCK_METHOD1(FindLiveTabContextWithGroup,
               sessions::LiveTabContext*(tab_groups::TabGroupId group));
  MOCK_METHOD1(ShouldTrackURLForRestore, bool(const GURL& url));
  MOCK_METHOD1(GetExtensionAppIDForTab, std::string(sessions::LiveTab* tab));
  MOCK_METHOD0(GetPathToSaveTo, base::FilePath());
  MOCK_METHOD0(GetNewTabURL, GURL());
  MOCK_METHOD0(HasLastSession, bool());
  MOCK_METHOD1(GetLastSession, void(sessions::GetLastSessionCallback callback));
  MOCK_METHOD1(OnTabRestored, void(const GURL& url));
};

// Create subclass that overrides TimeNow so that we can control the time used
// for closed tabs and windows.
class TabRestoreTimeFactory : public sessions::tab_restore::TimeFactory {
 public:
  TabRestoreTimeFactory() : time_(base::Time::Now()) {}

  ~TabRestoreTimeFactory() override = default;

  base::Time TimeNow() override { return time_; }

 private:
  base::Time time_;
};

class TabRestoreServiceImplTest : public ChromeRenderViewHostTestHarness {
 public:
  TabRestoreServiceImplTest()
      : url1_("http://1"),
        url2_("http://2"),
        url3_("http://3"),
        user_agent_override_(blink::UserAgentOverride::UserAgentOnly(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.19"
            " (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19")),
        time_factory_(nullptr),
        window_id_(SessionID::FromSerializedValue(1)),
        tab_id_(SessionID::FromSerializedValue(2)) {
    os_crypt_async_ = os_crypt_async::GetTestOSCryptAsyncForTesting(true);
    user_agent_override_.ua_metadata_override.emplace();
    user_agent_override_.ua_metadata_override->brand_version_list.emplace_back(
        "Chrome", "18");
    user_agent_override_.ua_metadata_override->brand_full_version_list
        .emplace_back("Chrome", "18.0.1025.45");
    user_agent_override_.ua_metadata_override->full_version = "18.0.1025.45";
    user_agent_override_.ua_metadata_override->platform = "Linux";
    user_agent_override_.ua_metadata_override->architecture = "x86_64";
    user_agent_override_.ua_metadata_override->bitness = "32";
  }

  ~TabRestoreServiceImplTest() override = default;

  SessionID tab_id() const { return tab_id_; }
  SessionID window_id() const { return window_id_; }

 protected:
  enum {
    kMaxEntries = sessions::TabRestoreServiceHelper::kMaxEntries,
  };

  // testing::Test:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    CreateSessionServiceTabHelper(web_contents());
    live_tab_ = base::WrapUnique(new sessions::ContentLiveTab(web_contents()));
    time_factory_ = new TabRestoreTimeFactory();

    CreateService();
  }

  void TearDown() override {
    service_->Shutdown();
    service_.reset();
    delete time_factory_;
    ChromeRenderViewHostTestHarness::TearDown();
  }

  sessions::TabRestoreService::Entries* mutable_entries() {
    return service_->mutable_entries();
  }

  void PruneEntries() { service_->PruneEntries(); }

  void AddThreeNavigations() {
    // Navigate to three URLs.
    NavigateAndCommit(url1_);
    NavigateAndCommit(url2_);
    NavigateAndCommit(url3_);
  }

  void NavigateToIndex(int index) {
    // Navigate back. We have to do this song and dance as NavigationController
    // isn't happy if you navigate immediately while going back.
    controller().GoToIndex(index);
    WebContentsTester::For(web_contents())->CommitPendingNavigation();
  }

  virtual void CreateService() {
    service_ = std::make_unique<sessions::TabRestoreServiceImpl>(
        std::make_unique<ChromeTabRestoreServiceClient>(profile()),
        profile()->GetPrefs(), time_factory_, os_crypt_async_.get());
  }

  void RecreateService() {
    // Must set service to null first so that it is destroyed before the new
    // one is created.
    service_->Shutdown();
    content::RunAllTasksUntilIdle();
    service_.reset();

    CreateService();
    SynchronousLoadTabsFromLastSession();
  }

  // Adds a window with one tab and url to the profile's session
  // service. If |pinned| is true, the tab is marked as pinned in the
  // session service. If |group| is present, sets the tab's group ID. If
  // |group_visual_data| is also present, sets |group|'s visual data.
  void AddWindowWithOneTabToSessionService(
      bool pinned,
      std::optional<tab_groups::TabGroupId> group = std::nullopt,
      std::optional<tab_groups::TabGroupVisualData> group_visual_data =
          std::nullopt,
      std::optional<ExtraData> extra_data = std::nullopt) {
    // Create new window / tab IDs so that these remain distinct.
    window_id_ = SessionID::NewUnique();
    tab_id_ = SessionID::NewUnique();

    SessionService* session_service =
        SessionServiceFactory::GetForProfile(profile());
    session_service->SetWindowType(window_id(),
                                   BrowserWindowInterface::TYPE_NORMAL);
    session_service->SetTabWindow(window_id(), tab_id());
    session_service->SetTabIndexInWindow(window_id(), tab_id(), 0);
    session_service->SetSelectedTabInWindow(window_id(), 0);
    if (pinned)
      session_service->SetPinnedState(window_id(), tab_id(), true);
    if (group)
      session_service->SetTabGroup(window_id(), tab_id(), group);
    if (group && group_visual_data)
      session_service->SetTabGroupMetadata(window_id(), *group,
                                           &*group_visual_data);

    session_service->UpdateTabNavigation(
        window_id(), tab_id(),
        ContentTestHelper::CreateNavigation(url1_.spec(), "title"));
  }

  // Creates a SessionService and assigns it to the Profile. The SessionService
  // is configured with a single window with a single tab pointing at url1_ by
  // way of AddWindowWithOneTabToSessionService. If |pinned| is true, the
  // tab is marked as pinned in the session service.
  void CreateSessionServiceWithOneWindow(bool pinned) {
    std::unique_ptr<SessionService> session_service(
        new SessionService(profile()));
    SessionServiceFactory::SetForTestProfile(profile(),
                                             std::move(session_service));

    AddWindowWithOneTabToSessionService(pinned);

    // Set this, otherwise previous session won't be loaded.
    ExitTypeService::GetInstanceForProfile(profile())
        ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
  }

  void SynchronousLoadTabsFromLastSession() {
    // Ensures that the load is complete before continuing.
    TabRestoreServiceLoadWaiter waiter(service_.get());
    service_->LoadTabsFromLastSession();
    waiter.Wait();
  }

  sessions::LiveTab* live_tab() { return live_tab_.get(); }

  GURL url1_;
  GURL url2_;
  GURL url3_;
  blink::UserAgentOverride user_agent_override_;
  std::unique_ptr<sessions::LiveTab> live_tab_;
  std::unique_ptr<sessions::TabRestoreServiceImpl> service_;
  raw_ptr<TabRestoreTimeFactory, DanglingUntriaged> time_factory_;
  std::unique_ptr<os_crypt_async::OSCryptAsync> os_crypt_async_;
  SessionID window_id_;
  SessionID tab_id_;
};

class TabRestoreServiceImplWithMockClientTest
    : public TabRestoreServiceImplTest {
 public:
  TabRestoreServiceImplWithMockClientTest() = default;
  ~TabRestoreServiceImplWithMockClientTest() override = default;

 protected:
  void CreateService() override {
    std::unique_ptr<MockTabRestoreServiceClient> service_client =
        std::make_unique<testing::NiceMock<MockTabRestoreServiceClient>>();
    mock_tab_restore_service_client_ = service_client.get();
    ON_CALL(*mock_tab_restore_service_client_, GetPathToSaveTo())
        .WillByDefault(Return(profile()->GetPath()));

    service_ = std::make_unique<sessions::TabRestoreServiceImpl>(
        std::move(service_client), profile()->GetPrefs(), time_factory_,
        os_crypt_async_.get());
  }

  void SetupMockSplit(MockLiveTabContext* context,
                      MockLiveTab* tab1,
                      MockLiveTab* tab2,
                      const GURL& url1,
                      const GURL& url2,
                      const split_tabs::SplitTabId& split_id) {
    SessionID id1 = SessionID::NewUnique();
    SessionID id2 = SessionID::NewUnique();

    ON_CALL(*context, GetSessionID())
        .WillByDefault(Return(SessionID::NewUnique()));
    ON_CALL(*context, GetTabCount()).WillByDefault(Return(2));

    ON_CALL(*tab1, GetSessionID()).WillByDefault(Return(id1));
    ON_CALL(*tab2, GetSessionID()).WillByDefault(Return(id2));

    ON_CALL(*tab1, GetEntryCount()).WillByDefault(Return(1));
    ON_CALL(*tab2, GetEntryCount()).WillByDefault(Return(1));
    ON_CALL(*tab1, GetEntryAtIndex(_))
        .WillByDefault(Return(
            sessions::ContentTestHelper::CreateNavigation(url1.spec(), "T1")));
    ON_CALL(*tab2, GetEntryAtIndex(_))
        .WillByDefault(Return(
            sessions::ContentTestHelper::CreateNavigation(url2.spec(), "T2")));

    ON_CALL(*context, GetLiveTabAt(0)).WillByDefault(Return(tab1));
    ON_CALL(*context, GetLiveTabAt(1)).WillByDefault(Return(tab2));
    ON_CALL(*context, GetSplitForTab(_)).WillByDefault(Return(split_id));

    ON_CALL(*mock_tab_restore_service_client_, FindLiveTabContextForTab(_))
        .WillByDefault(Return(context));
  }

  raw_ptr<MockTabRestoreServiceClient, DanglingUntriaged>
      mock_tab_restore_service_client_;
};

TEST_F(TabRestoreServiceImplTest, Basic) {
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  // Make sure an entry was created.
  ASSERT_EQ(1U, service_->entries().size());

  // Make sure the entry matches.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  EXPECT_FALSE(tab->pinned);
  EXPECT_TRUE(tab->extension_app_id.empty());
  ASSERT_EQ(3U, tab->navigations.size());
  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
  EXPECT_EQ("", tab->user_agent_override.ua_string_override);
  EXPECT_TRUE(!blink::UserAgentMetadata::Demarshal(
                   tab->user_agent_override.opaque_ua_metadata_override)
                   .has_value());
  EXPECT_EQ(2, tab->current_navigation_index);
  EXPECT_EQ(
      time_factory_->TimeNow().ToDeltaSinceWindowsEpoch().InMicroseconds(),
      tab->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());

  NavigateToIndex(1);

  // And check again, but set the user agent override this time.
  web_contents()->SetUserAgentOverride(user_agent_override_, false);
  service_->CreateHistoricalTab(live_tab(), -1);

  // There should be two entries now.
  ASSERT_EQ(2U, service_->entries().size());

  // Make sure the entry matches.
  entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  tab = static_cast<Tab*>(entry);
  EXPECT_FALSE(tab->pinned);
  ASSERT_EQ(3U, tab->navigations.size());
  EXPECT_EQ(url1_, tab->navigations[0].virtual_url());
  EXPECT_EQ(url2_, tab->navigations[1].virtual_url());
  EXPECT_EQ(url3_, tab->navigations[2].virtual_url());
  EXPECT_EQ(user_agent_override_.ua_string_override,
            tab->user_agent_override.ua_string_override);
  std::optional<blink::UserAgentMetadata> client_hints_override =
      blink::UserAgentMetadata::Demarshal(
          tab->user_agent_override.opaque_ua_metadata_override);
  EXPECT_EQ(user_agent_override_.ua_metadata_override, client_hints_override);
  EXPECT_EQ(1, tab->current_navigation_index);
  EXPECT_EQ(
      time_factory_->TimeNow().ToDeltaSinceWindowsEpoch().InMicroseconds(),
      tab->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
}

TEST_F(TabRestoreServiceImplWithMockClientTest,
       TabExtraDataPresentInHistoricalTab) {
  constexpr char kSampleKey[] = "test";
  constexpr char kSampleValue[] = "true";

  std::unique_ptr<MockLiveTabContext> mock_live_tab_context_ptr(
      new ::testing::NiceMock<MockLiveTabContext>());
  SessionID sample_session_id = SessionID::NewUnique();
  EXPECT_CALL(*mock_live_tab_context_ptr, GetSessionID)
      .WillOnce(Return(sample_session_id));
  EXPECT_CALL(*mock_live_tab_context_ptr, GetExtraDataForTab)
      .WillOnce([kSampleKey, kSampleValue]() {
        std::map<std::string, std::string> sample_extra_data;
        sample_extra_data[kSampleKey] = kSampleValue;
        return sample_extra_data;
      });
  ON_CALL(*mock_tab_restore_service_client_, FindLiveTabContextForTab(_))
      .WillByDefault(Return(mock_live_tab_context_ptr.get()));
  ON_CALL(*mock_tab_restore_service_client_, GetNewTabURL())
      .WillByDefault(Return(GURL("https://www.google.com")));

  NavigateAndCommit(url1_);
  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  // Make sure an entry was created.
  ASSERT_EQ(1U, service_->entries().size());
  // Make sure the entry data matches.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  ASSERT_EQ(1U, tab->navigations.size());
  EXPECT_EQ(url1_, tab->navigations[0].virtual_url());
  ASSERT_EQ(1U, tab->extra_data.size());
  ASSERT_EQ(kSampleValue, tab->extra_data[kSampleKey]);
}

// Ensure fields are written and read from saved state.
TEST_F(TabRestoreServiceImplWithMockClientTest, WindowRestore) {
  ON_CALL(*mock_tab_restore_service_client_, ShouldTrackURLForRestore(_))
      .WillByDefault(Return(true));

  SerializedNavigationEntry navigation_entry =
      SerializedNavigationEntryTestHelper::CreateNavigationForTest();
  testing::NiceMock<MockLiveTab> mock_live_tab;
  ON_CALL(mock_live_tab, GetSessionID)
      .WillByDefault(Return(SessionID::NewUnique()));
  ON_CALL(mock_live_tab, GetEntryCount).WillByDefault(Return(1));
  ON_CALL(mock_live_tab, GetEntryAtIndex)
      .WillByDefault(Return(navigation_entry));

  testing::NiceMock<MockLiveTabContext> mock_live_tab_context;
  SessionID session_id = SessionID::NewUnique();
  ON_CALL(mock_live_tab_context, GetSessionID)
      .WillByDefault(Return(session_id));
  ON_CALL(mock_live_tab_context, GetWindowType)
      .WillByDefault(Return(sessions::SessionWindow::TYPE_APP_POPUP));
  ON_CALL(mock_live_tab_context, GetAppName).WillByDefault(Return("app-name"));
  ON_CALL(mock_live_tab_context, GetUserTitle)
      .WillByDefault(Return("user-title"));
  ON_CALL(mock_live_tab_context, GetRestoredBounds)
      .WillByDefault(Return(gfx::Rect(10, 20, 30, 40)));
  ON_CALL(mock_live_tab_context, GetRestoredState)
      .WillByDefault(Return(ui::mojom::WindowShowState::kMaximized));
  ON_CALL(mock_live_tab_context, GetWorkspace)
      .WillByDefault(Return("workspace"));
  ON_CALL(mock_live_tab_context, GetTabCount).WillByDefault(Return(1));
  ON_CALL(mock_live_tab_context, GetLiveTabAt)
      .WillByDefault(Return(&mock_live_tab));

  service_->BrowserClosing(&mock_live_tab_context);

  // Validate while entries are in memory.
  auto validate = [&]() {
    ASSERT_EQ(1u, service_->entries().size());
    Entry* entry = service_->entries().front().get();
    EXPECT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);
    Window* window = static_cast<Window*>(entry);
    EXPECT_EQ(sessions::SessionWindow::TYPE_APP_POPUP, window->window_type);
    EXPECT_EQ(0, window->selected_tab_index);
    EXPECT_EQ("app-name", window->app_name);
    EXPECT_EQ("user-title", window->user_title);
    EXPECT_EQ(gfx::Rect(10, 20, 30, 40), window->bounds);
    EXPECT_EQ(ui::mojom::WindowShowState::kMaximized, window->show_state);
    EXPECT_EQ("workspace", window->workspace);
  };
  validate();

  // Validate after persisting and reading from storage.
  RecreateService();
  validate();
}

// Make sure TabRestoreService doesn't create an entry for a tab with no
// navigations.
TEST_F(TabRestoreServiceImplTest, DontCreateEmptyTab) {
  service_->CreateHistoricalTab(live_tab(), -1);
  EXPECT_TRUE(service_->entries().empty());
}

// Tests restoring a single tab.
TEST_F(TabRestoreServiceImplTest, Restore) {
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);
  EXPECT_EQ(1U, service_->entries().size());

  // Recreate the service and have it load the tabs.
  RecreateService();

  // One entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  // And verify the entry.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  EXPECT_FALSE(tab->pinned);
  ASSERT_EQ(3U, tab->navigations.size());
  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
  EXPECT_EQ(2, tab->current_navigation_index);
  EXPECT_EQ(
      time_factory_->TimeNow().ToDeltaSinceWindowsEpoch().InMicroseconds(),
      tab->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
}

// Tests restoring a tab with more than gMaxPersistNavigationCount entries.
TEST_F(TabRestoreServiceImplTest, RestoreManyNavigations) {
  AddThreeNavigations();
  AddThreeNavigations();
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  // Recreate the service and have it load the tabs.
  RecreateService();

  // One entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  // And verify the entry.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  // Only gMaxPersistNavigationCount + 1 (current navigation) are persisted.
  ASSERT_EQ(7U, tab->navigations.size());
  // Check that they are created with correct indices.
  EXPECT_EQ(0, tab->navigations[0].index());
  EXPECT_EQ(6, tab->navigations[6].index());
  EXPECT_EQ(6, tab->current_navigation_index);
}

// Tests restoring a single pinned tab.
TEST_F(TabRestoreServiceImplTest, RestorePinnedAndApp) {
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  // One entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  // We have to explicitly mark the tab as pinned as there is no browser for
  // these tests.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  tab->pinned = true;
  const std::string extension_app_id("test");
  tab->extension_app_id = extension_app_id;

  // Recreate the service and have it load the tabs.
  RecreateService();

  // One entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  // And verify the entry.
  entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  tab = static_cast<Tab*>(entry);
  EXPECT_TRUE(tab->pinned);
  ASSERT_EQ(3U, tab->navigations.size());
  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
  EXPECT_EQ(2, tab->current_navigation_index);
  EXPECT_TRUE(extension_app_id == tab->extension_app_id);
}

// Make sure TabRestoreService doesn't create a restored entry.
TEST_F(TabRestoreServiceImplTest, DontCreateRestoredEntry) {
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);
  EXPECT_EQ(1U, service_->entries().size());

  // Record the tab's id.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  SessionID first_id = entry->id;

  // Service record the second tab.
  service_->CreateHistoricalTab(live_tab(), -1);
  EXPECT_EQ(2U, service_->entries().size());

  // Record the tab's id.
  entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  SessionID second_id = entry->id;

  service_->Shutdown();

  // Add a restored entry command
  service_->CreateRestoredEntryCommandForTest(second_id);

  // Recreate the service
  RecreateService();

  // Only one entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  // And verify the entry.
  entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  ASSERT_EQ(first_id, entry->original_id);
}

// Tests deleting entries.
TEST_F(TabRestoreServiceImplTest, DeleteNavigationEntries) {
  SynchronousLoadTabsFromLastSession();
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  service_->DeleteNavigationEntries(
      base::BindLambdaForTesting([&](const SerializedNavigationEntry& entry) {
        return entry.virtual_url() == url2_;
      }));

  // The entry should still exist but url2_ was removed and indices adjusted.
  ASSERT_EQ(1U, service_->entries().size());
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  ASSERT_EQ(2U, tab->navigations.size());
  EXPECT_EQ(url1_, tab->navigations[0].virtual_url());
  EXPECT_EQ(0, tab->navigations[0].index());
  EXPECT_EQ(url3_, tab->navigations[1].virtual_url());
  EXPECT_EQ(1, tab->navigations[1].index());
  EXPECT_EQ(1, tab->current_navigation_index);

  service_->DeleteNavigationEntries(base::BindRepeating(
      [](const SerializedNavigationEntry& entry) { return true; }));

  // The entry should be removed.
  EXPECT_EQ(0U, service_->entries().size());
}

// Tests deleting entries.
TEST_F(TabRestoreServiceImplTest, DeleteCurrentEntry) {
  SynchronousLoadTabsFromLastSession();
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  service_->DeleteNavigationEntries(
      base::BindLambdaForTesting([&](const SerializedNavigationEntry& entry) {
        return entry.virtual_url() == url3_;
      }));

  // The entry should be deleted because the current url was deleted.
  EXPECT_EQ(0U, service_->entries().size());
}

// Tests deleting entries.
TEST_F(TabRestoreServiceImplTest, DeleteEntriesAndRecreate) {
  SynchronousLoadTabsFromLastSession();
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  // Delete the navigation for url2_.
  service_->DeleteNavigationEntries(
      base::BindLambdaForTesting([&](const SerializedNavigationEntry& entry) {
        return entry.virtual_url() == url2_;
      }));
  // Recreate the service and have it load the tabs.
  RecreateService();
  // The entry should still exist but url2_ was removed and indices adjusted.
  ASSERT_EQ(1U, service_->entries().size());
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  ASSERT_EQ(2U, tab->navigations.size());
  EXPECT_EQ(url1_, tab->navigations[0].virtual_url());
  EXPECT_EQ(0, tab->navigations[0].index());
  EXPECT_EQ(url3_, tab->navigations[1].virtual_url());
  EXPECT_EQ(1, tab->navigations[1].index());
  EXPECT_EQ(1, tab->current_navigation_index);

  // Delete all entries.
  service_->DeleteNavigationEntries(base::BindRepeating(
      [](const SerializedNavigationEntry& entry) { return true; }));
  // Recreate the service and have it load the tabs.
  RecreateService();
  // The entry should be removed.
  ASSERT_EQ(0U, service_->entries().size());
}

// Make sure we persist entries to disk that have post data.
TEST_F(TabRestoreServiceImplTest, DontPersistPostData) {
  AddThreeNavigations();
  controller().GetEntryAtIndex(0)->SetHasPostData(true);
  controller().GetEntryAtIndex(1)->SetHasPostData(true);
  controller().GetEntryAtIndex(2)->SetHasPostData(true);

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);
  ASSERT_EQ(1U, service_->entries().size());

  // Recreate the service and have it load the tabs.
  RecreateService();

  // One entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  const Entry* restored_entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, restored_entry->type);

  const Tab* restored_tab = static_cast<const Tab*>(restored_entry);
  // There should be 3 navs.
  ASSERT_EQ(3U, restored_tab->navigations.size());
  EXPECT_EQ(
      time_factory_->TimeNow().ToDeltaSinceWindowsEpoch().InMicroseconds(),
      restored_tab->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
}

// Make sure we don't persist entries to disk that have post data. This
// differs from DontPersistPostData1 in that all the navigations have post
// data, so that nothing should be persisted.
TEST_F(TabRestoreServiceImplTest, DontLoadTwice) {
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);
  ASSERT_EQ(1U, service_->entries().size());

  // Recreate the service and have it load the tabs.
  RecreateService();

  SynchronousLoadTabsFromLastSession();

  // There should only be one entry.
  ASSERT_EQ(1U, service_->entries().size());
}

// Makes sure we load the previous session as necessary.
TEST_F(TabRestoreServiceImplTest, LoadPreviousSession) {
  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  EXPECT_FALSE(service_->IsLoaded());

  SynchronousLoadTabsFromLastSession();

  // Make sure we get back one entry with one tab whose url is url1.
  ASSERT_EQ(1U, service_->entries().size());
  Entry* entry2 = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry2->type);
  sessions::tab_restore::Window* window =
      static_cast<sessions::tab_restore::Window*>(entry2);
  EXPECT_EQ(sessions::SessionWindow::TYPE_NORMAL, window->window_type);
  ASSERT_EQ(1U, window->tabs.size());
  EXPECT_EQ(0, window->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  EXPECT_EQ(0, window->selected_tab_index);
  ASSERT_EQ(1U, window->tabs[0]->navigations.size());
  EXPECT_EQ(0, window->tabs[0]->current_navigation_index);
  EXPECT_EQ(
      0,
      window->tabs[0]->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  EXPECT_TRUE(url1_ == window->tabs[0]->navigations[0].virtual_url());
}

// Makes sure we don't attempt to load previous sessions after a restore.
TEST_F(TabRestoreServiceImplTest, DontLoadAfterRestore) {
  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  profile()->set_restored_last_session(true);

  SynchronousLoadTabsFromLastSession();

  // Because we restored a session TabRestoreService shouldn't load the tabs.
  ASSERT_EQ(0U, service_->entries().size());
}

// Makes sure we don't attempt to load previous sessions after a clean exit.
TEST_F(TabRestoreServiceImplTest, DontLoadAfterCleanExit) {
  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  ExitTypeService::GetInstanceForProfile(profile())
      ->SetLastSessionExitTypeForTest(ExitType::kClean);

  SynchronousLoadTabsFromLastSession();

  ASSERT_EQ(0U, service_->entries().size());
}

// Makes sure we don't save sessions when saving history is disabled.
TEST_F(TabRestoreServiceImplTest, DontSaveWhenSavingIsDisabled) {
  profile()->GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled, true);

  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  SynchronousLoadTabsFromLastSession();

  ASSERT_EQ(0U, service_->entries().size());
}

// Makes sure we don't attempt to load previous sessions when saving history is
// disabled.
TEST_F(TabRestoreServiceImplTest, DontLoadWhenSavingIsDisabled) {
  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  profile()->GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled, true);

  SynchronousLoadTabsFromLastSession();

  ASSERT_EQ(0U, service_->entries().size());
}

// Makes sure toggling the policy mid-session immediately clears temporary
// in-memory tab restore entries and prevents subsequent entries.
TEST_F(TabRestoreServiceImplTest,
       SavingBrowserHistoryDisabledMidSessionClearsEntries) {
  AddThreeNavigations();

  // Have the service record a closed tab.
  service_->CreateHistoricalTab(live_tab(), -1);
  EXPECT_EQ(1U, service_->entries().size());

  // Enable the policy mid-session.
  profile()->GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled, true);

  // Temporary restore entries should be wiped immediately from memory for
  // privacy.
  EXPECT_EQ(0U, service_->entries().size());

  // Attempt to record another historical tab.
  AddThreeNavigations();
  std::optional<SessionID> result =
      service_->CreateHistoricalTab(live_tab(), -1);

  // New tab restores should be blocked.
  EXPECT_EQ(std::nullopt, result);
  EXPECT_EQ(0U, service_->entries().size());
}

// Regression test to ensure Window::show_state is set correctly when reading
// TabRestoreSession from saved state.
TEST_F(TabRestoreServiceImplTest, WindowShowStateIsSet) {
  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  SynchronousLoadTabsFromLastSession();

  RecreateService();

  // There should be at least one window and its show state should be the
  // default.
  bool got_window = false;
  for (auto& entry : service_->entries()) {
    if (entry->type == sessions::tab_restore::Type::WINDOW) {
      got_window = true;
      Window* window = static_cast<Window*>(entry.get());
      EXPECT_EQ(window->show_state, ui::mojom::WindowShowState::kDefault);
    }
  }
  EXPECT_TRUE(got_window);
}

TEST_F(TabRestoreServiceImplTest, LoadPreviousSessionAndTabs) {
  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  AddThreeNavigations();

  service_->CreateHistoricalTab(live_tab(), -1);

  RecreateService();

  // We should get back two entries, one from the previous session and one from
  // the tab restore service. The previous session entry should be first.
  ASSERT_EQ(2U, service_->entries().size());
  // The first entry should come from the session service.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);
  sessions::tab_restore::Window* window =
      static_cast<sessions::tab_restore::Window*>(entry);
  ASSERT_EQ(1U, window->tabs.size());
  EXPECT_EQ(0, window->selected_tab_index);
  EXPECT_EQ(0, window->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  ASSERT_EQ(1U, window->tabs[0]->navigations.size());
  EXPECT_EQ(0, window->tabs[0]->current_navigation_index);
  EXPECT_EQ(
      0,
      window->tabs[0]->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  EXPECT_TRUE(url1_ == window->tabs[0]->navigations[0].virtual_url());

  // Then the closed tab.
  entry = (++service_->entries().begin())->get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  ASSERT_FALSE(tab->pinned);
  ASSERT_EQ(3U, tab->navigations.size());
  EXPECT_EQ(2, tab->current_navigation_index);
  EXPECT_EQ(
      time_factory_->TimeNow().ToDeltaSinceWindowsEpoch().InMicroseconds(),
      tab->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
}

// Make sure window bounds and workspace are properly loaded from the session
// service.
TEST_F(TabRestoreServiceImplTest, LoadWindowBoundsAndWorkspace) {
  constexpr gfx::Rect kBounds(10, 20, 640, 480);
  constexpr ui::mojom::WindowShowState kShowState =
      ui::mojom::WindowShowState::kMinimized;
  constexpr char kWorkspace[] = "workspace";

  CreateSessionServiceWithOneWindow(false);

  // Set the bounds, show state and workspace.
  SessionService* session_service =
      SessionServiceFactory::GetForProfile(profile());
  session_service->SetWindowBounds(window_id(), kBounds, kShowState);
  session_service->SetWindowWorkspace(window_id(), kWorkspace);

  session_service->MoveCurrentSessionToLastSession();

  AddThreeNavigations();

  service_->CreateHistoricalTab(live_tab(), -1);

  RecreateService();

  // We should get back two entries, one from the previous session and one from
  // the tab restore service. The previous session entry should be first.
  ASSERT_EQ(2U, service_->entries().size());

  // The first entry should come from the session service.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);
  sessions::tab_restore::Window* window =
      static_cast<sessions::tab_restore::Window*>(entry);
  ASSERT_EQ(kBounds, window->bounds);
  ASSERT_EQ(kShowState, window->show_state);
  ASSERT_EQ(kWorkspace, window->workspace);
  ASSERT_EQ(1U, window->tabs.size());
  EXPECT_EQ(0, window->selected_tab_index);
  EXPECT_FALSE(window->tabs[0]->pinned);
  ASSERT_EQ(1U, window->tabs[0]->navigations.size());
  EXPECT_EQ(0, window->tabs[0]->current_navigation_index);
  EXPECT_TRUE(url1_ == window->tabs[0]->navigations[0].virtual_url());

  // Then the closed tab.
  entry = (++service_->entries().begin())->get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  ASSERT_FALSE(tab->pinned);
  ASSERT_EQ(3U, tab->navigations.size());
  EXPECT_EQ(2, tab->current_navigation_index);
  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
}

// Make sure pinned state is correctly loaded from session service.
TEST_F(TabRestoreServiceImplTest, LoadPreviousSessionAndTabsPinned) {
  CreateSessionServiceWithOneWindow(true);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  AddThreeNavigations();

  service_->CreateHistoricalTab(live_tab(), -1);

  RecreateService();

  // We should get back two entries, one from the previous session and one from
  // the tab restore service. The previous session entry should be first.
  ASSERT_EQ(2U, service_->entries().size());
  // The first entry should come from the session service.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);
  sessions::tab_restore::Window* window =
      static_cast<sessions::tab_restore::Window*>(entry);
  ASSERT_EQ(1U, window->tabs.size());
  EXPECT_EQ(0, window->selected_tab_index);
  EXPECT_TRUE(window->tabs[0]->pinned);
  ASSERT_EQ(1U, window->tabs[0]->navigations.size());
  EXPECT_EQ(0, window->tabs[0]->current_navigation_index);
  EXPECT_TRUE(url1_ == window->tabs[0]->navigations[0].virtual_url());

  // Then the closed tab.
  entry = (++service_->entries().begin())->get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
  Tab* tab = static_cast<Tab*>(entry);
  ASSERT_FALSE(tab->pinned);
  ASSERT_EQ(3U, tab->navigations.size());
  EXPECT_EQ(2, tab->current_navigation_index);
  EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
  EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
  EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
}

// Creates kMaxEntries + 1 windows in the session service and makes sure we only
// get back kMaxEntries on restore.
TEST_F(TabRestoreServiceImplTest, ManyWindowsInSessionService) {
  CreateSessionServiceWithOneWindow(false);

  for (size_t i = 0; i < kMaxEntries; ++i)
    AddWindowWithOneTabToSessionService(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  AddThreeNavigations();

  service_->CreateHistoricalTab(live_tab(), -1);

  RecreateService();

  // We should get back kMaxEntries entries. We added more, but
  // TabRestoreService only allows up to kMaxEntries.
  ASSERT_EQ(static_cast<size_t>(kMaxEntries), service_->entries().size());

  // The first entry should come from the session service.
  Entry* entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);
  sessions::tab_restore::Window* window =
      static_cast<sessions::tab_restore::Window*>(entry);
  ASSERT_EQ(1U, window->tabs.size());
  EXPECT_EQ(0, window->selected_tab_index);
  EXPECT_EQ(0, window->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  ASSERT_EQ(1U, window->tabs[0]->navigations.size());
  EXPECT_EQ(0, window->tabs[0]->current_navigation_index);
  EXPECT_EQ(
      0,
      window->tabs[0]->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  EXPECT_TRUE(url1_ == window->tabs[0]->navigations[0].virtual_url());
}

// Makes sure we restore timestamps correctly.
TEST_F(TabRestoreServiceImplTest, TimestampSurvivesRestore) {
  base::Time tab_timestamp(
      base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(123456789)));

  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  // Make sure an entry was created.
  ASSERT_EQ(1U, service_->entries().size());

  // Make sure the entry matches.
  std::vector<SerializedNavigationEntry> old_navigations;
  {
    // |entry|/|tab| doesn't survive after RecreateService().
    Entry* entry = service_->entries().front().get();
    ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
    Tab* tab = static_cast<Tab*>(entry);
    tab->timestamp = tab_timestamp;
    old_navigations = tab->navigations;
  }

  EXPECT_EQ(3U, old_navigations.size());
  for (size_t i = 0; i < old_navigations.size(); ++i) {
    EXPECT_FALSE(old_navigations[i].timestamp().is_null());
  }

  // Set this, otherwise previous session won't be loaded.
  ExitTypeService::GetInstanceForProfile(profile())
      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);

  RecreateService();

  // One entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  // And verify the entry.
  Entry* restored_entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, restored_entry->type);
  Tab* restored_tab = static_cast<Tab*>(restored_entry);
  EXPECT_EQ(
      tab_timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds(),
      restored_tab->timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
  ASSERT_EQ(old_navigations.size(), restored_tab->navigations.size());
  for (size_t i = 0; i < restored_tab->navigations.size(); ++i) {
    EXPECT_EQ(old_navigations[i].timestamp(),
              restored_tab->navigations[i].timestamp());
  }
}

// Makes sure we restore status codes correctly.
TEST_F(TabRestoreServiceImplTest, StatusCodesSurviveRestore) {
  AddThreeNavigations();

  // Have the service record the tab.
  service_->CreateHistoricalTab(live_tab(), -1);

  // Make sure an entry was created.
  ASSERT_EQ(1U, service_->entries().size());

  // Make sure the entry matches.
  std::vector<sessions::SerializedNavigationEntry> old_navigations;
  {
    // |entry|/|tab| doesn't survive after RecreateService().
    Entry* entry = service_->entries().front().get();
    ASSERT_EQ(sessions::tab_restore::Type::TAB, entry->type);
    Tab* tab = static_cast<Tab*>(entry);
    old_navigations = tab->navigations;
  }

  EXPECT_EQ(3U, old_navigations.size());
  for (size_t i = 0; i < old_navigations.size(); ++i) {
    EXPECT_EQ(200, old_navigations[i].http_status_code());
  }

  // Set this, otherwise previous session won't be loaded.
  ExitTypeService::GetInstanceForProfile(profile())
      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);

  RecreateService();

  // One entry should be created.
  ASSERT_EQ(1U, service_->entries().size());

  // And verify the entry.
  Entry* restored_entry = service_->entries().front().get();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, restored_entry->type);
  Tab* restored_tab = static_cast<Tab*>(restored_entry);
  ASSERT_EQ(old_navigations.size(), restored_tab->navigations.size());
  for (size_t i = 0; i < restored_tab->navigations.size(); ++i) {
    EXPECT_EQ(200, restored_tab->navigations[i].http_status_code());
  }
}

TEST_F(TabRestoreServiceImplTest, PruneEntries) {
  service_->ClearEntries();
  ASSERT_TRUE(service_->entries().empty());

  const size_t max_entries = kMaxEntries;
  for (size_t i = 0; i < max_entries + 5; i++) {
    SerializedNavigationEntry navigation = ContentTestHelper::CreateNavigation(
        base::StringPrintf("http://%d", static_cast<int>(i)),
        base::NumberToString(i));

    auto tab = std::make_unique<Tab>();
    tab->navigations.push_back(navigation);
    tab->current_navigation_index = 0;

    mutable_entries()->push_back(std::move(tab));
  }

  // Only keep kMaxEntries around.
  EXPECT_EQ(max_entries + 5, service_->entries().size());
  PruneEntries();
  EXPECT_EQ(max_entries, service_->entries().size());
  // Pruning again does nothing.
  PruneEntries();
  EXPECT_EQ(max_entries, service_->entries().size());

  // Prune older first.
  const char kRecentUrl[] = "http://recent";
  SerializedNavigationEntry navigation =
      ContentTestHelper::CreateNavigation(kRecentUrl, "Most recent");
  auto tab = std::make_unique<Tab>();
  tab->navigations.push_back(navigation);
  tab->current_navigation_index = 0;
  mutable_entries()->push_front(std::move(tab));
  EXPECT_EQ(max_entries + 1, service_->entries().size());
  PruneEntries();
  EXPECT_EQ(max_entries, service_->entries().size());
  EXPECT_EQ(GURL(kRecentUrl), static_cast<Tab&>(*service_->entries().front())
                                  .navigations[0]
                                  .virtual_url());

  // Ignore NTPs.
  navigation = ContentTestHelper::CreateNavigation(chrome::kChromeUINewTabURL,
                                                   "New tab");

  tab = std::make_unique<Tab>();
  tab->navigations.push_back(navigation);
  tab->current_navigation_index = 0;
  mutable_entries()->push_front(std::move(tab));

  EXPECT_EQ(max_entries + 1, service_->entries().size());
  PruneEntries();
  EXPECT_EQ(max_entries, service_->entries().size());
  EXPECT_EQ(GURL(kRecentUrl), static_cast<Tab&>(*service_->entries().front())
                                  .navigations[0]
                                  .virtual_url());

  // Don't prune pinned NTPs.
  tab = std::make_unique<Tab>();
  tab->pinned = true;
  tab->current_navigation_index = 0;
  tab->navigations.push_back(navigation);
  mutable_entries()->push_front(std::move(tab));
  EXPECT_EQ(max_entries + 1, service_->entries().size());
  PruneEntries();
  EXPECT_EQ(max_entries, service_->entries().size());
  EXPECT_EQ(chrome::ChromeUINewTabURLAsGURL(),
            static_cast<Tab*>(service_->entries().front().get())
                ->navigations[0]
                .virtual_url());

  // Don't prune NTPs that have multiple navigations.
  // (Erase the last NTP first.)
  mutable_entries()->erase(mutable_entries()->begin());
  tab = std::make_unique<Tab>();
  tab->current_navigation_index = 1;
  tab->navigations.push_back(navigation);
  tab->navigations.push_back(navigation);
  mutable_entries()->push_front(std::move(tab));
  EXPECT_EQ(max_entries, service_->entries().size());
  PruneEntries();
  EXPECT_EQ(max_entries, service_->entries().size());
  EXPECT_EQ(chrome::ChromeUINewTabURLAsGURL(),
            static_cast<Tab*>(service_->entries().front().get())
                ->navigations[1]
                .virtual_url());
}

// Regression test for crbug.com/40122191
TEST_F(TabRestoreServiceImplTest, PruneIsCalled) {
  CreateSessionServiceWithOneWindow(false);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();

  profile()->set_restored_last_session(true);

  const size_t max_entries = kMaxEntries;
  for (size_t i = 0; i < max_entries + 5; i++) {
    NavigateAndCommit(
        GURL(base::StringPrintf("http://%d", static_cast<int>(i))));
    service_->CreateHistoricalTab(live_tab(), -1);
  }

  EXPECT_EQ(max_entries, service_->entries().size());
  // This should not crash.
  SynchronousLoadTabsFromLastSession();
  EXPECT_EQ(max_entries, service_->entries().size());
}

// Makes sure invoking LoadTabsFromLastSession() when the max number of entries
// have been added results in IsLoaded() returning true and notifies observers.
TEST_F(TabRestoreServiceImplTest, GoToLoadedWhenHaveMaxEntries) {
  const size_t max_entries = kMaxEntries;
  for (size_t i = 0; i < max_entries + 5; i++) {
    NavigateAndCommit(
        GURL(base::StringPrintf("http://%d", static_cast<int>(i))));
    service_->CreateHistoricalTab(live_tab(), -1);
  }

  EXPECT_FALSE(service_->IsLoaded());
  EXPECT_EQ(max_entries, service_->entries().size());
  SynchronousLoadTabsFromLastSession();
  EXPECT_TRUE(service_->IsLoaded());
}

// Ensures tab group data is restored from previous session.
TEST_F(TabRestoreServiceImplTest, TabGroupsRestoredFromSessionData) {
  CreateSessionServiceWithOneWindow(false);

  auto group = tab_groups::TabGroupId::GenerateNew();
  auto group_visual_data = tab_groups::TabGroupVisualData(
      u"Foo", tab_groups::TabGroupColorId::kBlue);
  AddWindowWithOneTabToSessionService(false, group, group_visual_data);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();
  EXPECT_FALSE(service_->IsLoaded());
  SynchronousLoadTabsFromLastSession();

  ASSERT_EQ(2u, service_->entries().size());
  Entry* entry = service_->entries().back().get();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);
  auto* window = static_cast<sessions::tab_restore::Window*>(entry);
  ASSERT_EQ(1u, window->tabs.size());
  EXPECT_EQ(group, window->tabs[0]->group);
  EXPECT_EQ(group_visual_data, window->tab_groups[group]->visual_data);
}

// Ensures tab extra data is restored from previous session.
TEST_F(TabRestoreServiceImplTest, TabExtraDataRestoredFromSessionData) {
  const char kSampleKey[] = "test";
  const char kSampleData[] = "true";

  CreateSessionServiceWithOneWindow(false);
  AddWindowWithOneTabToSessionService(false);

  SessionService* session_service =
      SessionServiceFactory::GetForProfile(profile());
  session_service->AddTabExtraData(window_id(), tab_id(), kSampleKey,
                                   kSampleData);

  SessionServiceFactory::GetForProfile(profile())
      ->MoveCurrentSessionToLastSession();
  EXPECT_FALSE(service_->IsLoaded());
  SynchronousLoadTabsFromLastSession();

  ASSERT_EQ(2U, service_->entries().size());
  Entry* entry = service_->entries().back().get();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);
  auto* window = static_cast<sessions::tab_restore::Window*>(entry);
  ASSERT_EQ(1U, window->tabs.size());
  ASSERT_EQ(1U, window->tabs[0]->extra_data.size());
  EXPECT_EQ(kSampleData, window->tabs[0]->extra_data[kSampleKey]);
}

// Ensures split tab data is saved as an entry type, that the correct tabs
// are restored, and that the creation and suppression of tabs is handled
// correctly.
TEST_F(TabRestoreServiceImplWithMockClientTest, SplitCreationAndSuppression) {
  testing::NiceMock<MockLiveTabContext> mock_live_tab_context;
  testing::NiceMock<MockLiveTab> mock_tab1, mock_tab2;
  split_tabs::SplitTabId split_id = split_tabs::SplitTabId::GenerateNew();

  SetupMockSplit(&mock_live_tab_context, &mock_tab1, &mock_tab2,
                 GURL("http://split1"), GURL("http://split2"), split_id);

  // Create the Historical Split
  service_->CreateHistoricalSplit(&mock_live_tab_context, split_id);
  ASSERT_EQ(1U, service_->entries().size());
  ASSERT_EQ(sessions::tab_restore::Type::SPLIT,
            service_->entries().front()->type);

  // Simulate closure of the component tabs. Verify that the system prevents
  // duplicate history creation.
  EXPECT_FALSE(service_->CreateHistoricalTab(&mock_tab1, 0).has_value());
  EXPECT_FALSE(service_->CreateHistoricalTab(&mock_tab2, 1).has_value());
  EXPECT_EQ(1U, service_->entries().size());

  // Verify that a third unrelated tab is not suppressed while the split
  // closure is active.
  testing::NiceMock<MockLiveTab> mock_tab3;
  ON_CALL(mock_tab3, GetSessionID())
      .WillByDefault(Return(SessionID::NewUnique()));
  ON_CALL(mock_tab3, GetEntryCount()).WillByDefault(Return(1));
  ON_CALL(mock_tab3, GetEntryAtIndex(_))
      .WillByDefault(Return(
          sessions::ContentTestHelper::CreateNavigation("http://tab3", "T3")));
  EXPECT_TRUE(service_->CreateHistoricalTab(&mock_tab3, 2).has_value());
  EXPECT_EQ(2U, service_->entries().size());

  // An additional closure attempt on the same tab now processes normally as a
  // standard tab entry.
  service_->SplitClosed(split_id);
  EXPECT_TRUE(service_->CreateHistoricalTab(&mock_tab1, 0).has_value());
  ASSERT_EQ(3U, service_->entries().size());
  EXPECT_EQ(sessions::tab_restore::Type::TAB,
            service_->entries().front()->type);
}

TEST_F(TabRestoreServiceImplWithMockClientTest, SplitVisualDataPersistence) {
  testing::NiceMock<MockLiveTabContext> mock_live_tab_context;
  testing::NiceMock<MockLiveTab> mock_tab1, mock_tab2;
  split_tabs::SplitTabId split_id = split_tabs::SplitTabId::GenerateNew();

  SetupMockSplit(&mock_live_tab_context, &mock_tab1, &mock_tab2,
                 GURL("http://split1"), GURL("http://split2"), split_id);

  ON_CALL(mock_live_tab_context, AddRestoredTab(_, _, _, _, _))
      .WillByDefault(testing::Return(&mock_tab1));

  split_tabs::SplitTabVisualData visual_data(
      split_tabs::SplitTabLayout::kStacked, 0.3);

  ON_CALL(mock_live_tab_context, GetVisualDataForSplit(split_id))
      .WillByDefault(testing::Return(&visual_data));

  // Create the Historical Split
  service_->CreateHistoricalSplit(&mock_live_tab_context, split_id);
  ASSERT_EQ(1U, service_->entries().size());

  auto& entry = service_->entries().front();
  ASSERT_EQ(sessions::tab_restore::Type::SPLIT, entry->type);

  auto* split_entry = static_cast<sessions::tab_restore::Split*>(entry.get());
  EXPECT_EQ(visual_data, split_entry->visual_data);

  // Now verify that restoring the split passes the correct visual data.
  EXPECT_CALL(mock_live_tab_context,
              ReconstructSplit(_, _, split_id, testing::Eq(visual_data)))
      .Times(1);

  service_->RestoreEntryById(&mock_live_tab_context, entry->id,
                             WindowOpenDisposition::NEW_FOREGROUND_TAB);
}

TEST_F(TabRestoreServiceImplWithMockClientTest,
       WindowSplitVisualDataPersistence) {
  testing::NiceMock<MockLiveTabContext> mock_live_tab_context;
  testing::NiceMock<MockLiveTab> mock_tab1, mock_tab2;
  split_tabs::SplitTabId split_id = split_tabs::SplitTabId::GenerateNew();

  SetupMockSplit(&mock_live_tab_context, &mock_tab1, &mock_tab2,
                 GURL("http://split1"), GURL("http://split2"), split_id);

  ON_CALL(mock_live_tab_context, AddRestoredTab(_, _, _, _, _))
      .WillByDefault(testing::Return(&mock_tab1));

  split_tabs::SplitTabVisualData visual_data(
      split_tabs::SplitTabLayout::kStacked, 0.3);

  ON_CALL(mock_live_tab_context, GetVisualDataForSplit(split_id))
      .WillByDefault(testing::Return(&visual_data));

  ON_CALL(mock_live_tab_context, GetWindowType)
      .WillByDefault(testing::Return(sessions::SessionWindow::TYPE_NORMAL));

  ON_CALL(*mock_tab_restore_service_client_,
          CreateLiveTabContext(_, _, _, _, _, _, _, _))
      .WillByDefault(testing::Return(&mock_live_tab_context));

  // Create the Historical Window (via BrowserClosing)
  service_->BrowserClosing(&mock_live_tab_context);
  ASSERT_EQ(1U, service_->entries().size());

  auto& entry = service_->entries().front();
  ASSERT_EQ(sessions::tab_restore::Type::WINDOW, entry->type);

  auto* window_entry = static_cast<sessions::tab_restore::Window*>(entry.get());
  ASSERT_EQ(2U, window_entry->tabs.size());

  // Verify that the tabs inside the window preserved the split visual data.
  EXPECT_EQ(split_id, window_entry->tabs[0]->split_id);
  EXPECT_EQ(visual_data, window_entry->tabs[0]->split_visual_data);

  // Now verify that restoring the window passes the correct visual data.
  EXPECT_CALL(mock_live_tab_context,
              ReconstructSplit(_, _, split_id, testing::Eq(visual_data)))
      .Times(1);

  service_->RestoreEntryById(&mock_live_tab_context, entry->id,
                             WindowOpenDisposition::NEW_FOREGROUND_TAB);
}

TEST_F(TabRestoreServiceImplWithMockClientTest,
       SplitVisualDataPersistenceAfterRestart) {
  ON_CALL(*mock_tab_restore_service_client_, ShouldTrackURLForRestore(_))
      .WillByDefault(testing::Return(true));

  testing::NiceMock<MockLiveTabContext> mock_live_tab_context;
  testing::NiceMock<MockLiveTab> mock_tab1, mock_tab2;
  split_tabs::SplitTabId split_id = split_tabs::SplitTabId::GenerateNew();

  SetupMockSplit(&mock_live_tab_context, &mock_tab1, &mock_tab2,
                 GURL("http://split1"), GURL("http://split2"), split_id);

  ON_CALL(mock_live_tab_context, AddRestoredTab(_, _, _, _, _))
      .WillByDefault(testing::Return(&mock_tab1));

  split_tabs::SplitTabVisualData visual_data(
      split_tabs::SplitTabLayout::kStacked, 0.3);

  ON_CALL(mock_live_tab_context, GetVisualDataForSplit(split_id))
      .WillByDefault(testing::Return(&visual_data));

  // Create the Historical Split
  service_->CreateHistoricalSplit(&mock_live_tab_context, split_id);
  ASSERT_EQ(1U, service_->entries().size());

  // Recreate the service to trigger persistence write and load.
  RecreateService();

  // Set expectation on the new mock client created inside RecreateService.
  ON_CALL(*mock_tab_restore_service_client_, ShouldTrackURLForRestore(_))
      .WillByDefault(testing::Return(true));

  // Verify the split visual data survived the restart.
  ASSERT_EQ(1U, service_->entries().size());
  auto& entry = service_->entries().front();
  ASSERT_EQ(sessions::tab_restore::Type::SPLIT, entry->type);

  auto* split_entry = static_cast<sessions::tab_restore::Split*>(entry.get());
  EXPECT_EQ(visual_data, split_entry->visual_data);

  // Now verify that restoring the split after restart passes the correct visual
  // data.
  EXPECT_CALL(mock_live_tab_context,
              ReconstructSplit(_, _, split_id, testing::Eq(visual_data)))
      .Times(1);

  service_->RestoreEntryById(&mock_live_tab_context, entry->id,
                             WindowOpenDisposition::NEW_FOREGROUND_TAB);
}

TEST_F(TabRestoreServiceImplWithMockClientTest, RestoreOneTabFromSplit) {
  testing::NiceMock<MockLiveTabContext> mock_live_tab_context;
  testing::NiceMock<MockLiveTab> mock_tab1, mock_tab2;
  split_tabs::SplitTabId split_id = split_tabs::SplitTabId::GenerateNew();

  SetupMockSplit(&mock_live_tab_context, &mock_tab1, &mock_tab2,
                 GURL("http://split1"), GURL("http://split2"), split_id);

  ON_CALL(mock_live_tab_context, AddRestoredTab(_, _, _, _, _))
      .WillByDefault(testing::Return(&mock_tab1));

  // Create the Historical Split
  service_->CreateHistoricalSplit(&mock_live_tab_context, split_id);
  ASSERT_EQ(1U, service_->entries().size());

  auto& entry = service_->entries().front();
  ASSERT_EQ(sessions::tab_restore::Type::SPLIT, entry->type);

  auto* split_entry = static_cast<sessions::tab_restore::Split*>(entry.get());
  ASSERT_EQ(2U, split_entry->tabs.size());

  SessionID tab1_id = split_entry->tabs[0]->id;
  SessionID tab2_id = split_entry->tabs[1]->id;

  // Restore only tab1 from the split.
  service_->RestoreEntryById(&mock_live_tab_context, tab1_id,
                             WindowOpenDisposition::NEW_FOREGROUND_TAB);

  // Verify the Split entry has been replaced by the remaining tab entry.
  ASSERT_EQ(1U, service_->entries().size());
  auto& remaining_entry = service_->entries().front();
  ASSERT_EQ(sessions::tab_restore::Type::TAB, remaining_entry->type);

  auto* tab_entry =
      static_cast<sessions::tab_restore::Tab*>(remaining_entry.get());
  EXPECT_EQ(tab2_id, tab_entry->id);
  EXPECT_EQ(std::nullopt, tab_entry->split_id);
  EXPECT_EQ(std::nullopt, tab_entry->split_visual_data);

  // Restore the remaining tab to verify that it does not crash.
  service_->RestoreEntryById(&mock_live_tab_context, tab2_id,
                             WindowOpenDisposition::NEW_FOREGROUND_TAB);

  EXPECT_TRUE(service_->entries().empty());
}

TEST_F(TabRestoreServiceImplWithMockClientTest,
       DontCreateGroupEntryWhileRestoring) {
  ON_CALL(*mock_tab_restore_service_client_, ShouldTrackURLForRestore(_))
      .WillByDefault(Return(true));

  // Create a group entry with two tabs and place it at the back of the list so
  // that it would be the first entry dropped if the list were ever pruned.
  auto group = std::make_unique<sessions::tab_restore::Group>();
  group->group_id = tab_groups::TabGroupId::GenerateNew();
  for (int i = 0; i < 2; ++i) {
    auto tab = std::make_unique<Tab>();
    tab->navigations.push_back(ContentTestHelper::CreateNavigation(
        base::StringPrintf("http://group/%d", i), "title"));
    tab->current_navigation_index = 0;
    group->tabs.push_back(std::move(tab));
  }
  const SessionID group_entry_id = group->id;
  mutable_entries()->push_back(std::move(group));

  // Fill the rest of the list up to kMaxEntries with tab entries that are
  // newer than the group entry.
  const size_t max_entries = kMaxEntries;
  for (size_t i = 0; i < max_entries - 1; ++i) {
    auto tab = std::make_unique<Tab>();
    tab->navigations.push_back(ContentTestHelper::CreateNavigation(
        base::StringPrintf("http://%d", static_cast<int>(i)), "title"));
    tab->current_navigation_index = 0;
    mutable_entries()->push_front(std::move(tab));
  }
  ASSERT_EQ(max_entries, service_->entries().size());

  // A separate context that simulates a tab group closure being committed
  // while the restore below is in progress.
  testing::NiceMock<MockLiveTabContext> closing_context;
  testing::NiceMock<MockLiveTab> closing_tab;
  tab_groups::TabGroupId closing_group_id =
      tab_groups::TabGroupId::GenerateNew();
  tab_groups::TabGroupVisualData closing_group_visual_data;
  ON_CALL(closing_context, GetSessionID())
      .WillByDefault(Return(SessionID::NewUnique()));
  ON_CALL(closing_context, GetTabCount()).WillByDefault(Return(1));
  ON_CALL(closing_context, GetTabGroupForTab(0))
      .WillByDefault(Return(closing_group_id));
  ON_CALL(closing_context, GetVisualDataForGroup(_))
      .WillByDefault(Return(&closing_group_visual_data));
  ON_CALL(closing_context, GetLiveTabAt(0)).WillByDefault(Return(&closing_tab));
  ON_CALL(closing_tab, GetEntryCount()).WillByDefault(Return(1));
  ON_CALL(closing_tab, GetEntryAtIndex(_))
      .WillByDefault(
          Return(ContentTestHelper::CreateNavigation("http://closing", "T")));

  // Restoring a tab can synchronously commit pending tab group closures, which
  // calls back into CreateHistoricalGroup. The service must not add a new
  // entry while a restore is already in progress.
  testing::NiceMock<MockLiveTabContext> restore_context;
  int restored_tab_count = 0;
  ON_CALL(restore_context, AddRestoredTab(_, _, _, _, _))
      .WillByDefault([&](const sessions::tab_restore::Tab&, int, bool, bool,
                         sessions::tab_restore::Type) -> sessions::LiveTab* {
        ++restored_tab_count;
        EXPECT_TRUE(service_->IsRestoring());
        service_->CreateHistoricalGroup(&closing_context, closing_group_id);
        EXPECT_EQ(group_entry_id, service_->entries().back()->id);
        return nullptr;
      });

  service_->RestoreEntryById(&restore_context, group_entry_id,
                             WindowOpenDisposition::NEW_FOREGROUND_TAB);

  EXPECT_EQ(2, restored_tab_count);
  EXPECT_EQ(max_entries - 1, service_->entries().size());
}
