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

#include "chrome/browser/sessions/session_restore.h"

#include <stddef.h>

#include <algorithm>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <utility>

#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/debug/alias.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/observer_list.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/token.h"
#include "build/build_config.h"
#include "chrome/browser/after_startup_task_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/buildflags.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/sessions/app_session_service.h"
#include "chrome/browser/sessions/app_session_service_factory.h"
#include "chrome/browser/sessions/session_restore_delegate.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/sessions/session_service_log.h"
#include "chrome/browser/sessions/session_service_lookup.h"
#include "chrome/browser/sessions/session_service_utils.h"
#include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h"
#include "chrome/browser/ui/browser_init_state.h"
#include "chrome/browser/ui/browser_tabrestore.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_collection.h"
#include "chrome/browser/ui/browser_window/public/browser_collection_observer.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/create_browser_window.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/browser/ui/startup/startup_tab.h"
#include "chrome/browser/ui/startup/startup_types.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_selection_state.h"
#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
#include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/waap/initial_webui_window_metrics_manager.h"
#include "chrome/browser/ui/webui/whats_new/whats_new_util.h"
#include "chrome/browser/ui/window_metadata/window_metadata_controller.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/extension_metrics.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/prefs/pref_service.h"
#include "components/saved_tab_groups/public/features.h"
#include "components/saved_tab_groups/public/saved_tab_group.h"
#include "components/saved_tab_groups/public/tab_group_sync_service.h"
#include "components/saved_tab_groups/public/types.h"
#include "components/sessions/core/session_types.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tabs/public/tab_collection.h"
#include "components/tabs/public/tab_group.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/dom_storage_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/session_storage_namespace.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_set.h"
#include "ui/base/mojom/window_show_state.mojom.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#include "ash/metrics/login_unlock_throughput_recorder.h"
#include "ash/shell.h"
#include "chrome/browser/ash/boot_times_recorder/boot_times_recorder.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "components/app_restore/window_properties.h"
#include "ui/aura/window_occlusion_tracker.h"
#include "ui/compositor/layer.h"
#include "ui/wm/core/scoped_animation_disabler.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#include "chrome/browser/ui/webui/whats_new/whats_new_fetcher.h"
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

using content::NavigationController;
using content::RenderWidgetHost;
using content::WebContents;
using RestoredTab = SessionRestoreDelegate::RestoredTab;

namespace {

bool HasSingleNewTabPage(BrowserWindowInterface* browser) {
  if (!browser || !browser->GetTabStripModel() ||
      browser->GetTabStripModel()->count() != 1) {
    return false;
  }
  content::WebContents* active_tab =
      browser->GetTabStripModel()->GetWebContentsAt(0);
  return active_tab->GetURL() == chrome::ChromeUINewTabURLAsGURL() ||
         search::IsInstantNTP(active_tab);
}

// Records the difference in the number of tabs and windows for a given type.
void RecordDiffMetric(const base::DictValue& dict,
                      std::string_view value_key,
                      std::string_view histogram_name,
                      int actual_count) {
  std::optional<int> expected = dict.FindInt(value_key);
  if (expected.has_value()) {
    base::UmaHistogramSparse(histogram_name, expected.value() - actual_count);
  }
}

// Records the difference between expected and actual tab/window counts after a
// restart.
//
// Note: This metric is subject to noise if a restart fails (e.g. due to
// unload handlers) and the user continues browsing and closes tabs before
// a subsequent manual exit.
void RecordTabWindowDiff(Profile* profile,
                         const SessionRestore::StateCounts& counts) {
  if (!base::FeatureList::IsEnabled(features::kRecordTabWindowDiffOnRestart)) {
    return;
  }

  PrefService* prefs = profile->GetPrefs();

  // If this wasn't a restart, clear the pref and return. Here, use
  // StartupBrowserCreator::WasRestarted() instead of reading the pref directly
  // because StartupBrowserCreator reads and resets the pref during early
  // startup. Reading it here again would always return false.
  if (!StartupBrowserCreator::WasRestarted()) {
    prefs->ClearPref(prefs::kPreSmartRestartSessionState);
    return;
  }

  const base::DictValue& dict =
      prefs->GetDict(prefs::kPreSmartRestartSessionState);

  if (dict.empty()) {
    return;
  }

  RecordDiffMetric(dict, SessionRestore::kNormalTabsKey,
                   "SessionRestore.TabDiffAfterRestart.Normal",
                   counts.normal_tabs);
  RecordDiffMetric(dict, SessionRestore::kNormalWindowsKey,
                   "SessionRestore.WindowDiffAfterRestart.Normal",
                   counts.normal_windows);
  RecordDiffMetric(dict, SessionRestore::kAppTabsKey,
                   "SessionRestore.TabDiffAfterRestart.App", counts.app_tabs);
  RecordDiffMetric(dict, SessionRestore::kAppWindowsKey,
                   "SessionRestore.WindowDiffAfterRestart.App",
                   counts.app_windows);

  prefs->ClearPref(prefs::kPreSmartRestartSessionState);
}

// Pointers to SessionRestoreImpls which are currently restoring the session.
std::set<SessionRestoreImpl*>& GetActiveSessionRestorers() {
  static base::NoDestructor<std::set<SessionRestoreImpl*>> instance;
  return *instance;
}

// Tracks whether any session has been restored during the current process
// lifetime.
static bool g_is_any_session_restored = false;

std::unique_ptr<AfterStartupTaskUtils::StartupInProgressRef>&
GetSessionRestoreStartupRef() {
  static base::NoDestructor<
      std::unique_ptr<AfterStartupTaskUtils::StartupInProgressRef>>
      ref;
  return *ref;
}

#if BUILDFLAG(IS_CHROMEOS)
// Helper to pause occlusion tracking while it is alive and updates occlusion
// states of restored tabs when it goes out of scope.
class RestoredTabOcclusionPauserAndUpdater {
 public:
  explicit RestoredTabOcclusionPauserAndUpdater(
      std::vector<RestoredTab>& restored_tabs)
      : restored_tabs_(&restored_tabs) {
    pause_occlusion_tracking_.emplace();
    window_animation_disablers_.emplace();
  }

  ~RestoredTabOcclusionPauserAndUpdater() {
    // Triggers occlusion state calculation.
    window_animation_disablers_.reset();
    pause_occlusion_tracking_.reset();

    // Occlusion state should be calculated synchronously on ash after dropping
    // `pause_occlusion_tracking_`. Explicitly updating the contents visibility
    // based on HIDDEN/OCCLUDED state so that relevant restored tabs are marked
    // as backgrounded. This is needed because
    // `WebContentsImpl::UpdateWebContentsVisibility` ignores HIDDEN/OCCLUDED
    // state before contents are made visible for the first time.
    for (const auto& tab : *restored_tabs_) {
      content::WebContents* contents = tab.contents();
      aura::Window* contents_view = contents->GetNativeView();
      if (contents_view->GetOcclusionState() ==
          aura::Window::OcclusionState::HIDDEN) {
        contents->WasHidden();
      } else if (contents_view->GetOcclusionState() ==
                 aura::Window::OcclusionState::OCCLUDED) {
        contents->WasOccluded();
      }
    }
  }

  void DisableWindowAnimation(aura::Window* window) {
    (*window_animation_disablers_)[window].emplace(window);
  }

 private:
  // The restored tabs from session restore, updated externally.
  const raw_ptr<std::vector<RestoredTab>> restored_tabs_;

  // Pause occlusion tracking until all browser windows are created so that
  // their final occlusion state is used to trigger tab loading.
  // This is ash only because the final occlusion state is calculated
  // synchronously on ash.
  std::optional<aura::WindowOcclusionTracker::ScopedPause>
      pause_occlusion_tracking_;

  // Disables window animations for created browser windows. Otherwise,
  // because occlusion states are not calculated for animating windows, all
  // restored browser windows are considered visible and triggers tab load.
  std::optional<
      std::map<aura::Window*, std::optional<wm::ScopedAnimationDisabler>>>
      window_animation_disablers_;
};

void ReportRestoredWindowCreated(aura::Window* window) {
  // Ash is not always initialized in unit tests.
  if (!ash::Shell::HasInstance()) {
    return;
  }

  const int32_t restore_window_id =
      window->GetProperty(app_restore::kRestoreWindowIdKey);

  // Restored window IDs are always non-zero.
  if (restore_window_id == 0) {
    return;
  }

  ash::LoginUnlockThroughputRecorder* throughput_recorder =
      ash::Shell::Get()->login_unlock_throughput_recorder();
  throughput_recorder->OnRestoredWindowCreated(restore_window_id);
  aura::Window* root_window = window->GetRootWindow();
  if (root_window) {
    ui::Compositor* compositor = root_window->layer()->GetCompositor();
    throughput_recorder->OnBeforeRestoredWindowShown(restore_window_id,
                                                     compositor);
  }
}
#endif

}  // namespace

// SessionRestoreImpl ---------------------------------------------------------

// SessionRestoreImpl is responsible for fetching the set of tabs to create
// from SessionService. SessionRestoreImpl deletes itself when done.

class SessionRestoreImpl : public BrowserCollectionObserver {
 public:
  SessionRestoreImpl(Profile* profile,
                     BrowserWindowInterface* browser,
                     bool synchronous,
                     bool clobber_existing_tab,
                     bool always_create_tabbed_browser,
                     bool restore_apps,
                     bool restore_browser,
                     bool log_event,
                     const StartupTabs& startup_tabs)
      : profile_(profile),
        browser_(browser),
        synchronous_(synchronous),
        clobber_existing_tab_(clobber_existing_tab),
        always_create_tabbed_browser_(always_create_tabbed_browser),
        log_event_(log_event),
        restore_apps_(restore_apps),
        restore_browser_(restore_browser),
        startup_tabs_(startup_tabs),
        active_window_id_(SessionID::InvalidValue()),
        restore_started_(base::TimeTicks::Now()) {
    DCHECK(restore_browser_ || restore_apps_);

    auto& active_restorers = GetActiveSessionRestorers();
    // Only one SessionRestoreImpl should be operating on the profile at the
    // same time.
    DCHECK(std::ranges::find_if(active_restorers,
                                [profile](SessionRestoreImpl* restorer) {
                                  return restorer->profile_ == profile;
                                }) == active_restorers.end());

    active_restorers.insert(this);

    keep_alive_ = std::make_unique<ScopedKeepAlive>(
        KeepAliveOrigin::SESSION_RESTORE, KeepAliveRestartOption::DISABLED);
    profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
        profile, ProfileKeepAliveOrigin::kBrowserWindow);
  }

  bool synchronous() const { return synchronous_; }

  BrowserWindowInterface* Restore() {
    if (restore_browser_) {
      SessionServiceBase* service =
          SessionServiceFactory::GetForProfileForSessionRestore(profile_);
      CHECK(service);
      service->GetLastSession(base::BindOnce(&SessionRestoreImpl::OnGotSession,
                                             weak_factory_.GetWeakPtr(),
                                             /* for_apps */ false));
    }

    if (restore_apps_) {
      SessionServiceBase* app_service =
          AppSessionServiceFactory::GetForProfileForSessionRestore(profile_);
      CHECK(app_service);
      app_service->GetLastSession(base::BindOnce(
          &SessionRestoreImpl::OnGotSession, weak_factory_.GetWeakPtr(),
          /* for_apps */ true));

      // Ensure the registry is ready so that when we reopen apps they work
      // properly. If we don't wait, it's possible that apps are restored in
      // an incoherent state.
      web_app::WebAppProvider* provider =
          web_app::WebAppProvider::GetForLocalAppsUnchecked(profile_);
      DCHECK(provider);

      provider->on_registry_ready().Post(
          FROM_HERE, base::BindOnce(&SessionRestoreImpl::WebAppRegistryReady,
                                    weak_factory_.GetWeakPtr()));
    }

    if (synchronous_) {
      {
        base::RunLoop loop(base::RunLoop::Type::kNestableTasksAllowed);
        quit_closure_for_sync_restore_ = loop.QuitClosure();
        loop.Run();
        quit_closure_for_sync_restore_ = base::OnceClosure();
      }
      BrowserWindowInterface* browser =
          ProcessSessionWindowsAndNotify(&windows_, active_window_id_);
      delete this;
      return browser;
    }

    if (browser_) {
      browser_collection_observation_.Observe(
          ProfileBrowserCollection::GetForProfile(profile_));
    }

    return browser_;
  }

  // Restore window(s) from a foreign session. Returns newly created
  // BrowserWindowInterfaces.
  std::vector<BrowserWindowInterface*> RestoreForeignSession(
      std::vector<const sessions::SessionWindow*>::const_iterator begin,
      std::vector<const sessions::SessionWindow*>::const_iterator end) {
    std::vector<BrowserWindowInterface*> windows;
    std::vector<RestoredTab> restored_tabs;
    // Create a browser instance to put the restored tabs in.
    for (auto i = begin; i != end; ++i) {
      BrowserWindowInterface* window = CreateRestoredBrowser(
          BrowserTypeForWindowType((*i)->type), (*i)->bounds, (*i)->workspace,
          (*i)->visible_on_all_workspaces, (*i)->show_state, (*i)->app_name,
          (*i)->user_title, (*i)->extra_data, (*i)->window_id.id());
      windows.push_back(window);

      // A foreign session window will not contain tab groups, however an
      // instance is still required for RestoreTabsToBrowser.
      base::flat_map<tab_groups::TabGroupId, tab_groups::TabGroupId>
          new_group_ids;

      // Restore and show the browser.
      const int initial_tab_count = 0;
      bool did_show_browser = false;
      RestoreTabsToBrowser(*(*i), window, initial_tab_count,
                           /*is_active_browser=*/false, restored_tabs,
                           &new_group_ids, did_show_browser);
      NotifySessionServiceOfRestoredTabs(window, initial_tab_count);
    }

    // Always create in a new window.
    FinishedTabCreation(true, true, restored_tabs);

    SessionRestore::on_session_restored_callbacks()->Notify(
        profile_, static_cast<int>(restored_tabs.size()));

    return windows;
  }

  // Restore a single tab from a foreign session.
  // Opens in the tab in the last active browser, unless disposition is
  // NEW_WINDOW, in which case the tab will be opened in a new browser. Returns
  // the WebContents of the restored tab.
  WebContents* RestoreForeignTab(const sessions::SessionTab& tab,
                                 WindowOpenDisposition disposition) {
    DCHECK(!tab.navigations.empty());
    int selected_index = tab.current_navigation_index;
    selected_index = std::max(
        0,
        std::min(selected_index, static_cast<int>(tab.navigations.size() - 1)));

    bool use_new_window = disposition == WindowOpenDisposition::NEW_WINDOW;

    BrowserWindowInterface* browser =
        use_new_window ? CreateBrowserWindow(BrowserWindowCreateParams(
                             profile_, /* from_user_gesture = */ true))
                       : browser_.get();
    CHECK(browser);
    RecordAppLaunchForTab(browser, tab, selected_index);

    WebContents* web_contents;
    if (disposition == WindowOpenDisposition::CURRENT_TAB) {
      DCHECK(!use_new_window);
      web_contents = chrome::ReplaceRestoredTab(
          browser, tab.navigations, selected_index, tab.extension_app_id,
          nullptr, tab.user_agent_override, tab.extra_data,
          true /* from_session_restore */);
    } else {
      int tab_index =
          use_new_window ? 0 : browser->GetTabStripModel()->active_index() + 1;
      web_contents = chrome::AddRestoredTab(
          browser, tab.navigations, tab_index, selected_index,
          tab.extension_app_id, std::nullopt,
          disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB,  // selected
          tab.pinned, base::TimeTicks(), base::Time(), nullptr,
          tab.user_agent_override, tab.extra_data,
          true /* from_session_restore */, std::nullopt);
      // Start loading the tab immediately.
      web_contents->GetController().LoadIfNecessary();
    }

    if (use_new_window) {
      browser->GetTabStripModel()->ActivateTabAt(
          0, TabStripUserGestureDetails(
                 TabStripUserGestureDetails::GestureType::kOther));
      browser->GetWindow()->Show();
    }
    NotifySessionServiceOfRestoredTabs(browser,
                                       browser->GetTabStripModel()->count());

    // Since FinishedTabCreation() is not called here, |this| will leak if we
    // are not in sychronous mode.
    DCHECK(synchronous_);

    SessionRestore::on_session_restored_callbacks()->Notify(profile_, 1);

    return web_contents;
  }

  SessionRestoreImpl(const SessionRestoreImpl&) = delete;
  SessionRestoreImpl& operator=(const SessionRestoreImpl&) = delete;

  ~SessionRestoreImpl() override { GetActiveSessionRestorers().erase(this); }

  // BrowserCollectionObserver:
  void OnBrowserClosed(BrowserWindowInterface* browser) override {
    if (browser == browser_) {
      if (log_event_) {
        LogSessionServiceRestoreCanceledEvent(profile_);
      }
      delete this;
    }
  }

  Profile* profile() { return profile_; }

 private:
  // Invoked when done with creating all the tabs/browsers.
  //
  // |created_tabbed_browser| indicates whether a tabbed browser was created,
  // or we used an existing tabbed browser.
  //
  // If successful, this begins loading tabs and deletes itself when all tabs
  // have been loaded.
  //
  // Returns the BrowserWindowInterface that was created, if any.
  BrowserWindowInterface* FinishedTabCreation(
      bool succeeded,
      bool created_tabbed_browser,
      std::vector<RestoredTab>& restored_tabs) {
    BrowserWindowInterface* browser = nullptr;
    if (!created_tabbed_browser && always_create_tabbed_browser_) {
      base::TimeTicks now = base::TimeTicks::Now();
      browser = CreateBrowserWindow(BrowserWindowCreateParams(profile_, false));
      if (auto* manager = InitialWebUIWindowMetricsManager::From(browser)) {
        manager->SetWindowCreationInfo(
            waap::NewWindowCreationSource::kBrowserInitiated, now);
      }
      if (startup_tabs_.empty() ||
          (startup_tabs_.size() == 1 && whats_new::IsEnabled() &&
           startup_tabs_[0].url == whats_new::GetWebUIStartupURL())) {
        // No tab browsers were created and no URLs were supplied on the command
        // line, or only the What's New page is specified at startup and may or
        // may not add a tab. Open the new tab page.
        startup_tabs_.emplace_back(chrome::ChromeUINewTabURLAsGURL());
      }
      AppendURLsToBrowser(browser, startup_tabs_);
      browser->GetWindow()->Show();
    }

    // Remove any tabs that have been deleted.
    std::erase_if(restored_tabs,
                  [](RestoredTab& tab) { return tab.contents() == nullptr; });

    if (succeeded) {
      // Sort the tabs in the order they should be restored, and start loading
      // them.
      std::stable_sort(restored_tabs.begin(), restored_tabs.end());
      SessionRestoreDelegate::RestoreTabs(restored_tabs, restore_started_);
    }

    if (!synchronous_) {
      // If we're not synchronous we need to delete ourself.
      // NOTE: we must use DeleteLater here as most likely we're in a callback
      // from the history service which doesn't deal well with deleting the
      // object it is notifying.
      base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
                                                                    this);

      // The delete may take a while and at this point we no longer care about
      // if the browser is deleted. Don't listen to anything. This avoid a
      // possible double delete too (if browser is closed before DeleteSoon() is
      // processed).
      browser_collection_observation_.Reset();
    }

#if BUILDFLAG(IS_CHROMEOS)
    ash::BootTimesRecorder::Get()->AddLoginTimeMarker("SessionRestore-End",
                                                      false);
#endif
    return browser;
  }

  // We typically want to restore windows in order of creation.
  void SortWindowsByWindowId() {
    std::sort(windows_.begin(), windows_.end(),
              [](const std::unique_ptr<sessions::SessionWindow>& lhs,
                 const std::unique_ptr<sessions::SessionWindow>& rhs) {
                return lhs->window_id < rhs->window_id;
              });
  }

  void OnGotSession(
      bool for_apps,
      std::vector<std::unique_ptr<sessions::SessionWindow>> windows,
      SessionID active_window_id,
      bool read_error) {
#if BUILDFLAG(IS_CHROMEOS)
    ash::BootTimesRecorder::Get()->AddLoginTimeMarker(
        "SessionRestore-GotSession", false);
#endif

    // This function could be called twice from both SessionService and
    // AppSessionService. If one of them returns error, then |read_error_| is
    // true. So check whether |read_error_| has been set as true to prevent the
    // result is overwritten.
    if (!read_error_) {
      read_error_ = read_error;
    }

    // Copy windows into windows_ so that we can combine both app and browser
    // windows together before doing a one-pass restore.
    std::ranges::move(windows, std::back_inserter(windows_));

    // Build a read-only view of the windows for the observers.
    std::vector<const sessions::SessionWindow*> windows_view;
    windows_view.reserve(windows_.size());
    for (const auto& w : windows_) {
      windows_view.push_back(w.get());
    }
    SessionRestore::OnGotSession(profile(), for_apps, windows_view);
    windows.clear();

    // Since we could now be possibly waiting for two |GetSession|s, we need
    // to track if we're completely finished or not. While we're at it,
    // store the windows for later merging and restoring.
    if (for_apps) {
      got_app_windows_ = true;
    } else {
      got_browser_windows_ = true;
    }

    // Don't let app restores set the active_window_id, or else it will
    // always bring the app to the forefront.
    if (!for_apps) {
      active_window_id_ = active_window_id;
    }

    ProcessSessionWindowsIfReady();
  }

  void WebAppRegistryReady() {
    DCHECK_EQ(web_app_registry_ready_, false);
    DCHECK(restore_apps_);
    web_app_registry_ready_ = true;

    ProcessSessionWindowsIfReady();
  }

  // This is called by callback handlers and may trigger session restore if
  // all the required conditions have been met. It also handles the differences
  // between async/sync restores.
  void ProcessSessionWindowsIfReady() {
    bool got_all_sessions = IsReadyToProcessSessionWindows();

    // For async restores, we need to early exit here if we aren't ready to
    // start restoring windows.
    if (!got_all_sessions) {
      return;
    }

    SortWindowsByWindowId();

    if (synchronous_) {
      CHECK(!quit_closure_for_sync_restore_.is_null());
      // now we know we have all the windows we need merge them into windows_
      // for processing.
      std::move(quit_closure_for_sync_restore_).Run();
      return;
    }

    ProcessSessionWindowsAndNotify(&windows_, active_window_id_);
  }

  // This helper, based on the restore_apps_ state tells us if we're ready to
  // begin restoring windows. There's two ways we are ready to process:
  // 1. we aren't restoring apps, and have browser_windows.
  // 2. we are restoring apps, we have both browser and app windows and
  //   WebAppRegistryReady() has been called.
  bool IsReadyToProcessSessionWindows() const {
    if (!restore_apps_) {
      return got_browser_windows_;
    }

    if (!restore_browser_) {
      return got_app_windows_ && web_app_registry_ready_;
    }

    return (got_app_windows_ && got_browser_windows_ &&
            web_app_registry_ready_);
  }

  BrowserWindowInterface* ProcessSessionWindowsAndNotify(
      std::vector<std::unique_ptr<sessions::SessionWindow>>* windows,
      SessionID active_window_id) {
    int window_count = 0;
    int tab_count = 0;
    SessionRestore::StateCounts counts;
    std::vector<RestoredTab> restored_tabs;
    BrowserWindowInterface* result =
        ProcessSessionWindows(windows, active_window_id, restored_tabs,
                              &window_count, &tab_count, &counts);
    if (log_event_) {
      LogSessionServiceRestoreEvent(profile_, window_count, tab_count,
                                    read_error_);
    }
    RecordTabWindowDiff(profile_, counts);
    SessionRestore::on_session_restored_callbacks()->Notify(
        profile_, static_cast<int>(restored_tabs.size()));
    return result;
  }

  void PruneWindows(
      std::vector<std::unique_ptr<sessions::SessionWindow>>* windows) {
#if BUILDFLAG(IS_CHROMEOS)
    web_app::WebAppProvider* provider =
        web_app::WebAppProvider::GetForWebApps(profile_);
    if (!provider) {
      return;
    }

    auto to_remove = std::ranges::remove_if(
        *windows,
        [provider](
            const std::unique_ptr<sessions::SessionWindow>& window) -> bool {
          // Windows that are auto-started and prevented from closing are
          // exempted from session restore.
          webapps::AppId app_id =
              web_app::GetAppIdFromApplicationName(window->app_name);
          // Checking for close prevention does not require an `AppLock`
          // and therefore `registrar_unsafe()` is safe to use.
          return provider->registrar_unsafe().IsPreventCloseEnabled(app_id);
        });
    windows->erase(to_remove.begin(), to_remove.end());
#endif  // BUIDLFLAG(IS_CHROMEOS)
  }

  // Creates browsers for `windows` and returns the last tabbed browser or the
  // one needs to be activated. If there is no tabbed browser (e.g. only PWAs
  // open in the last session, see b/40275406) returns the last app browser in
  // `windows`.
  BrowserWindowInterface* ProcessSessionWindows(
      std::vector<std::unique_ptr<sessions::SessionWindow>>* windows,
      SessionID active_window_id,
      std::vector<RestoredTab>& restored_tabs,
      int* window_count,
      int* tab_count,
      SessionRestore::StateCounts* counts) {
    DVLOG(1) << "ProcessSessionWindows " << windows->size();

    PruneWindows(windows);

    if (windows->empty()) {
      // Restore was unsuccessful. The cookie/storage systems can also delete
      // their data, since no session restore will happen at a later point.
      profile_->GetDefaultStoragePartition()->DeleteStaleSessionData();
      return FinishedTabCreation(false, false, restored_tabs);
    }

#if BUILDFLAG(IS_CHROMEOS)
    ash::BootTimesRecorder::Get()->AddLoginTimeMarker(
        "SessionRestore-CreatingTabs-Start", false);
#endif

    // After the for loop this contains the last TYPE_NORMAL browser, or nullptr
    // if no TYPE_NORMAL browser exists.
    BrowserWindowInterface* last_normal_browser = nullptr;
    bool has_normal_browser = false;

    // After the for loop this contains the last TYPE_APP browser, or nullptr
    // if no TYPE_APP browser exists.
    BrowserWindowInterface* last_app_browser = nullptr;

    // After the for loop, this contains the browser to activate, if one of the
    // windows has the same id as specified in active_window_id.
    BrowserWindowInterface* browser_to_activate = nullptr;

    // Determine if there is a visible window, or if the active window exists.
    // Even if all windows are ui::mojom::WindowShowState::kMinimized, if one of
    // them is the active window it will be made visible by the call to
    // browser_to_activate->GetWindow()->Activate() later on in this method.
    bool has_visible_browser = false;
    for (const auto& window : *windows) {
      if (window->show_state != ui::mojom::WindowShowState::kMinimized ||
          window->window_id == active_window_id) {
        has_visible_browser = true;
      }
    }

#if BUILDFLAG(IS_CHROMEOS)
    std::optional<RestoredTabOcclusionPauserAndUpdater> occlusion_helper;

    if (base::FeatureList::IsEnabled(
            ash::features::kAshSessionRestoreDeferOccludedActiveTabLoad)) {
      occlusion_helper.emplace(restored_tabs);
    }
#endif  //  BUILDFLAG(IS_CHROMEOS)

    for (const std::unique_ptr<sessions::SessionWindow>& window : *windows) {
      ++(*window_count);
      // 1. Choose between restoring tabs in an existing browser or in a newly
      //    created browser.
      BrowserWindowInterface* browser = nullptr;
      const bool is_first_window = window.get() == windows->begin()->get();
      const bool is_normal_window =
          window->type == sessions::SessionWindow::TYPE_NORMAL;
      const bool is_app_window =
          window->type == sessions::SessionWindow::TYPE_APP;

      if (is_normal_window) {
        counts->normal_windows++;
      } else if (is_app_window) {
        counts->app_windows++;
      }

      if (is_first_window && is_normal_window &&
          ShouldRestoreToExistingBrowser()) {
        // The first set of tabs is added to the existing browser.
        browser = browser_;
      } else {
#if BUILDFLAG(IS_CHROMEOS)
        ash::BootTimesRecorder::Get()->AddLoginTimeMarker(
            "SessionRestore-CreateRestoredBrowser-Start", false);
#endif
        // Change the initial show state of the created browser to
        // WindowShowState::kNormal if there are no visible browsers.
        ui::mojom::WindowShowState show_state = window->show_state;
        if (!has_visible_browser) {
          show_state = ui::mojom::WindowShowState::kNormal;
          has_visible_browser = true;
        }
        browser = CreateRestoredBrowser(
            BrowserTypeForWindowType(window->type), window->bounds,
            window->workspace, window->visible_on_all_workspaces, show_state,
            window->app_name, window->user_title, window->extra_data,
            window->window_id.id());

#if BUILDFLAG(IS_CHROMEOS)
        aura::Window* browser_window = browser->GetWindow()->GetNativeWindow();
        if (occlusion_helper) {
          occlusion_helper->DisableWindowAnimation(browser_window);
        }

        ash::BootTimesRecorder::Get()->AddLoginTimeMarker(
            "SessionRestore-CreateRestoredBrowser-End", false);
        ReportRestoredWindowCreated(browser_window);
#endif
      }

      // 2. Track TYPE_NORMAL browsers.
      if (is_normal_window) {
        has_normal_browser = true;
        last_normal_browser = browser;
        WindowMetadataController::From(browser)->SetWindowUserTitle(
            window->user_title);
      }

      // 3. Track TYPE_APP browsers.
      if (window->type == sessions::SessionWindow::TYPE_APP) {
        last_app_browser = browser;
      }

      // 4. Determine whether the currently active tab should be closed.
      WebContents* active_tab =
          browser->GetTabStripModel()->GetActiveWebContents();
      int initial_tab_count = browser->GetTabStripModel()->count();
      bool close_active_tab = clobber_existing_tab_ && is_first_window &&
                              is_normal_window && active_tab &&
                              browser == browser_ && !window->tabs.empty();
      if (close_active_tab) {
        --initial_tab_count;
      }

      if (window->window_id == active_window_id) {
        browser_to_activate = browser;
      }

      // 5. Restore tabs in |browser|. This will also call Show() on |browser|
      //    if its initial show state is not mimimized.
      // For the cases that users have more than one desk, a window is restored
      // to its parent desk, which can be non-active desk, and left invisible
      // but unminimized.
      base::flat_map<tab_groups::TabGroupId, tab_groups::TabGroupId>
          new_group_ids;

      bool did_show_browser = false;
      RestoreTabsToBrowser(*window, browser, initial_tab_count,
                           browser == browser_to_activate, restored_tabs,
                           &new_group_ids, did_show_browser);
      // Newly created browsers should be shown by RestoreTabsToBrowser. If they
      // aren't shown, they are likely to be never shown.
      if (browser != browser_) {
        DCHECK(did_show_browser);
      }

      int total_tab_count =
          browser->GetTabStripModel()->count() - initial_tab_count;
      (*tab_count) += total_tab_count;
      if (is_normal_window) {
        counts->normal_tabs += total_tab_count;
      } else if (is_app_window) {
        counts->app_tabs += total_tab_count;
      }

      // 6. Tabs will be grouped appropriately in RestoreTabsToBrowser. Now
      // restore the visual data.
      RestoreSplitTabVisualData(browser, window->split_tabs);

      // 7. Tabs will be grouped appropriately in RestoreTabsToBrowser. Now
      //    restore the groups' visual data.
      //    Note, this may delete some of the WebContents created earlier.
      RestoreTabGroupMetadata(browser, new_group_ids, window->tab_groups);

      // 8. Restore the focused tab group for the window if one was previously
      //    focused.
      RestoreFocusedTabGroup(browser, new_group_ids);

      // 9. Notify SessionService of restored tabs, so they can be saved to the
      //    current session.
      // TODO(fdoray): This seems redundant with the call to
      // SessionService::TabRestored() at the end of chrome::AddRestoredTab().
      // Consider removing it.
      NotifySessionServiceOfRestoredTabs(browser, initial_tab_count);

      // 10. Close the tab that was active in the window prior to session
      //    restore, if needed.
      if (close_active_tab) {
        chrome::CloseWebContents(browser, active_tab, true);
      }

      // Sanity check: A restored browser should have an active tab.
      // TODO(crbug.com/40662817): Change to DCHECK once we understand
      // why some browsers don't have an active tab on startup.
      CHECK(browser->GetTabStripModel()->GetActiveWebContents());
    }

    if (browser_to_activate && browser_to_activate->GetType() ==
                                   BrowserWindowInterface::Type::TYPE_NORMAL) {
      last_normal_browser = browser_to_activate;
    }

    if (last_normal_browser && !startup_tabs_.empty()) {
      browser_to_activate = OpenStartupUrls(last_normal_browser, startup_tabs_);
    }

#if BUILDFLAG(IS_CHROMEOS)
    ash::BootTimesRecorder::Get()->AddLoginTimeMarker(
        "SessionRestore-CreatingTabs-End", false);
#endif
    if (browser_to_activate) {
      browser_to_activate->GetWindow()->Activate();
    }

    // If last_normal_browser is NULL and startup_tabs_ is non-empty,
    // FinishedTabCreation will create a new TabbedBrowser and add the urls to
    // it.
    BrowserWindowInterface* const finished_browser =
        FinishedTabCreation(true, has_normal_browser, restored_tabs);
    if (finished_browser) {
      last_normal_browser = finished_browser;
    }

    // Session cookies/storage needed for the session restore have now been
    // recreated by RestoreTab so it's safe to start deleting leftover data.
    profile_->GetDefaultStoragePartition()->DeleteStaleSessionData();

    return last_normal_browser ? last_normal_browser : last_app_browser;
  }

  // Record an app launch event (if appropriate) for a tab which is about to
  // be restored. Callers should ensure that selected_index is within the
  // bounds of tab.navigations before calling.
  void RecordAppLaunchForTab(BrowserWindowInterface* browser,
                             const sessions::SessionTab& tab,
                             int selected_index) {
    DCHECK(selected_index >= 0 &&
           selected_index < static_cast<int>(tab.navigations.size()));
    GURL url = tab.navigations[selected_index].virtual_url();
    const extensions::Extension* extension =
        extensions::ExtensionRegistry::Get(profile())
            ->enabled_extensions()
            .GetAppByURL(url);
    if (extension) {
      extensions::RecordAppLaunchType(
          extension_misc::APP_LAUNCH_SESSION_RESTORE, extension->GetType());
    }
  }

  // Adds the tabs from |window| to |browser|. Normal tabs go after the existing
  // tabs but pinned tabs will be pushed in front.
  // If there are no existing tabs, the tab at |window.selected_tab_index| will
  // be selected. Otherwise, the tab selection will remain untouched.
  void RestoreTabsToBrowser(
      const sessions::SessionWindow& window,
      BrowserWindowInterface* browser,
      int initial_tab_count,
      bool is_active_browser,
      std::vector<RestoredTab>& restored_tabs,
      base::flat_map<tab_groups::TabGroupId, tab_groups::TabGroupId>*
          new_group_ids,
      bool& did_show_browser) {
    DVLOG(1) << "RestoreTabsToBrowser " << window.tabs.size();
    // TODO(crbug.com/40662817): Change to DCHECK once we understand
    // why some browsers don't have an active tab on startup.
    CHECK(!window.tabs.empty());

    // TODO(crbug.com/41440719): Check that tab groups are contiguous in
    // |window| to ensure tabs will not be reordered when restoring. This is not
    // possible yet due the ordering of TabStripModelObserver notifications in
    // an edge case.

    const int selected_tab_index = std::clamp(
        window.selected_tab_index, 0, static_cast<int>(window.tabs.size() - 1));

    // Capture all the splits and all tabs to split.
    std::map<split_tabs::SplitTabId, std::vector<tabs::TabInterface*>>
        tabs_by_split_id;

    // Anchor both clocks to the current instant so the last active time (a
    // base::Time) can be mapped onto the TimeTicks timeline WebContents wants.
    const base::Time now = base::Time::Now();
    const base::TimeTicks now_ticks = base::TimeTicks::Now();
    for (int i = 0; i < static_cast<int>(window.tabs.size()); ++i) {
      const sessions::SessionTab& tab = *(window.tabs[i]);

      // Loads are scheduled for each restored tab unless the tab is going to
      // be selected as ShowBrowser() will load the selected tab.
      bool is_selected_tab =
          (initial_tab_count == 0) && (i == selected_tab_index);

      // Convert the last active time because WebContents needs a TimeTicks.
      const base::TimeTicks last_active_time_ticks =
          now_ticks - (now - tab.last_active_time);

      // If the browser already has tabs, we want to restore the new ones after
      // the existing ones. E.g. this happens in Win8 Metro where we merge
      // windows or when launching a hosted app from the app launcher.
      int tab_index = i + initial_tab_count;
      RestoreTab(tab, browser, is_active_browser, restored_tabs, new_group_ids,
                 &tabs_by_split_id, tab_index, is_selected_tab,
                 last_active_time_ticks, tab.last_active_time,
                 did_show_browser);
    }

    for (const auto& pair : tabs_by_split_id) {
      const split_tabs::SplitTabId split_id = pair.first;
      const std::vector<tabs::TabInterface*> tab_contents_list = pair.second;
      std::vector<int> tab_index_list;
      for (auto* tab : tab_contents_list) {
        tab_index_list.push_back(
            browser->GetTabStripModel()->GetIndexOfTab(tab));
      }

      // The number of supported split tabs is 2. Do not attempt to restore
      // more than this to future proof it.
      if (tab_contents_list.size() == 2) {
        browser->GetTabStripModel()->RestoreSplit(
            split_id, tab_index_list, split_tabs::SplitTabVisualData());
      }
    }
  }

  // |tab_index| is ignored for pinned tabs which will always be pushed behind
  // the last existing pinned tab. If |is_selected_tab| is true the tab will be
  // shown after loading, otherwise it's loaded hidden. |last_active_time| is
  // the value to use to set the last time the WebContents was made active.
  void RestoreTab(const sessions::SessionTab& tab,
                  BrowserWindowInterface* browser,
                  bool is_active_browser,
                  std::vector<RestoredTab>& restored_tabs,
                  base::flat_map<tab_groups::TabGroupId,
                                 tab_groups::TabGroupId>* new_group_ids,
                  std::map<split_tabs::SplitTabId,
                           std::vector<tabs::TabInterface*>>* tabs_by_split_id,
                  const int tab_index,
                  bool is_selected_tab,
                  base::TimeTicks last_active_time_ticks,
                  base::Time last_active_time,
                  bool& did_show_browser) {
    // It's possible (particularly for foreign sessions) to receive a tab
    // without valid navigations. In that case, just skip it.
    // See crbug.com/40290475.
    if (tab.navigations.empty()) {
      return;
    }

    SessionRestore::NotifySessionRestoreStartedLoadingTabs();
    int selected_index = GetNavigationIndexToSelect(tab);

    RecordAppLaunchForTab(browser, tab, selected_index);

    // Associate sessionStorage (if any) to the restored tab.
    scoped_refptr<content::SessionStorageNamespace> session_storage_namespace;
    if (!tab.session_storage_persistent_id.empty()) {
      session_storage_namespace =
          profile_->GetDefaultStoragePartition()
              ->GetDOMStorageContext()
              ->RecreateSessionStorage(tab.session_storage_persistent_id);
    }

    // Relabel group IDs to prevent duplicating groups. See crbug.com/40055647.
    std::optional<tab_groups::TabGroupId> new_group;
    if (tab.group) {
      auto it = new_group_ids->find(*tab.group);
      if (it == new_group_ids->end()) {
        it = new_group_ids
                 ->emplace(*tab.group, tab_groups::TabGroupId::GenerateNew())
                 .first;
      }
      new_group = it->second;
    }

    // Apply the stored group.
    WebContents* web_contents = chrome::AddRestoredTab(
        browser, tab.navigations, tab_index, selected_index,
        tab.extension_app_id, new_group, is_selected_tab, tab.pinned,
        last_active_time_ticks, last_active_time,
        session_storage_namespace.get(), tab.user_agent_override,
        tab.extra_data, true /* from_session_restore */, is_active_browser);
    DCHECK(web_contents);

    RestoredTab restored_tab(web_contents, is_selected_tab,
                             tab.extension_app_id.empty(), tab.pinned,
                             new_group, tab.split_id);
    restored_tabs.push_back(restored_tab);

    if (tab.split_id) {
      // add tab to tabs_by_split_id.
      tabs::TabInterface* tab_interface =
          browser->GetTabStripModel()->GetTabForWebContents(
              restored_tab.contents());
      (*tabs_by_split_id)[*tab.split_id].push_back(tab_interface);
    }

    // If this isn't the selected tab, there's nothing else to do.
    if (!is_selected_tab) {
      return;
    }

    if (browser != browser_) {
      did_show_browser = true;
    }
    ShowBrowser(browser, browser->GetTabStripModel()->GetIndexOfWebContents(
                             web_contents));
  }

  void RestoreSplitTabVisualData(
      BrowserWindowInterface* browser,
      const std::vector<std::unique_ptr<sessions::SessionSplitTab>>&
          split_tabs) {
    for (const std::unique_ptr<sessions::SessionSplitTab>& session_split_tab :
         split_tabs) {
      if (!browser->GetTabStripModel()->ContainsSplit(session_split_tab->id_)) {
        continue;
      }

      CHECK(browser->GetTabStripModel()->GetSplitData(session_split_tab->id_));
      browser->GetTabStripModel()->UpdateSplitLayout(
          session_split_tab->id_,
          session_split_tab->split_visual_data_.split_layout());
      browser->GetTabStripModel()->UpdateSplitRatio(
          session_split_tab->id_,
          session_split_tab->split_visual_data_.split_ratio());
    }
  }

  void RestoreFocusedTabGroup(
      BrowserWindowInterface* browser,
      const base::flat_map<tab_groups::TabGroupId, tab_groups::TabGroupId>&
          new_group_ids) {
    if (!base::FeatureList::IsEnabled(features::kTabGroupsFocusing) ||
        !browser->GetTabStripModel()->SupportsTabGroups()) {
      return;
    }
    std::optional<tab_groups::TabGroupId> initial_group =
        BrowserInitState::From(browser)->initial_focused_tab_group_id();
    if (!initial_group.has_value()) {
      return;
    }
    if (browser->GetTabStripModel()->group_model()->ContainsTabGroup(
            *initial_group)) {
      browser->GetTabStripModel()->SetFocusedGroup(*initial_group);
      return;
    }
    auto it = new_group_ids.find(*initial_group);
    if (it != new_group_ids.end() &&
        browser->GetTabStripModel()->group_model()->ContainsTabGroup(
            it->second)) {
      browser->GetTabStripModel()->SetFocusedGroup(it->second);
    }
  }

  void RestoreTabGroupMetadata(
      BrowserWindowInterface* browser,
      const base::flat_map<tab_groups::TabGroupId, tab_groups::TabGroupId>&
          new_group_ids,
      const std::vector<std::unique_ptr<sessions::SessionTabGroup>>&
          tab_groups) {
    if (!browser->GetTabStripModel()->SupportsTabGroups()) {
      return;
    }

    SessionService* session_service =
        SessionServiceFactory::GetForProfile(browser->GetProfile());
    CHECK(session_service);

    for (const std::unique_ptr<sessions::SessionTabGroup>& session_tab_group :
         tab_groups) {
      const tab_groups::TabGroupId& new_tab_group_id =
          new_group_ids.at(session_tab_group->id);
      if (session_tab_group->saved_guid) {
        // We add this mapping to ensure the call to
        // TabStripModel::ChangeTabGroupVisuals results in writing the saved
        // guid to disk. This ensures we do not duplicate saved tab groups if
        // there is a crash prior to or during model initialization.
        session_service->AddSavedTabGroupsMapping(
            new_tab_group_id, session_tab_group->saved_guid.value());
      }

      TabGroup* model_tab_group =
          browser->GetTabStripModel()->group_model()->GetTabGroup(
              new_tab_group_id);
      CHECK(model_tab_group);
      browser->GetTabStripModel()->ChangeTabGroupVisuals(
          new_tab_group_id, session_tab_group->visual_data);

      ProcessSavedGroup(browser->GetProfile(), new_tab_group_id,
                        session_tab_group->saved_guid);
    }
  }

  void ProcessSavedGroup(Profile* profile,
                         const tab_groups::LocalTabGroupID& local_id,
                         std::optional<std::string> sync_id) {
    tab_groups::TabGroupSyncService* service =
        tab_groups::TabGroupSyncServiceFactory::GetForProfile(profile);
    if (!service) {
      return;
    }

    if (sync_id) {
      const base::Uuid& sync_guid = base::Uuid::ParseLowercase(sync_id.value());
      service->ConnectLocalTabGroup(
          sync_guid, local_id,
          tab_groups::OpeningSource::kConnectOnSessionRestore);
    } else {
      // Default save any groups that are not saved yet. This happens when
      // a user goes from V1 of SavedTabGroups to V2 through an update.
      service->SaveGroup(
          tab_groups::SavedTabGroupUtils::CreateSavedTabGroupFromLocalId(
              local_id));
    }
  }

  BrowserWindowInterface* CreateRestoredBrowser(
      BrowserWindowInterface::Type type,
      gfx::Rect bounds,
      const std::string& workspace,
      bool visible_on_all_workspaces,
      ui::mojom::WindowShowState show_state,
      const std::string& app_name,
      const std::string& user_title,
      const std::map<std::string, std::string>& extra_data,
      int32_t restore_id) {
    BrowserWindowCreateParams params(type, profile_, false);
    params.initial_bounds = bounds;
    params.user_title = user_title;

    // We only store trusted app windows, so we also create them as trusted.
    if (type == BrowserWindowInterface::Type::TYPE_APP) {
      params = BrowserWindowCreateParams::CreateForApp(
          app_name, /*trusted_source=*/true, bounds, profile_,
          /*user_gesture=*/false);
    } else if (type == BrowserWindowInterface::Type::TYPE_APP_POPUP) {
      params = BrowserWindowCreateParams::CreateForAppPopup(
          app_name, /*trusted_source=*/true, bounds, profile_,
          /*user_gesture=*/false);
    }

#if BUILDFLAG(IS_OZONE)
    params.restore_id = restore_id;
#endif

    params.initial_show_state = show_state;
    params.initial_workspace = workspace;
    params.initial_visible_on_all_workspaces_state = visible_on_all_workspaces;
    params.creation_source =
        BrowserWindowCreateParams::CreationSource::kSessionRestore;

    if (extra_data.contains(
            tabs::VerticalTabStripStateController::kCollapsedKey)) {
      params.vertical_tab_strip_collapsed =
          extra_data.at(tabs::VerticalTabStripStateController::kCollapsedKey) ==
          "true";
    }

    if (extra_data.contains(
            tabs::VerticalTabStripStateController::kUncollapsedWidthKey)) {
      int uncollapsed_width = 0;
      if (base::StringToInt(
              extra_data.at(
                  tabs::VerticalTabStripStateController::kUncollapsedWidthKey),
              &uncollapsed_width)) {
        params.vertical_tab_strip_uncollapsed_width = uncollapsed_width;
      }
    }

    if (base::FeatureList::IsEnabled(features::kTabGroupsFocusing)) {
      auto it = extra_data.find(
          tabs::TabStripModelSelectionState::kFocusedTabGroupIdKey);
      if (it != extra_data.end() && !it->second.empty()) {
        std::optional<base::Token> token = base::Token::FromString(it->second);
        if (token.has_value()) {
          params.focused_tab_group_id =
              tab_groups::TabGroupId::FromRawToken(*token);
        }
      }
    }

    base::TimeTicks now = base::TimeTicks::Now();
    BrowserWindowInterface* browser_window =
        CreateBrowserWindow(std::move(params));
    if (auto* manager =
            InitialWebUIWindowMetricsManager::From(browser_window)) {
      manager->SetWindowCreationInfo(
          waap::NewWindowCreationSource::kSessionRestore, now);
    }
    g_is_any_session_restored = true;
    return browser_window;
  }

  void ShowBrowser(BrowserWindowInterface* browser, int selected_tab_index) {
    DCHECK(browser);
    DCHECK(browser->GetTabStripModel()->count());
    browser->GetTabStripModel()->ActivateTabAt(
        selected_tab_index,
        TabStripUserGestureDetails(
            TabStripUserGestureDetails::GestureType::kOther));

    if (browser_ == browser) {
      return;
    }

    browser->GetWindow()->Show();
    BrowserInitState::From(browser)->set_is_session_restore(false);
  }

  // Appends the urls in |startup_tabs| to |browser|.
  void AppendURLsToBrowser(BrowserWindowInterface* browser,
                           const StartupTabs& startup_tabs) {
    bool is_first_tab = true;
    for (const auto& startup_tab : startup_tabs) {
      const GURL& url = startup_tab.url;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
      if (url == whats_new::GetWebUIStartupURL()) {
        whats_new::StartWhatsNewFetch(browser);
        continue;
      }
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
      int add_types = AddTabTypes::ADD_FORCE_INDEX;
      if (is_first_tab) {
        add_types |= AddTabTypes::ADD_ACTIVE;
      }
      NavigateParams params(browser, url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
      params.disposition = is_first_tab
                               ? WindowOpenDisposition::NEW_FOREGROUND_TAB
                               : WindowOpenDisposition::NEW_BACKGROUND_TAB;
      params.tabstrip_add_types = add_types;
      is_first_tab = false;
      Navigate(&params);
    }
  }

  // Normally opens |startup_tabs| in |last_normal_browser|, but if there are
  // urls set from LAST_AND_URLS startup pref, those are opened in a new
  // browser. Returns the browser to activate.
  BrowserWindowInterface* OpenStartupUrls(
      BrowserWindowInterface* last_normal_browser,
      const StartupTabs& startup_tabs) {
    BrowserWindowInterface* browser_to_activate = last_normal_browser;
    StartupTabs normal_startup_tabs, startup_tabs_from_last_and_urls_pref;
    for (const StartupTab& startup_tab : startup_tabs) {
      if (startup_tab.type == StartupTab::Type::kFromLastAndUrlsStartupPref) {
        startup_tabs_from_last_and_urls_pref.push_back(startup_tab);
      } else {
        normal_startup_tabs.push_back(startup_tab);
      }
    }
    if (last_normal_browser && !normal_startup_tabs.empty()) {
      AppendURLsToBrowser(last_normal_browser, normal_startup_tabs);
    }
    if (!startup_tabs_from_last_and_urls_pref.empty()) {
      BrowserWindowCreateParams params(profile_, /*from_user_gesture=*/false);
      params.creation_source =
          BrowserWindowCreateParams::CreationSource::kLastAndUrlsStartupPref;
      BrowserWindowInterface* new_browser =
          CreateBrowserWindow(std::move(params));
      AppendURLsToBrowser(new_browser, startup_tabs_from_last_and_urls_pref);
      new_browser->GetWindow()->Show();
      browser_to_activate = new_browser;
    }
    return browser_to_activate;
  }

  // Invokes TabRestored on the SessionService for all tabs in browser after
  // initial_count.
  void NotifySessionServiceOfRestoredTabs(BrowserWindowInterface* browser,
                                          int initial_count) {
    SessionServiceBase* service =
        GetAppropriateSessionServiceForProfile(browser);
    if (!service) {
      return;
    }

    TabStripModel* model = browser->GetTabStripModel();
    if (initial_count >= model->count()) {
      return;
    }

    for (tabs::TabCollection::TabIterator it(
             model->GetTabAtIndex(initial_count));
         it != model->end(); ++it) {
      service->TabRestored(it->GetContents(), it->IsPinned());
    }
  }

  // Returns true if the first set of tabs should be restored the Browser
  // supplied to the constructor.
  bool ShouldRestoreToExistingBrowser() const {
    // Assume that if the window is not-visible the browser is about to
    // be deleted. This is necessitated by browser destruction first hiding
    // the window, and then asynchronously deleting it.
    return browser_ &&
           browser_->GetType() == BrowserWindowInterface::Type::TYPE_NORMAL &&
           !browser_->GetProfile()->IsOffTheRecord() &&
           browser_->GetWindow()->IsVisible();
  }

  // The profile to create the sessions for.
  raw_ptr<Profile> profile_;

  // The first browser to restore to, may be null.
  raw_ptr<BrowserWindowInterface, DanglingUntriaged> browser_;

  // Whether or not restore is synchronous.
  const bool synchronous_;

  // The quit-closure to terminate the nested message-loop started for
  // synchronous session-restore.
  base::OnceClosure quit_closure_for_sync_restore_;

  // See description of CLOBBER_CURRENT_TAB.
  const bool clobber_existing_tab_;

  // If true and there is an error or there are no windows to restore, we
  // create a tabbed browser anyway. This is used on startup to make sure at
  // at least one window is created.
  const bool always_create_tabbed_browser_;

  // If true, LogSessionServiceRestoreEvent() is called after restore.
  const bool log_event_;

  // If true, restores apps.
  const bool restore_apps_;

  // If true, restores the normal browser.
  bool restore_browser_ = true;

  // App restores depend on web_app::WebAppProvider on_registry_ready(). This
  // bool will track that and hold up restores until that's ready too if apps
  // are being restored.
  bool web_app_registry_ready_ = false;

  // During app restores, we make two GetLastSession calls to
  // [App]SessionService, we need to wait till they both return before
  // processing the windows together in one pass.
  bool got_app_windows_ = false;
  bool got_browser_windows_ = false;

  // Set of URLs to open in addition to those restored from the session.
  StartupTabs startup_tabs_;

  // When synchronous we run a nested run loop. To avoid creating windows
  // from the nested run loop (which can make exiting the nested message
  // loop take a while) we cache the SessionWindows here and create the actual
  // windows when the nested run loop exits.
  std::vector<std::unique_ptr<sessions::SessionWindow>> windows_;
  SessionID active_window_id_;

  // When asynchronous it's possible for there to be no windows. To make sure
  // Chrome doesn't prematurely exit we register a KeepAlive for the lifetime
  // of this object.
  std::unique_ptr<ScopedKeepAlive> keep_alive_;

  // Same as |keep_alive_|, but also prevent |profile_| from getting deleted
  // (when DestroyProfileOnBrowserClose is enabled).
  std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;

  base::ScopedObservation<ProfileBrowserCollection, BrowserCollectionObserver>
      browser_collection_observation_{this};

  // The time we started the restore.
  base::TimeTicks restore_started_;

  // Set to true if reading the last commands encountered an error.
  bool read_error_ = false;

  base::WeakPtrFactory<SessionRestoreImpl> weak_factory_{this};
};

// SessionRestore -------------------------------------------------------------

// static
BrowserWindowInterface* SessionRestore::RestoreSession(
    Profile* profile,
    BrowserWindowInterface* browser,
    SessionRestore::BehaviorBitmask behavior,
    const StartupTabs& startup_tabs) {
#if DCHECK_IS_ON()
  // Profiles that are locked because they require signin should not be
  // restored.
  if (g_browser_process->profile_manager()) {
    ProfileAttributesEntry* entry =
        g_browser_process->profile_manager()
            ->GetProfileAttributesStorage()
            .GetProfileAttributesWithPath(profile->GetPath());
    DCHECK(!entry || !entry->IsSigninRequired());
  }
#endif
#if BUILDFLAG(IS_CHROMEOS)
  ash::BootTimesRecorder::Get()->AddLoginTimeMarker("SessionRestore-Start",
                                                    false);
#endif
  DCHECK(profile);
  DCHECK(SessionServiceFactory::GetForProfile(profile));
  profile->set_restored_last_session(true);

  LogSessionServiceRestoreInitiatedEvent(profile, (behavior & SYNCHRONOUS) != 0,
                                         (behavior & RESTORE_BROWSER) != 0);

  // SessionRestoreImpl takes care of deleting itself when done.
  SessionRestoreImpl* restorer = new SessionRestoreImpl(
      profile, browser, (behavior & SYNCHRONOUS) != 0,
      (behavior & CLOBBER_CURRENT_TAB) != 0,
      (behavior & ALWAYS_CREATE_TABBED_BROWSER) != 0,
      (behavior & RESTORE_APPS) != 0, (behavior & RESTORE_BROWSER) != 0,
      /* log_event */ true, startup_tabs);
  return restorer->Restore();
}

// static
void SessionRestore::RestoreSessionAfterCrash(BrowserWindowInterface* browser) {
  auto* profile = browser->GetProfile();

#if BUILDFLAG(IS_CHROMEOS)
  // Desks restore a window to the right desk, so we should not reuse any
  // browser window. Otherwise, the conflict of the parent desk arises because
  // tabs created in this |browser| should remain in the current active desk,
  // but the first restored window should be restored to its saved parent desk
  // before a crash. This also avoids users' confusion of the current window
  // disappearing from the current desk after pressing a restore button.
  browser = nullptr;
#endif

  SessionRestore::BehaviorBitmask behavior =
      SessionRestore::RESTORE_BROWSER |
      (browser && HasSingleNewTabPage(browser)
           ? SessionRestore::CLOBBER_CURRENT_TAB
           : 0);

#if !BUILDFLAG(IS_CHROMEOS)
  // Apps should always be restored on crash restore except on Chrome OS. In
  // Chrome OS, apps are restored by full restore only. This function is called
  // when the chrome browser is launched after crash, so only browser restored,
  // apps are not restored in Chrome OS.
  behavior |= SessionRestore::RESTORE_APPS;
#endif
  SessionRestore::RestoreSession(profile, browser, behavior, StartupTabs());
}

// static
void SessionRestore::OpenStartupPagesAfterCrash(
    BrowserWindowInterface* browser) {
  WebContents* tab_to_clobber = nullptr;
  if (HasSingleNewTabPage(browser)) {
    tab_to_clobber = browser->GetTabStripModel()->GetActiveWebContents();
  }

  StartupBrowserCreator::OpenStartupPages(
      browser, chrome::startup::IsProcessStartup::kYes);
  if (tab_to_clobber && browser->GetTabStripModel()->count() > 1) {
    chrome::CloseWebContents(browser, tab_to_clobber, true);
  }
}

// static
void SessionRestore::RestoreForeignSessionWindows(
    Profile* profile,
    std::vector<const sessions::SessionWindow*>::const_iterator begin,
    std::vector<const sessions::SessionWindow*>::const_iterator end,
    base::OnceCallback<void(std::vector<BrowserWindowInterface*>)> callback) {
  StartupTabs startup_tabs;
  SessionRestoreImpl restorer(profile, nullptr, true, false, true,
                              /* restore_apps */ false,
                              /* restore_browser */ true,
                              /* log_event */ false, startup_tabs);
  std::vector<BrowserWindowInterface*> windows =
      restorer.RestoreForeignSession(begin, end);
  std::move(callback).Run(std::move(windows));
}

// static
WebContents* SessionRestore::RestoreForeignSessionTab(
    content::WebContents* source_web_contents,
    const sessions::SessionTab& tab,
    WindowOpenDisposition disposition,
    bool skip_renderer_creation) {
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          source_web_contents);
  Profile* profile = browser->GetProfile();
  StartupTabs startup_tabs;
  SessionRestoreImpl restorer(profile, browser, true, false, false,
                              /* restore_apps */ false,
                              /* restore_browser */ true,
                              /* log_event */ false, startup_tabs);
  return restorer.RestoreForeignTab(tab, disposition);
}

// static
bool SessionRestore::IsRestoring(const Profile* profile) {
  for (SessionRestoreImpl* const active_session_restorer :
       GetActiveSessionRestorers()) {
    if (active_session_restorer->profile() == profile) {
      return true;
    }
  }
  return false;
}

// static
bool SessionRestore::IsAnySessionRestored() {
  return g_is_any_session_restored;
}

// static
bool SessionRestore::IsRestoringSynchronously() {
  for (const SessionRestoreImpl* const active_session_restorer :
       GetActiveSessionRestorers()) {
    if (active_session_restorer->synchronous()) {
      return true;
    }
  }
  return false;
}

// static
base::CallbackListSubscription
SessionRestore::RegisterOnSessionRestoredCallback(
    const RestoredCallback& callback) {
  return on_session_restored_callbacks()->Add(callback);
}

// static
void SessionRestore::AddObserver(SessionRestoreObserver* observer) {
  observers()->AddObserver(observer);
}

// static
void SessionRestore::RemoveObserver(SessionRestoreObserver* observer) {
  observers()->RemoveObserver(observer);
}

// static
void SessionRestore::OnTabLoaderFinishedLoadingTabs() {
  if (!session_restore_started_) {
    return;
  }

  GetSessionRestoreStartupRef().reset();

  session_restore_started_ = false;
  for (auto& observer : *observers()) {
    observer.OnSessionRestoreFinishedLoadingTabs();
  }
}

// static
void SessionRestore::NotifySessionRestoreStartedLoadingTabs() {
  if (session_restore_started_) {
    return;
  }

  if (base::FeatureList::IsEnabled(features::kImprovedStartupBestEffortDelay) &&
      features::kStartupDelayIncludesSessionRestore.Get()) {
    GetSessionRestoreStartupRef() =
        AfterStartupTaskUtils::RegisterStartupInProgressRef(
            StartupIsCompleteReason::kSessionRestore);
  }

  session_restore_started_ = true;
  for (auto& observer : *observers()) {
    observer.OnSessionRestoreStartedLoadingTabs();
  }
}

// static
void SessionRestore::OnGotSession(
    Profile* profile,
    bool for_apps,
    const std::vector<const sessions::SessionWindow*>& windows) {
  for (auto& observer : *observers()) {
    observer.OnGotSession(profile, for_apps, windows);
  }
}

// static
SessionRestore::CallbackList* SessionRestore::on_session_restored_callbacks() {
  static base::NoDestructor<CallbackList> instance;
  return instance.get();
}

// static
SessionRestore::SessionRestoreObserverList* SessionRestore::observers() {
  static base::NoDestructor<SessionRestoreObserverList> instance;
  return instance.get();
}

// static
bool SessionRestore::session_restore_started_ = false;
