// 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/ui/browser_navigator.h"

#include <algorithm>

#include "base/command_line.h"
#include "chrome/browser/browser_url_handler.h"
#include "chrome/browser/extensions/extension_tab_helper.h"
#include "chrome/browser/profiles/profile.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_window.h"
#include "chrome/browser/ui/omnibox/location_bar.h"
#include "chrome/browser/ui/status_bubble.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/browser/ui/webui/chrome_web_ui_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "content/browser/site_instance.h"
#include "content/browser/tab_contents/tab_contents.h"

namespace {

// Returns an appropriate SiteInstance for WebUI URLs, or the SiteInstance for
// |source_contents| if it represents the same website as |url|.  Returns NULL
// otherwise.
SiteInstance* GetSiteInstance(TabContents* source_contents, Profile* profile,
                              const GURL& url) {
  // If url is a WebUI or extension, we need to be sure to use the right type
  // of renderer process up front.  Otherwise, we create a normal SiteInstance
  // as part of creating the tab.
  if (ChromeWebUIFactory::GetInstance()->UseWebUIForURL(profile, url))
    return SiteInstance::CreateSiteInstanceForURL(profile, url);

  if (!source_contents)
    return NULL;

  // Don't use this logic when "--process-per-tab" is specified.
  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab) &&
      SiteInstance::IsSameWebSite(source_contents->profile(),
                                  source_contents->GetURL(),
                                  url)) {
    return source_contents->GetSiteInstance();
  }
  return NULL;
}

// Returns true if the specified Browser can open tabs. Not all Browsers support
// multiple tabs, such as app frames and popups. This function returns false for
// those types of Browser.
bool WindowCanOpenTabs(Browser* browser) {
  return browser->CanSupportWindowFeature(Browser::FEATURE_TABSTRIP) ||
      browser->tabstrip_model()->empty();
}

// Finds an existing Browser compatible with |profile|, making a new one if no
// such Browser is located.
Browser* GetOrCreateBrowser(Profile* profile) {
  Browser* browser = BrowserList::FindBrowserWithType(profile,
                                                      Browser::TYPE_NORMAL,
                                                      false);
  return browser ? browser : Browser::Create(profile);
}

// Returns true if two URLs are equal after taking |replacements| into account.
bool CompareURLsWithReplacements(
    const GURL& url,
    const GURL& other,
    const url_canon::Replacements<char>& replacements) {
  if (url == other)
    return true;

  GURL url_replaced = url.ReplaceComponents(replacements);
  GURL other_replaced = other.ReplaceComponents(replacements);
  return url_replaced == other_replaced;
}

// Returns the index of an existing singleton tab in |params->browser| matching
// the URL specified in |params|.
int GetIndexOfSingletonTab(browser::NavigateParams* params) {
  if (params->disposition != SINGLETON_TAB)
    return -1;

  // In case the URL was rewritten by the BrowserURLHandler we need to ensure
  // that we do not open another URL that will get redirected to the rewritten
  // URL.
  GURL rewritten_url(params->url);
  bool reverse_on_redirect = false;
  BrowserURLHandler::RewriteURLIfNecessary(&rewritten_url,
                                           params->browser->profile(),
                                           &reverse_on_redirect);

  // If there are several matches: prefer the active tab by starting there.
  int start_index = std::max(0, params->browser->active_index());
  int tab_count = params->browser->tab_count();
  for (int i = 0; i < tab_count; ++i) {
    int tab_index = (start_index + i) % tab_count;
    TabContentsWrapper* tab =
        params->browser->GetTabContentsWrapperAt(tab_index);

    url_canon::Replacements<char> replacements;
    replacements.ClearRef();
    if (params->path_behavior == browser::NavigateParams::IGNORE_AND_NAVIGATE ||
        params->path_behavior == browser::NavigateParams::IGNORE_AND_STAY_PUT) {
      replacements.ClearPath();
      replacements.ClearQuery();
    }

    if (CompareURLsWithReplacements(tab->tab_contents()->GetURL(),
                                    params->url, replacements) ||
        CompareURLsWithReplacements(tab->tab_contents()->GetURL(),
                                    rewritten_url, replacements)) {
      params->target_contents = tab;
      return tab_index;
    }
  }

  return -1;
}

// Change some of the navigation parameters based on the particular URL.
// Currently this applies to chrome://settings and the bookmark manager,
// which we always want to open in a normal (not incognito) window. Guest
// session is an exception.
void AdjustNavigateParamsForURL(browser::NavigateParams* params) {
  if (!params->target_contents &&
      params->url.scheme() == chrome::kChromeUIScheme &&
      (params->url.host() == chrome::kChromeUISettingsHost ||
       params->url.host() == chrome::kChromeUIBookmarksHost)) {
    Profile* profile =
        params->browser ? params->browser->profile() : params->profile;

    if (profile->IsOffTheRecord() && !Profile::IsGuestSession()) {
      profile = profile->GetOriginalProfile();

      params->disposition = SINGLETON_TAB;
      params->profile = profile;
      params->browser = Browser::GetOrCreateTabbedBrowser(profile);
      params->window_action = browser::NavigateParams::SHOW_WINDOW;
    }
  }
}

// Returns a Browser that can host the navigation or tab addition specified in
// |params|. This might just return the same Browser specified in |params|, or
// some other if that Browser is deemed incompatible.
Browser* GetBrowserForDisposition(browser::NavigateParams* params) {
  // If no source TabContents was specified, we use the selected one from the
  // target browser. This must happen first, before GetBrowserForDisposition()
  // has a chance to replace |params->browser| with another one.
  if (!params->source_contents && params->browser)
    params->source_contents =
        params->browser->GetSelectedTabContentsWrapper();

  Profile* profile =
      params->browser ? params->browser->profile() : params->profile;

  switch (params->disposition) {
    case CURRENT_TAB:
      if (!params->browser && profile) {
        // We specified a profile instead of a browser; find or create one.
        params->browser = Browser::GetOrCreateTabbedBrowser(profile);
      }
      return params->browser;
    case SINGLETON_TAB:
    case NEW_FOREGROUND_TAB:
    case NEW_BACKGROUND_TAB:
      // See if we can open the tab in the window this navigator is bound to.
      if (params->browser && WindowCanOpenTabs(params->browser))
        return params->browser;
      // Find a compatible window and re-execute this command in it. Otherwise
      // re-run with NEW_WINDOW.
      if (profile)
        return GetOrCreateBrowser(profile);
      return NULL;
    case NEW_POPUP: {
      // Make a new popup window. Coerce app-style if |params->browser| or the
      // |source| represents an app.
      Browser::Type type = Browser::TYPE_POPUP;
      if ((params->browser && (params->browser->type() & Browser::TYPE_APP)) ||
          (params->source_contents &&
           params->source_contents->extension_tab_helper()->is_app())) {
        type = Browser::TYPE_APP_POPUP;
      }
      if (profile) {
        Browser* browser = new Browser(type, profile);
        browser->set_override_bounds(params->window_bounds);
        browser->InitBrowserWindow();
        return browser;
      }
      return NULL;
    }
    case NEW_WINDOW:
      // Make a new normal browser window.
      if (profile) {
        Browser* browser = new Browser(Browser::TYPE_NORMAL, profile);
        browser->InitBrowserWindow();
        return browser;
      }
      return NULL;
    case OFF_THE_RECORD:
      // Make or find an incognito window.
      if (profile)
        return GetOrCreateBrowser(profile->GetOffTheRecordProfile());
      return NULL;
    // The following types all result in no navigation.
    case SUPPRESS_OPEN:
    case SAVE_TO_DISK:
    case IGNORE_ACTION:
      return NULL;
    default:
      NOTREACHED();
  }
  return NULL;
}

// Fix disposition and other parameter values depending on prevailing
// conditions.
void NormalizeDisposition(browser::NavigateParams* params) {
  // Calculate the WindowOpenDisposition if necessary.
  if (params->browser->tabstrip_model()->empty() &&
      (params->disposition == NEW_BACKGROUND_TAB ||
       params->disposition == CURRENT_TAB ||
       params->disposition == SINGLETON_TAB)) {
    params->disposition = NEW_FOREGROUND_TAB;
  }
  if (params->browser->profile()->IsOffTheRecord() &&
      params->disposition == OFF_THE_RECORD) {
    params->disposition = NEW_FOREGROUND_TAB;
  }

  switch (params->disposition) {
    case NEW_BACKGROUND_TAB:
      // Disposition trumps add types. ADD_ACTIVE is a default, so we need to
      // remove it if disposition implies the tab is going to open in the
      // background.
      params->tabstrip_add_types &= ~TabStripModel::ADD_ACTIVE;
      break;

    case NEW_WINDOW:
    case NEW_POPUP:
      // Code that wants to open a new window typically expects it to be shown
      // automatically.
      if (params->window_action == browser::NavigateParams::NO_ACTION)
        params->window_action = browser::NavigateParams::SHOW_WINDOW;
      // Fall-through.
    case NEW_FOREGROUND_TAB:
    case SINGLETON_TAB:
      params->tabstrip_add_types |= TabStripModel::ADD_ACTIVE;
      break;

    default:
      break;
  }
}

// Obtain the profile used by the code that originated the Navigate() request.
// |source_browser| represents the Browser that was supplied in |params| before
// it was modified.
Profile* GetSourceProfile(browser::NavigateParams* params,
    Browser* source_browser) {
  if (params->source_contents)
    return params->source_contents->profile();

  if (source_browser)
    return source_browser->profile();

  if (params->profile)
    return params->profile;

  // We couldn't find one in any of the source metadata, so we'll fall back to
  // the profile associated with the target browser.
  return params->browser->profile();
}


// This class makes sure the Browser object held in |params| is made visible
// by the time it goes out of scope, provided |params| wants it to be shown.
class ScopedBrowserDisplayer {
 public:
  explicit ScopedBrowserDisplayer(browser::NavigateParams* params)
      : params_(params) {
  }
  ~ScopedBrowserDisplayer() {
    if (params_->window_action == browser::NavigateParams::SHOW_WINDOW_INACTIVE)
      params_->browser->window()->ShowInactive();
    else if (params_->window_action == browser::NavigateParams::SHOW_WINDOW)
      params_->browser->window()->Show();
  }
 private:
  browser::NavigateParams* params_;
  DISALLOW_COPY_AND_ASSIGN(ScopedBrowserDisplayer);
};

// This class manages the lifetime of a TabContents created by the Navigate()
// function. When Navigate() creates a TabContents for a URL, an instance of
// this class takes ownership of it via TakeOwnership() until the TabContents
// is added to a tab strip at which time ownership is relinquished via
// ReleaseOwnership(). If this object goes out of scope without being added
// to a tab strip, the created TabContents is deleted to avoid a leak and the
// params->target_contents field is set to NULL.
class ScopedTargetContentsOwner {
 public:
  explicit ScopedTargetContentsOwner(browser::NavigateParams* params)
      : params_(params) {
  }
  ~ScopedTargetContentsOwner() {
    if (target_contents_owner_.get())
      params_->target_contents = NULL;
  }

  // Assumes ownership of |params_|' target_contents until ReleaseOwnership
  // is called.
  void TakeOwnership() {
    target_contents_owner_.reset(params_->target_contents);
  }

  // Relinquishes ownership of |params_|' target_contents.
  TabContentsWrapper* ReleaseOwnership() {
    return target_contents_owner_.release();
  }

 private:
  browser::NavigateParams* params_;
  scoped_ptr<TabContentsWrapper> target_contents_owner_;
  DISALLOW_COPY_AND_ASSIGN(ScopedTargetContentsOwner);
};

}  // namespace

namespace browser {

NavigateParams::NavigateParams(
    Browser* a_browser,
    const GURL& a_url,
    PageTransition::Type a_transition)
    : url(a_url),
      target_contents(NULL),
      source_contents(NULL),
      disposition(CURRENT_TAB),
      transition(a_transition),
      tabstrip_index(-1),
      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
      window_action(NO_ACTION),
      path_behavior(RESPECT),
      browser(a_browser),
      profile(NULL) {
}

NavigateParams::NavigateParams(Browser* a_browser,
                               TabContentsWrapper* a_target_contents)
    : target_contents(a_target_contents),
      source_contents(NULL),
      disposition(CURRENT_TAB),
      transition(PageTransition::LINK),
      tabstrip_index(-1),
      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
      window_action(NO_ACTION),
      path_behavior(RESPECT),
      browser(a_browser),
      profile(NULL) {
}

NavigateParams::~NavigateParams() {
}

void Navigate(NavigateParams* params) {
  Browser* source_browser = params->browser;
  AdjustNavigateParamsForURL(params);

  params->browser = GetBrowserForDisposition(params);
  if (!params->browser)
    return;
  // Navigate() must not return early after this point.

  if (GetSourceProfile(params, source_browser) != params->browser->profile()) {
    // A tab is being opened from a link from a different profile, we must reset
    // source information that may cause state to be shared.
    params->source_contents = NULL;
    params->referrer = GURL();
  }

  if (params->window_action == browser::NavigateParams::NO_ACTION &&
      source_browser != params->browser &&
      params->browser->tabstrip_model()->empty()) {
    // A new window has been created. So it needs to be displayed.
    params->window_action = browser::NavigateParams::SHOW_WINDOW;
  }

  // Make sure the Browser is shown if params call for it.
  ScopedBrowserDisplayer displayer(params);

  // Makes sure any TabContents created by this function is destroyed if
  // not properly added to a tab strip.
  ScopedTargetContentsOwner target_contents_owner(params);

  // Some dispositions need coercion to base types.
  NormalizeDisposition(params);

  // Determine if the navigation was user initiated. If it was, we need to
  // inform the target TabContents, and we may need to update the UI.
  PageTransition::Type base_transition =
      PageTransition::StripQualifier(params->transition);
  bool user_initiated = base_transition == PageTransition::TYPED ||
                        base_transition == PageTransition::AUTO_BOOKMARK;

  // Check if this is a singleton tab that already exists
  int singleton_index = GetIndexOfSingletonTab(params);

  // If no target TabContents was specified, we need to construct one if we are
  // supposed to target a new tab; unless it's a singleton that already exists.
  if (!params->target_contents && singleton_index < 0) {
    GURL url = params->url.is_empty() ? params->browser->GetHomePage()
                                      : params->url;
    if (params->disposition != CURRENT_TAB) {
      TabContents* source_contents = params->source_contents ?
          params->source_contents->tab_contents() : NULL;
      params->target_contents =
          Browser::TabContentsFactory(
              params->browser->profile(),
              GetSiteInstance(source_contents, params->browser->profile(), url),
              MSG_ROUTING_NONE,
              source_contents,
              NULL);
      // This function takes ownership of |params->target_contents| until it
      // is added to a TabStripModel.
      target_contents_owner.TakeOwnership();
      params->target_contents->extension_tab_helper()->
          SetExtensionAppById(params->extension_app_id);
      // TODO(sky): figure out why this is needed. Without it we seem to get
      // failures in startup tests.
      // By default, content believes it is not hidden.  When adding contents
      // in the background, tell it that it's hidden.
      if ((params->tabstrip_add_types & TabStripModel::ADD_ACTIVE) == 0) {
        // TabStripModel::AddTabContents invokes HideContents if not foreground.
        params->target_contents->tab_contents()->WasHidden();
      }
    } else {
      // ... otherwise if we're loading in the current tab, the target is the
      // same as the source.
      params->target_contents = params->source_contents;
      DCHECK(params->target_contents);
    }

    if (user_initiated) {
      static_cast<RenderViewHostDelegate*>(params->target_contents->
          tab_contents())->OnUserGesture();
    }

    // Perform the actual navigation.
    params->target_contents->controller().LoadURL(url, params->referrer,
                                                  params->transition);
  } else {
    // |target_contents| was specified non-NULL, and so we assume it has already
    // been navigated appropriately. We need to do nothing more other than
    // add it to the appropriate tabstrip.
  }

  if (params->source_contents == params->target_contents) {
    // The navigation occurred in the source tab.
    params->browser->UpdateUIForNavigationInTab(
        params->target_contents,
        params->transition,
        user_initiated);
  } else if (singleton_index == -1) {
    // If some non-default value is set for the index, we should tell the
    // TabStripModel to respect it.
    if (params->tabstrip_index != -1)
      params->tabstrip_add_types |= TabStripModel::ADD_FORCE_INDEX;

    // The navigation should insert a new tab into the target Browser.
    params->browser->tabstrip_model()->AddTabContents(
        params->target_contents,
        params->tabstrip_index,
        params->transition,
        params->tabstrip_add_types);
    // Now that the |params->target_contents| is safely owned by the target
    // Browser's TabStripModel, we can release ownership.
    target_contents_owner.ReleaseOwnership();
  }

  if (singleton_index >= 0) {
    TabContents* target = params->browser->GetTabContentsAt(singleton_index);

    if (params->path_behavior == NavigateParams::IGNORE_AND_NAVIGATE &&
        target->GetURL() != params->url) {
      target->controller().LoadURL(
          params->url, params->referrer, params->transition);
    }

    // If the singleton tab isn't already selected, select it.
    if (params->source_contents != params->target_contents)
      params->browser->ActivateTabAt(singleton_index, user_initiated);
  }
}

}  // namespace browser