// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// 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 <algorithm>
#include <list>
#include <set>
#include <vector>

#include "base/callback.h"
#include "base/command_line.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_types.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window.h"
#include "content/browser/renderer_host/render_widget_host.h"
#include "content/browser/renderer_host/render_widget_host_view.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_view.h"
#include "content/common/notification_registrar.h"
#include "content/common/notification_service.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/boot_times_loader.h"
#include "chrome/browser/chromeos/network_state_notifier.h"
#endif

// Are we in the process of restoring?
static bool restoring = false;

namespace {

// TabLoader ------------------------------------------------------------------

// Initial delay (see class decription for details).
static const int kInitialDelayTimerMS = 100;

// TabLoader is responsible for loading tabs after session restore creates
// tabs. New tabs are loaded after the current tab finishes loading, or a delay
// is reached (initially kInitialDelayTimerMS). If the delay is reached before
// a tab finishes loading a new tab is loaded and the time of the delay
// doubled. When all tabs are loading TabLoader deletes itself.
//
// This is not part of SessionRestoreImpl so that synchronous destruction
// of SessionRestoreImpl doesn't have timing problems.
class TabLoader : public NotificationObserver {
 public:
  explicit TabLoader(base::TimeTicks restore_started);
  ~TabLoader();

  // Schedules a tab for loading.
  void ScheduleLoad(NavigationController* controller);

  // Notifies the loader that a tab has been scheduled for loading through
  // some other mechanism.
  void TabIsLoading(NavigationController* controller);

  // Invokes |LoadNextTab| to load a tab.
  //
  // This must be invoked once to start loading.
  void StartLoading();

 private:
  typedef std::set<NavigationController*> TabsLoading;
  typedef std::list<NavigationController*> TabsToLoad;
  typedef std::set<RenderWidgetHost*> RenderWidgetHostSet;

  // Loads the next tab. If there are no more tabs to load this deletes itself,
  // otherwise |force_load_timer_| is restarted.
  void LoadNextTab();

  // NotificationObserver method. Removes the specified tab and loads the next
  // tab.
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details);

  // Removes the listeners from the specified tab and removes the tab from
  // the set of tabs to load and list of tabs we're waiting to get a load
  // from.
  void RemoveTab(NavigationController* tab);

  // Invoked from |force_load_timer_|. Doubles |force_load_delay_| and invokes
  // |LoadNextTab| to load the next tab
  void ForceLoadTimerFired();

  // Returns the RenderWidgetHost associated with a tab if there is one,
  // NULL otherwise.
  static RenderWidgetHost* GetRenderWidgetHost(NavigationController* tab);

  // Register for necessary notificaitons on a tab navigation controller.
  void RegisterForNotifications(NavigationController* controller);

  // Called when a tab goes away or a load completes.
  void HandleTabClosedOrLoaded(NavigationController* controller);

  NotificationRegistrar registrar_;

  // Current delay before a new tab is loaded. See class description for
  // details.
  int64 force_load_delay_;

  // Has Load been invoked?
  bool loading_;

  // Have we recorded the times for a tab paint?
  bool got_first_paint_;

  // The set of tabs we've initiated loading on. This does NOT include the
  // selected tabs.
  TabsLoading tabs_loading_;

  // The tabs we need to load.
  TabsToLoad tabs_to_load_;

  // The renderers we have started loading into.
  RenderWidgetHostSet render_widget_hosts_loading_;

  // The renderers we have loaded and are waiting on to paint.
  RenderWidgetHostSet render_widget_hosts_to_paint_;

  // The number of tabs that have been restored.
  int tab_count_;

  base::OneShotTimer<TabLoader> force_load_timer_;

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

  DISALLOW_COPY_AND_ASSIGN(TabLoader);
};

TabLoader::TabLoader(base::TimeTicks restore_started)
    : force_load_delay_(kInitialDelayTimerMS),
      loading_(false),
      got_first_paint_(false),
      tab_count_(0),
      restore_started_(restore_started) {
}

TabLoader::~TabLoader() {
  DCHECK((got_first_paint_ || render_widget_hosts_to_paint_.empty()) &&
          tabs_loading_.empty() && tabs_to_load_.empty());
}

void TabLoader::ScheduleLoad(NavigationController* controller) {
  DCHECK(controller);
  DCHECK(find(tabs_to_load_.begin(), tabs_to_load_.end(), controller) ==
         tabs_to_load_.end());
  tabs_to_load_.push_back(controller);
  RegisterForNotifications(controller);
}

void TabLoader::TabIsLoading(NavigationController* controller) {
  DCHECK(controller);
  DCHECK(find(tabs_loading_.begin(), tabs_loading_.end(), controller) ==
         tabs_loading_.end());
  tabs_loading_.insert(controller);
  RenderWidgetHost* render_widget_host = GetRenderWidgetHost(controller);
  DCHECK(render_widget_host);
  render_widget_hosts_loading_.insert(render_widget_host);
  RegisterForNotifications(controller);
}

void TabLoader::StartLoading() {
  registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT,
                 NotificationService::AllSources());
#if defined(OS_CHROMEOS)
  if (chromeos::NetworkStateNotifier::is_connected()) {
    loading_ = true;
    LoadNextTab();
  } else {
    // Start listening to network state notification now.
    registrar_.Add(this, NotificationType::NETWORK_STATE_CHANGED,
                   NotificationService::AllSources());
  }
#else
  loading_ = true;
  LoadNextTab();
#endif
}

void TabLoader::LoadNextTab() {
  if (!tabs_to_load_.empty()) {
    NavigationController* tab = tabs_to_load_.front();
    DCHECK(tab);
    tabs_loading_.insert(tab);
    tabs_to_load_.pop_front();
    tab->LoadIfNecessary();
    if (tab->tab_contents()) {
      int tab_index;
      Browser* browser = Browser::GetBrowserForController(tab, &tab_index);
      if (browser && browser->active_index() != tab_index) {
        // By default tabs are marked as visible. As only the active tab is
        // visible we need to explicitly tell non-active tabs they are hidden.
        // Without this call non-active tabs are not marked as backgrounded.
        //
        // NOTE: We need to do this here rather than when the tab is added to
        // the Browser as at that time not everything has been created, so that
        // the call would do nothing.
        tab->tab_contents()->WasHidden();
      }
    }
  }

  if (!tabs_to_load_.empty()) {
    force_load_timer_.Stop();
    // Each time we load a tab we also set a timer to force us to start loading
    // the next tab if this one doesn't load quickly enough.
    force_load_timer_.Start(
        base::TimeDelta::FromMilliseconds(force_load_delay_),
        this, &TabLoader::ForceLoadTimerFired);
  }
}

void TabLoader::Observe(NotificationType type,
                        const NotificationSource& source,
                        const NotificationDetails& details) {
  switch (type.value) {
#if defined(OS_CHROMEOS)
    case NotificationType::NETWORK_STATE_CHANGED: {
      chromeos::NetworkStateDetails* state_details =
          Details<chromeos::NetworkStateDetails>(details).ptr();
      switch (state_details->state()) {
        case chromeos::NetworkStateDetails::CONNECTED:
          if (!loading_) {
            loading_ = true;
            LoadNextTab();
          }
          // Start loading
          break;
        case chromeos::NetworkStateDetails::CONNECTING:
        case chromeos::NetworkStateDetails::DISCONNECTED:
          // Disconnected while loading. Set loading_ false so
          // that it stops trying to load next tab.
          loading_ = false;
          break;
        default:
          NOTREACHED() << "Unknown nework state notification:"
                       << state_details->state();
      }
      break;
    }
#endif
    case NotificationType::LOAD_START: {
      // Add this render_widget_host to the set of those we're waiting for
      // paints on. We want to only record stats for paints that occur after
      // a load has finished.
      NavigationController* tab = Source<NavigationController>(source).ptr();
      RenderWidgetHost* render_widget_host = GetRenderWidgetHost(tab);
      DCHECK(render_widget_host);
      render_widget_hosts_loading_.insert(render_widget_host);
      break;
    }
    case NotificationType::TAB_CONTENTS_DESTROYED: {
      TabContents* tab_contents = Source<TabContents>(source).ptr();
      if (!got_first_paint_) {
        RenderWidgetHost* render_widget_host =
            GetRenderWidgetHost(&tab_contents->controller());
        render_widget_hosts_loading_.erase(render_widget_host);
      }
      HandleTabClosedOrLoaded(&tab_contents->controller());
      break;
    }
    case NotificationType::LOAD_STOP: {
      NavigationController* tab = Source<NavigationController>(source).ptr();
      render_widget_hosts_to_paint_.insert(GetRenderWidgetHost(tab));
      HandleTabClosedOrLoaded(tab);
      break;
    }
    case NotificationType::RENDER_WIDGET_HOST_DID_PAINT: {
      if (!got_first_paint_) {
        RenderWidgetHost* render_widget_host =
            Source<RenderWidgetHost>(source).ptr();
        if (render_widget_hosts_to_paint_.find(render_widget_host) !=
            render_widget_hosts_to_paint_.end()) {
          // Got a paint for one of our renderers, so record time.
          got_first_paint_ = true;
          base::TimeDelta time_to_paint =
              base::TimeTicks::Now() - restore_started_;
          UMA_HISTOGRAM_CUSTOM_TIMES(
              "SessionRestore.FirstTabPainted",
              time_to_paint,
              base::TimeDelta::FromMilliseconds(10),
              base::TimeDelta::FromSeconds(100),
              100);
          // Record a time for the number of tabs, to help track down
          // contention.
          std::string time_for_count =
              StringPrintf("SessionRestore.FirstTabPainted_%d", tab_count_);
          base::Histogram* counter_for_count =
              base::Histogram::FactoryTimeGet(
                  time_for_count,
                  base::TimeDelta::FromMilliseconds(10),
                  base::TimeDelta::FromSeconds(100),
                  100,
                  base::Histogram::kUmaTargetedHistogramFlag);
          counter_for_count->AddTime(time_to_paint);
        } else if (render_widget_hosts_loading_.find(render_widget_host) ==
            render_widget_hosts_loading_.end()) {
          // If this is a host for a tab we're not loading some other tab
          // has rendered and there's no point tracking the time. This could
          // happen because the user opened a different tab or restored tabs
          // to an already existing browser and an existing tab painted.
          got_first_paint_ = true;
        }
      }
      break;
    }
    default:
      NOTREACHED() << "Unknown notification received:" << type.value;
  }
  // Delete ourselves when we're not waiting for any more notifications.
  if ((got_first_paint_ || render_widget_hosts_to_paint_.empty()) &&
      tabs_loading_.empty() && tabs_to_load_.empty())
    delete this;
}

void TabLoader::RemoveTab(NavigationController* tab) {
  registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
                    Source<TabContents>(tab->tab_contents()));
  registrar_.Remove(this, NotificationType::LOAD_STOP,
                    Source<NavigationController>(tab));
  registrar_.Remove(this, NotificationType::LOAD_START,
                    Source<NavigationController>(tab));

  TabsLoading::iterator i = tabs_loading_.find(tab);
  if (i != tabs_loading_.end())
    tabs_loading_.erase(i);

  TabsToLoad::iterator j =
      find(tabs_to_load_.begin(), tabs_to_load_.end(), tab);
  if (j != tabs_to_load_.end())
    tabs_to_load_.erase(j);
}

void TabLoader::ForceLoadTimerFired() {
  force_load_delay_ *= 2;
  LoadNextTab();
}

RenderWidgetHost* TabLoader::GetRenderWidgetHost(NavigationController* tab) {
  TabContents* tab_contents = tab->tab_contents();
  if (tab_contents) {
    RenderWidgetHostView* render_widget_host_view =
        tab_contents->GetRenderWidgetHostView();
    if (render_widget_host_view)
      return render_widget_host_view->GetRenderWidgetHost();
  }
  return NULL;
}

void TabLoader::RegisterForNotifications(NavigationController* controller) {
  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
                 Source<TabContents>(controller->tab_contents()));
  registrar_.Add(this, NotificationType::LOAD_STOP,
                 Source<NavigationController>(controller));
  registrar_.Add(this, NotificationType::LOAD_START,
                 Source<NavigationController>(controller));
  ++tab_count_;
}

void TabLoader::HandleTabClosedOrLoaded(NavigationController* tab) {
  RemoveTab(tab);
  if (loading_)
    LoadNextTab();
  if (tabs_loading_.empty() && tabs_to_load_.empty()) {
    base::TimeDelta time_to_load =
        base::TimeTicks::Now() - restore_started_;
    UMA_HISTOGRAM_CUSTOM_TIMES(
        "SessionRestore.AllTabsLoaded",
        time_to_load,
        base::TimeDelta::FromMilliseconds(10),
        base::TimeDelta::FromSeconds(100),
        100);
    // Record a time for the number of tabs, to help track down contention.
    std::string time_for_count =
        StringPrintf("SessionRestore.AllTabsLoaded_%d", tab_count_);
    base::Histogram* counter_for_count =
        base::Histogram::FactoryTimeGet(
            time_for_count,
            base::TimeDelta::FromMilliseconds(10),
            base::TimeDelta::FromSeconds(100),
            100,
            base::Histogram::kUmaTargetedHistogramFlag);
    counter_for_count->AddTime(time_to_load);
  }
}

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

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

class SessionRestoreImpl : public NotificationObserver {
 public:
  SessionRestoreImpl(Profile* profile,
                     Browser* browser,
                     bool synchronous,
                     bool clobber_existing_window,
                     bool always_create_tabbed_browser,
                     const std::vector<GURL>& urls_to_open)
      : profile_(profile),
        browser_(browser),
        synchronous_(synchronous),
        clobber_existing_window_(clobber_existing_window),
        always_create_tabbed_browser_(always_create_tabbed_browser),
        urls_to_open_(urls_to_open),
        restore_started_(base::TimeTicks::Now()) {
  }

  Browser* Restore() {
    SessionService* session_service = profile_->GetSessionService();
    DCHECK(session_service);
    SessionService::SessionCallback* callback =
        NewCallback(this, &SessionRestoreImpl::OnGotSession);
    session_service->GetLastSession(&request_consumer_, callback);

    if (synchronous_) {
      bool old_state = MessageLoop::current()->NestableTasksAllowed();
      MessageLoop::current()->SetNestableTasksAllowed(true);
      MessageLoop::current()->Run();
      MessageLoop::current()->SetNestableTasksAllowed(old_state);
      Browser* browser = ProcessSessionWindows(&windows_);
      delete this;
      return browser;
    }

    if (browser_) {
      registrar_.Add(this, NotificationType::BROWSER_CLOSED,
                     Source<Browser>(browser_));
    }

    return browser_;
  }

  // Restore window(s) from a foreign session.
  void RestoreForeignSession(
      std::vector<SessionWindow*>::const_iterator begin,
      std::vector<SessionWindow*>::const_iterator end) {
    StartTabCreation();
    // Create a browser instance to put the restored tabs in.
    for (std::vector<SessionWindow*>::const_iterator i = begin;
        i != end; ++i) {
      Browser* browser = CreateRestoredBrowser(
          static_cast<Browser::Type>((*i)->type),
          (*i)->bounds,
          (*i)->is_maximized);

      // Restore and show the browser.
      const int initial_tab_count = browser->tab_count();
      int selected_tab_index = (*i)->selected_tab_index;
      RestoreTabsToBrowser(*(*i), browser, selected_tab_index);
      ShowBrowser(browser, initial_tab_count, selected_tab_index);
      tab_loader_->TabIsLoading(
          &browser->GetSelectedTabContents()->controller());
      NotifySessionServiceOfRestoredTabs(browser, initial_tab_count);
    }

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

  // Restore a single tab from a foreign session.
  // Note: we currently restore the tab to the last active browser.
  void RestoreForeignTab(const SessionTab& tab) {
    StartTabCreation();
    Browser* current_browser =
        browser_ ? browser_ : BrowserList::GetLastActive();
    RestoreTab(tab, current_browser->tab_count(), current_browser, true);
    NotifySessionServiceOfRestoredTabs(current_browser,
                                       current_browser->tab_count());
    FinishedTabCreation(true, true);
  }

  ~SessionRestoreImpl() {
    STLDeleteElements(&windows_);
    restoring = false;
  }

  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details) {
    switch (type.value) {
      case NotificationType::BROWSER_CLOSED:
        delete this;
        return;

      default:
        NOTREACHED();
        break;
    }
  }

 private:
  // Invoked when beginning to create new tabs. Resets the tab_loader_.
  void StartTabCreation() {
    tab_loader_.reset(new TabLoader(restore_started_));
  }

  // 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 Browser that was created, if any.
  Browser* FinishedTabCreation(bool succeeded, bool created_tabbed_browser) {
    Browser* browser = NULL;
    if (!created_tabbed_browser && always_create_tabbed_browser_) {
      browser = Browser::Create(profile_);
      if (urls_to_open_.empty()) {
        // No tab browsers were created and no URLs were supplied on the command
        // line. Add an empty URL, which is treated as opening the users home
        // page.
        urls_to_open_.push_back(GURL());
      }
      AppendURLsToBrowser(browser, urls_to_open_);
      browser->window()->Show();
    }

    if (succeeded) {
      DCHECK(tab_loader_.get());
      // TabLoader delets itself when done loading.
      tab_loader_.release()->StartLoading();
    }

    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.
      MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    }

    return browser;
  }

  void OnGotSession(SessionService::Handle handle,
                    std::vector<SessionWindow*>* windows) {
    if (synchronous_) {
      // See comment above windows_ as to why we don't process immediately.
      windows_.swap(*windows);
      MessageLoop::current()->Quit();
      return;
    }

    ProcessSessionWindows(windows);
  }

  Browser* ProcessSessionWindows(std::vector<SessionWindow*>* windows) {
    if (windows->empty()) {
      // Restore was unsuccessful.
      return FinishedTabCreation(false, false);
    }

    StartTabCreation();

    Browser* current_browser =
        browser_ ? browser_ : BrowserList::GetLastActive();
    // After the for loop this contains the last TABBED_BROWSER. Is null if no
    // tabbed browsers exist.
    Browser* last_browser = NULL;
    bool has_tabbed_browser = false;
    for (std::vector<SessionWindow*>::iterator i = windows->begin();
         i != windows->end(); ++i) {
      Browser* browser = NULL;
      if (!has_tabbed_browser && (*i)->type == Browser::TYPE_NORMAL)
        has_tabbed_browser = true;
      if (i == windows->begin() && (*i)->type == Browser::TYPE_NORMAL &&
          !clobber_existing_window_) {
        // If there is an open tabbed browser window, use it. Otherwise fall
        // through and create a new one.
        browser = current_browser;
        if (browser && (browser->type() != Browser::TYPE_NORMAL ||
                        browser->profile()->IsOffTheRecord())) {
          browser = NULL;
        }
      }
      if (!browser) {
        browser = CreateRestoredBrowser(
            static_cast<Browser::Type>((*i)->type),
            (*i)->bounds,
            (*i)->is_maximized);
      }
      if ((*i)->type == Browser::TYPE_NORMAL)
        last_browser = browser;
      const int initial_tab_count = browser->tab_count();
      int selected_tab_index = (*i)->selected_tab_index;
      RestoreTabsToBrowser(*(*i), browser, selected_tab_index);
      ShowBrowser(browser, initial_tab_count, selected_tab_index);
      tab_loader_->TabIsLoading(
          &browser->GetSelectedTabContents()->controller());
      NotifySessionServiceOfRestoredTabs(browser, initial_tab_count);
    }

    // If we're restoring a session as the result of a crash and the session
    // included at least one tabbed browser, then close the browser window
    // that was opened when the user clicked to restore the session.
    if (clobber_existing_window_ && current_browser && has_tabbed_browser &&
        current_browser->type() == Browser::TYPE_NORMAL) {
      current_browser->CloseAllTabs();
    }
    if (last_browser && !urls_to_open_.empty())
      AppendURLsToBrowser(last_browser, urls_to_open_);
    // If last_browser is NULL and urls_to_open_ is non-empty,
    // FinishedTabCreation will create a new TabbedBrowser and add the urls to
    // it.
    Browser* finished_browser = FinishedTabCreation(true, has_tabbed_browser);
    if (finished_browser)
      last_browser = finished_browser;
    return last_browser;
  }

  void RestoreTabsToBrowser(const SessionWindow& window,
                            Browser* browser,
                            int selected_tab_index) {
    DCHECK(!window.tabs.empty());
    for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin();
         i != window.tabs.end(); ++i) {
      const SessionTab& tab = *(*i);
      const int tab_index = static_cast<int>(i - window.tabs.begin());
      // Don't schedule a load for the selected tab, as ShowBrowser() will
      // already have done that.
      RestoreTab(tab, tab_index, browser, tab_index != selected_tab_index);
    }
  }

  void RestoreTab(const SessionTab& tab,
                  const int tab_index,
                  Browser* browser,
                  bool schedule_load) {
    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)));

    // Record an app launch, if applicable.
    GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
    if (
#if defined(OS_CHROMEOS)
        browser->profile()->GetExtensionService() &&
#endif
        browser->profile()->GetExtensionService()->IsInstalledApp(url)) {
      UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
                                extension_misc::APP_LAUNCH_SESSION_RESTORE,
                                extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
    }

    TabContents* tab_contents =
        browser->AddRestoredTab(tab.navigations,
                                tab_index,
                                selected_index,
                                tab.extension_app_id,
                                false,
                                tab.pinned,
                                true,
                                NULL);
    if (schedule_load)
      tab_loader_->ScheduleLoad(&tab_contents->controller());
  }

  Browser* CreateRestoredBrowser(Browser::Type type,
                                 gfx::Rect bounds,
                                 bool is_maximized) {
    Browser* browser = new Browser(type, profile_);
    browser->set_override_bounds(bounds);
    browser->set_maximized_state(is_maximized ?
        Browser::MAXIMIZED_STATE_MAXIMIZED :
        Browser::MAXIMIZED_STATE_UNMAXIMIZED);
    browser->InitBrowserWindow();
    return browser;
  }

  void ShowBrowser(Browser* browser,
                   int initial_tab_count,
                   int selected_session_index) {
    if (browser_ == browser) {
      browser->ActivateTabAt(browser->tab_count() - 1, true);
      return;
    }

    DCHECK(browser);
    DCHECK(browser->tab_count());
    browser->ActivateTabAt(
        std::min(initial_tab_count + std::max(0, selected_session_index),
                 browser->tab_count() - 1), true);
    browser->window()->Show();
    // TODO(jcampan): http://crbug.com/8123 we should not need to set the
    //                initial focus explicitly.
    browser->GetSelectedTabContents()->view()->SetInitialFocus();
  }

  // Appends the urls in |urls| to |browser|.
  void AppendURLsToBrowser(Browser* browser,
                           const std::vector<GURL>& urls) {
    for (size_t i = 0; i < urls.size(); ++i) {
      int add_types = TabStripModel::ADD_FORCE_INDEX;
      if (i == 0)
        add_types |= TabStripModel::ADD_ACTIVE;
      int index = browser->GetIndexForInsertionDuringRestore(i);
      browser::NavigateParams params(browser, urls[i],
                                     PageTransition::START_PAGE);
      params.disposition = i == 0 ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB;
      params.tabstrip_index = index;
      params.tabstrip_add_types = add_types;
      browser::Navigate(&params);
    }
  }

  // Invokes TabRestored on the SessionService for all tabs in browser after
  // initial_count.
  void NotifySessionServiceOfRestoredTabs(Browser* browser, int initial_count) {
    SessionService* session_service = profile_->GetSessionService();
    for (int i = initial_count; i < browser->tab_count(); ++i)
      session_service->TabRestored(&browser->GetTabContentsAt(i)->controller(),
                                   browser->tabstrip_model()->IsTabPinned(i));
  }

  // The profile to create the sessions for.
  Profile* profile_;

  // The first browser to restore to, may be null.
  Browser* browser_;

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

  // See description in RestoreSession (in .h).
  const bool clobber_existing_window_;

  // 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_;

  // Set of URLs to open in addition to those restored from the session.
  std::vector<GURL> urls_to_open_;

  // Used to get the session.
  CancelableRequestConsumer request_consumer_;

  // Responsible for loading the tabs.
  scoped_ptr<TabLoader> tab_loader_;

  // When synchronous we run a nested message loop. To avoid creating windows
  // from the nested message 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 message loop exits.
  std::vector<SessionWindow*> windows_;

  NotificationRegistrar registrar_;

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

}  // namespace

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

static Browser* Restore(Profile* profile,
                        Browser* browser,
                        bool synchronous,
                        bool clobber_existing_window,
                        bool always_create_tabbed_browser,
                        const std::vector<GURL>& urls_to_open) {
#if defined(OS_CHROMEOS)
  chromeos::BootTimesLoader::Get()->AddLoginTimeMarker(
      "SessionRestoreStarted", false);
#endif
  DCHECK(profile);
  // Always restore from the original profile (incognito profiles have no
  // session service).
  profile = profile->GetOriginalProfile();
  if (!profile->GetSessionService()) {
    NOTREACHED();
    return NULL;
  }
  restoring = true;
  profile->set_restored_last_session(true);
  // SessionRestoreImpl takes care of deleting itself when done.
  SessionRestoreImpl* restorer =
      new SessionRestoreImpl(profile, browser, synchronous,
                             clobber_existing_window,
                             always_create_tabbed_browser, urls_to_open);
  return restorer->Restore();
}

// static
void SessionRestore::RestoreSession(Profile* profile,
                                    Browser* browser,
                                    bool clobber_existing_window,
                                    bool always_create_tabbed_browser,
                                    const std::vector<GURL>& urls_to_open) {
  Restore(profile, browser, false, clobber_existing_window,
          always_create_tabbed_browser, urls_to_open);
}

// static
void SessionRestore::RestoreForeignSessionWindows(
    Profile* profile,
    std::vector<SessionWindow*>::const_iterator begin,
    std::vector<SessionWindow*>::const_iterator end) {
  // Create a SessionRestore object to eventually restore the tabs.
  std::vector<GURL> gurls;
  SessionRestoreImpl restorer(profile,
      static_cast<Browser*>(NULL), true, false, true, gurls);
  restorer.RestoreForeignSession(begin, end);
}

// static
void SessionRestore::RestoreForeignSessionTab(Profile* profile,
    const SessionTab& tab) {
  // Create a SessionRestore object to eventually restore the tabs.
  std::vector<GURL> gurls;
  SessionRestoreImpl restorer(profile,
      static_cast<Browser*>(NULL), true, false, true, gurls);
  restorer.RestoreForeignTab(tab);
}

// static
Browser* SessionRestore::RestoreSessionSynchronously(
    Profile* profile,
    const std::vector<GURL>& urls_to_open) {
  return Restore(profile, NULL, true, false, true, urls_to_open);
}

// static
bool SessionRestore::IsRestoring() {
  return restoring;
}