// 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/task_manager/tab_contents_resource_provider.h"

#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/task_manager/renderer_resource.h"
#include "chrome/browser/task_manager/resource_provider.h"
#include "chrome/browser/task_manager/task_manager.h"
#include "chrome/browser/task_manager/task_manager_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/constants.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia.h"

#if defined(ENABLE_FULL_PRINTING)
#include "chrome/browser/printing/background_printing_manager.h"
#endif

using content::WebContents;
using extensions::Extension;

namespace {

bool IsContentsPrerendering(WebContents* web_contents) {
  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  prerender::PrerenderManager* prerender_manager =
      prerender::PrerenderManagerFactory::GetForProfile(profile);
  return prerender_manager &&
         prerender_manager->IsWebContentsPrerendering(web_contents, NULL);
}

bool IsContentsBackgroundPrinted(WebContents* web_contents) {
#if defined(ENABLE_FULL_PRINTING)
  printing::BackgroundPrintingManager* printing_manager =
      g_browser_process->background_printing_manager();
  return printing_manager->HasPrintPreviewDialog(web_contents);
#else
  return false;
#endif
}

}  // namespace

namespace task_manager {

// Tracks a single tab contents, prerendered page, Instant page, or background
// printing page.
class TabContentsResource : public RendererResource {
 public:
  explicit TabContentsResource(content::WebContents* web_contents);
  virtual ~TabContentsResource();

  // Resource methods:
  virtual Type GetType() const OVERRIDE;
  virtual base::string16 GetTitle() const OVERRIDE;
  virtual base::string16 GetProfileName() const OVERRIDE;
  virtual gfx::ImageSkia GetIcon() const OVERRIDE;
  virtual content::WebContents* GetWebContents() const OVERRIDE;
  virtual const extensions::Extension* GetExtension() const OVERRIDE;

 private:
  // Returns true if contains content rendered by an extension.
  bool HostsExtension() const;

  static gfx::ImageSkia* prerender_icon_;
  content::WebContents* web_contents_;
  Profile* profile_;
  bool is_instant_ntp_;

  DISALLOW_COPY_AND_ASSIGN(TabContentsResource);
};

gfx::ImageSkia* TabContentsResource::prerender_icon_ = NULL;

TabContentsResource::TabContentsResource(
    WebContents* web_contents)
    : RendererResource(web_contents->GetRenderProcessHost()->GetHandle(),
                       web_contents->GetRenderViewHost()),
      web_contents_(web_contents),
      profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
      is_instant_ntp_(chrome::IsPreloadedInstantExtendedNTP(web_contents)) {
  if (!prerender_icon_) {
    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    prerender_icon_ = rb.GetImageSkiaNamed(IDR_PRERENDER);
  }
}

TabContentsResource::~TabContentsResource() {
}

bool TabContentsResource::HostsExtension() const {
  return web_contents_->GetURL().SchemeIs(extensions::kExtensionScheme);
}

Resource::Type TabContentsResource::GetType() const {
  return HostsExtension() ? EXTENSION : RENDERER;
}

base::string16 TabContentsResource::GetTitle() const {
  // Fall back on the URL if there's no title.
  GURL url = web_contents_->GetURL();
  base::string16 tab_title = util::GetTitleFromWebContents(web_contents_);

  // Only classify as an app if the URL is an app and the tab is hosting an
  // extension process.  (It's possible to be showing the URL from before it
  // was installed as an app.)
  ExtensionService* extension_service = profile_->GetExtensionService();
  extensions::ProcessMap* process_map = extension_service->process_map();
  bool is_app = extension_service->IsInstalledApp(url) &&
      process_map->Contains(web_contents_->GetRenderProcessHost()->GetID());

  int message_id = util::GetMessagePrefixID(
      is_app,
      HostsExtension(),
      profile_->IsOffTheRecord(),
      IsContentsPrerendering(web_contents_),
      is_instant_ntp_,
      false);  // is_background
  return l10n_util::GetStringFUTF16(message_id, tab_title);
}

base::string16 TabContentsResource::GetProfileName() const {
  return util::GetProfileNameFromInfoCache(profile_);
}

gfx::ImageSkia TabContentsResource::GetIcon() const {
  if (IsContentsPrerendering(web_contents_))
    return *prerender_icon_;
  FaviconTabHelper::CreateForWebContents(web_contents_);
  return FaviconTabHelper::FromWebContents(web_contents_)->
      GetFavicon().AsImageSkia();
}

WebContents* TabContentsResource::GetWebContents() const {
  return web_contents_;
}

const Extension* TabContentsResource::GetExtension() const {
  if (HostsExtension()) {
    ExtensionService* extension_service = profile_->GetExtensionService();
    return extension_service->extensions()->GetByID(
        web_contents_->GetURL().host());
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////////////////
// TabContentsResourceProvider class
////////////////////////////////////////////////////////////////////////////////

TabContentsResourceProvider::
    TabContentsResourceProvider(TaskManager* task_manager)
    :  updating_(false),
       task_manager_(task_manager) {
}

TabContentsResourceProvider::~TabContentsResourceProvider() {
}

Resource* TabContentsResourceProvider::GetResource(
    int origin_pid,
    int render_process_host_id,
    int routing_id) {
  WebContents* web_contents =
      tab_util::GetWebContentsByID(render_process_host_id, routing_id);
  if (!web_contents)  // Not one of our resource.
    return NULL;

  // If an origin PID was specified then the request originated in a plugin
  // working on the WebContents's behalf, so ignore it.
  if (origin_pid)
    return NULL;

  std::map<WebContents*, TabContentsResource*>::iterator
      res_iter = resources_.find(web_contents);
  if (res_iter == resources_.end()) {
    // Can happen if the tab was closed while a network request was being
    // performed.
    return NULL;
  }
  return res_iter->second;
}

void TabContentsResourceProvider::StartUpdating() {
  DCHECK(!updating_);
  updating_ = true;

  // The contents that are tracked by this resource provider are those that
  // are tab contents (WebContents serving as a tab in a Browser), Instant
  // pages, prerender pages, and background printed pages.

  // Add all the existing WebContentses.
  for (TabContentsIterator iterator; !iterator.done(); iterator.Next()) {
    Add(*iterator);
    DevToolsWindow* docked =
        DevToolsWindow::GetDockedInstanceForInspectedTab(*iterator);
    if (docked)
      Add(docked->web_contents());
  }

  // Add all the prerender pages.
  std::vector<Profile*> profiles(
      g_browser_process->profile_manager()->GetLoadedProfiles());
  for (size_t i = 0; i < profiles.size(); ++i) {
    prerender::PrerenderManager* prerender_manager =
        prerender::PrerenderManagerFactory::GetForProfile(profiles[i]);
    if (prerender_manager) {
      const std::vector<content::WebContents*> contentses =
          prerender_manager->GetAllPrerenderingContents();
      for (size_t j = 0; j < contentses.size(); ++j)
        Add(contentses[j]);
    }
  }

  // Add all the Instant Extended prerendered NTPs.
  for (size_t i = 0; i < profiles.size(); ++i) {
    const InstantService* instant_service =
        InstantServiceFactory::GetForProfile(profiles[i]);
    if (instant_service && instant_service->GetNTPContents())
      Add(instant_service->GetNTPContents());
  }

#if defined(ENABLE_FULL_PRINTING)
  // Add all the pages being background printed.
  printing::BackgroundPrintingManager* printing_manager =
      g_browser_process->background_printing_manager();
  for (printing::BackgroundPrintingManager::WebContentsSet::iterator i =
           printing_manager->begin();
       i != printing_manager->end(); ++i) {
    Add(*i);
  }
#endif

  // Then we register for notifications to get new web contents.
  registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
                 content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Add(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
                 content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
                 content::NotificationService::AllBrowserContextsAndSources());
}

void TabContentsResourceProvider::StopUpdating() {
  DCHECK(updating_);
  updating_ = false;

  // Then we unregister for notifications to get new web contents.
  registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
      content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Remove(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
      content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
      content::NotificationService::AllBrowserContextsAndSources());

  // Delete all the resources.
  STLDeleteContainerPairSecondPointers(resources_.begin(), resources_.end());

  resources_.clear();
}

void TabContentsResourceProvider::AddToTaskManager(WebContents* web_contents) {
  TabContentsResource* resource = new TabContentsResource(web_contents);
  resources_[web_contents] = resource;
  task_manager_->AddResource(resource);
}

void TabContentsResourceProvider::Add(WebContents* web_contents) {
  if (!updating_)
    return;

  // The contents that are tracked by this resource provider are those that
  // are tab contents (WebContents serving as a tab in a Browser), Instant
  // pages, prerender pages, and background printed pages.
  if (!chrome::FindBrowserWithWebContents(web_contents) &&
      !IsContentsPrerendering(web_contents) &&
      !chrome::IsPreloadedInstantExtendedNTP(web_contents) &&
      !IsContentsBackgroundPrinted(web_contents) &&
      !DevToolsWindow::IsDevToolsWindow(web_contents->GetRenderViewHost())) {
    return;
  }

  // Don't add dead tabs or tabs that haven't yet connected.
  if (!web_contents->GetRenderProcessHost()->GetHandle() ||
      !web_contents->WillNotifyDisconnection()) {
    return;
  }

  if (resources_.count(web_contents)) {
    // The case may happen that we have added a WebContents as part of the
    // iteration performed during StartUpdating() call but the notification that
    // it has connected was not fired yet. So when the notification happens, we
    // already know about this tab and just ignore it.
    return;
  }
  AddToTaskManager(web_contents);
}

void TabContentsResourceProvider::Remove(WebContents* web_contents) {
  if (!updating_)
    return;
  std::map<WebContents*, TabContentsResource*>::iterator
      iter = resources_.find(web_contents);
  if (iter == resources_.end()) {
    // Since WebContents are destroyed asynchronously (see TabContentsCollector
    // in navigation_controller.cc), we can be notified of a tab being removed
    // that we don't know.  This can happen if the user closes a tab and quickly
    // opens the task manager, before the tab is actually destroyed.
    return;
  }

  // Remove the resource from the Task Manager.
  TabContentsResource* resource = iter->second;
  task_manager_->RemoveResource(resource);
  // And from the provider.
  resources_.erase(iter);
  // Finally, delete the resource.
  delete resource;
}

void TabContentsResourceProvider::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  WebContents* web_contents = content::Source<WebContents>(source).ptr();

  switch (type) {
    case content::NOTIFICATION_WEB_CONTENTS_CONNECTED:
      Add(web_contents);
      break;
    case content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED:
      Remove(web_contents);
      Add(web_contents);
      break;
    case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
      Remove(web_contents);
      break;
    default:
      NOTREACHED() << "Unexpected notification.";
      return;
  }
}

}  // namespace task_manager