// Copyright 2012 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/search/instant_loader.h"

#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/extensions/api/web_navigation/web_navigation_api.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/safe_browsing/safe_browsing_tab_observer.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
#include "chrome/browser/ui/search/search_tab_helper.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents_view.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"

#if !defined(OS_ANDROID)
#include "chrome/browser/ui/blocked_content/popup_blocker_tab_helper.h"
#endif

namespace {

// This HTTP header and value are set on loads that originate from Instant.
const char kInstantHeader[] = "X-Purpose: Instant";

}  // namespace

InstantLoader::Delegate::~Delegate() {
}

InstantLoader::InstantLoader(Delegate* delegate)
    : delegate_(delegate), stale_page_timer_(false, false) {}

InstantLoader::~InstantLoader() {
}

void InstantLoader::Init(const GURL& instant_url,
                         Profile* profile,
                         const base::Closure& on_stale_callback) {
  content::WebContents::CreateParams create_params(profile);
  create_params.site_instance = content::SiteInstance::CreateForURL(
      profile, instant_url);
  SetContents(scoped_ptr<content::WebContents>(
      content::WebContents::Create(create_params)));
  instant_url_ = instant_url;
  on_stale_callback_ = on_stale_callback;
}

void InstantLoader::Load() {
  DVLOG(1) << "LoadURL: " << instant_url_;
  contents_->GetController().LoadURL(
      instant_url_, content::Referrer(),
      content::PAGE_TRANSITION_GENERATED, kInstantHeader);

  // Explicitly set the new tab title and virtual URL.
  //
  // This ensures that the title is set even before we get a title from the
  // page, preventing a potential flicker of the URL, and also ensures that
  // (unless overridden by the page) the new tab title matches the browser UI
  // locale.
  content::NavigationEntry* entry =
      contents_->GetController().GetVisibleEntry();
  if (entry)
    entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));

  contents_->WasHidden();

  int staleness_timeout_ms = chrome::GetInstantLoaderStalenessTimeoutSec() *
      1000;
  if (staleness_timeout_ms > 0) {
    stale_page_timer_.Start(
        FROM_HERE,
        base::TimeDelta::FromMilliseconds(staleness_timeout_ms),
        on_stale_callback_);
  }
}

void InstantLoader::SetContents(scoped_ptr<content::WebContents> new_contents) {
  contents_.reset(new_contents.release());
  contents_->SetDelegate(this);

  // Set up various tab helpers. The rest will get attached when (if) the
  // contents is added to the tab strip.

  // Bookmarks (Users can bookmark the Instant NTP. This ensures the bookmarked
  // state is correctly set when the contents are swapped into a tab.)
  BookmarkTabHelper::CreateForWebContents(contents());

  // A tab helper to catch prerender content swapping shenanigans.
  CoreTabHelper::CreateForWebContents(contents());
  CoreTabHelper::FromWebContents(contents())->set_delegate(this);

  SearchTabHelper::CreateForWebContents(contents());

#if !defined(OS_ANDROID)
  // Observers.
  extensions::WebNavigationTabObserver::CreateForWebContents(contents());
#endif  // OS_ANDROID

  // Favicons, required by the Task Manager.
  FaviconTabHelper::CreateForWebContents(contents());

  // And some flat-out paranoia.
  safe_browsing::SafeBrowsingTabObserver::CreateForWebContents(contents());

  // When the WebContents finishes loading it should be checked to ensure that
  // it is in the instant process.
  registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
                 content::Source<content::WebContents>(contents_.get()));
}

scoped_ptr<content::WebContents> InstantLoader::ReleaseContents() {
  stale_page_timer_.Stop();
  contents_->SetDelegate(NULL);

  // Undo tab helper work done in SetContents().
  CoreTabHelper::FromWebContents(contents())->set_delegate(NULL);

  registrar_.Remove(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
                    content::Source<content::WebContents>(contents_.get()));
  return contents_.Pass();
}

void InstantLoader::Observe(int type,
                            const content::NotificationSource& source,
                            const content::NotificationDetails& details) {
  DCHECK_EQ(type, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME);
  const content::WebContents* web_contents =
      content::Source<content::WebContents>(source).ptr();
  DCHECK_EQ(contents_.get(), web_contents);
  delegate_->LoadCompletedMainFrame();
}

void InstantLoader::SwapTabContents(content::WebContents* old_contents,
                                    content::WebContents* new_contents) {
  DCHECK_EQ(old_contents, contents());
  // We release here without deleting since the caller has the responsibility
  // for deleting the old WebContents.
  ignore_result(ReleaseContents().release());
  SetContents(scoped_ptr<content::WebContents>(new_contents));
  delegate_->OnSwappedContents();
}

bool InstantLoader::ShouldSuppressDialogs() {
  // Messages shown during Instant cancel Instant, so we suppress them.
  return true;
}

bool InstantLoader::ShouldFocusPageAfterCrash() {
  return false;
}

void InstantLoader::CanDownload(content::RenderViewHost* /* render_view_host */,
                                int /* request_id */,
                                const std::string& /* request_method */,
                                const base::Callback<void(bool)>& callback) {
  // Downloads are disabled.
  callback.Run(false);
}

bool InstantLoader::OnGoToEntryOffset(int /* offset */) {
  return false;
}

content::WebContents* InstantLoader::OpenURLFromTab(
    content::WebContents* source,
    const content::OpenURLParams& params) {
  return delegate_->OpenURLFromTab(source, params);
}