// Copyright (c) 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/webui/feedback_ui.h"

#include <algorithm>
#include <vector>

#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/string_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/feedback/feedback_data.h"
#include "chrome/browser/feedback/feedback_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_manager.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/screenshot_source.h"
#include "chrome/browser/ui/window_snapshot/window_snapshot.h"
#include "chrome/common/cancelable_task_tracker.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "net/base/escape.h"
#include "ui/base/resource/resource_bundle.h"

#if defined(OS_CHROMEOS)
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/drive/drive.pb.h"
#include "chrome/browser/chromeos/drive/drive_file_system_interface.h"
#include "chrome/browser/chromeos/drive/drive_file_system_util.h"
#include "chrome/browser/chromeos/drive/drive_system_service.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/system/syslogs_provider.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#endif

using content::BrowserThread;
using content::WebContents;
using content::WebUIMessageHandler;
using ui::WebDialogUI;

namespace {

const char kCategoryTagParameter[] = "categoryTag=";
const char kDescriptionParameter[] = "description=";
const char kSessionIDParameter[] = "session_id=";
const char kTabIndexParameter[] = "tab_index=";
const char kCustomPageUrlParameter[] = "customPageUrl=";

#if defined(OS_CHROMEOS)

const char kTimestampParameter[] = "timestamp=";

const size_t kMaxSavedScreenshots = 2;
size_t kMaxNumScanFiles = 1000;

// Compare two screenshot filepaths, which include the screenshot timestamp
// in the format of screenshot-yyyymmdd-hhmmss.png. Return true if |filepath1|
// is more recent |filepath2|.
bool ScreenshotTimestampComp(const std::string& filepath1,
                             const std::string& filepath2) {
  return filepath1 > filepath2;
}

std::string GetUserEmail() {
  chromeos::UserManager* manager = chromeos::UserManager::Get();
  if (!manager)
    return std::string();
  else
    return manager->GetLoggedInUser()->display_email();
}

bool ScreenshotDriveTimestampComp(const drive::DriveEntryProto& entry1,
                                  const drive::DriveEntryProto& entry2) {
  return entry1.file_info().last_modified() >
      entry2.file_info().last_modified();
}

void ReadDirectoryCallback(size_t max_saved,
                           std::vector<std::string>* saved_screenshots,
                           base::Closure callback,
                           drive::DriveFileError error,
                           bool hide_hosted_documents,
                           scoped_ptr<drive::DriveEntryProtoVector> entries) {
  if (error != drive::DRIVE_FILE_OK) {
    callback.Run();
    return;
  }

  size_t max_scan = std::min(kMaxNumScanFiles, entries->size());
  std::vector<drive::DriveEntryProto> screenshot_entries;
  for (size_t i = 0; i < max_scan; ++i) {
    const drive::DriveEntryProto& entry = (*entries)[i];
    if (StartsWithASCII(entry.base_name(),
                        ScreenshotSource::kScreenshotPrefix, true) &&
        EndsWith(entry.base_name(),
                 ScreenshotSource::kScreenshotSuffix, true)) {
      screenshot_entries.push_back(entry);
    }
  }

  size_t sort_size = std::min(max_saved, screenshot_entries.size());
  std::partial_sort(screenshot_entries.begin(),
                    screenshot_entries.begin() + sort_size,
                    screenshot_entries.end(),
                    ScreenshotDriveTimestampComp);
  for (size_t i = 0; i < sort_size; ++i) {
    const drive::DriveEntryProto& entry = screenshot_entries[i];
    saved_screenshots->push_back(
        std::string(ScreenshotSource::kScreenshotUrlRoot) +
        std::string(ScreenshotSource::kScreenshotSaved) +
        entry.resource_id());
  }
  callback.Run();
}

#else

std::string GetUserEmail() {
  Profile* profile = ProfileManager::GetLastUsedProfile();
  if (!profile)
    return std::string();

  SigninManager* signin = SigninManagerFactory::GetForProfile(profile);
  if (!signin)
    return std::string();

  return signin->GetAuthenticatedUsername();
}

#endif  // OS_CHROMEOS

// Returns the index of the feedback tab if already open, -1 otherwise
int GetIndexOfFeedbackTab(Browser* browser) {
  GURL feedback_url(chrome::kChromeUIFeedbackURL);
  for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
    WebContents* tab = browser->tab_strip_model()->GetWebContentsAt(i);
    if (tab && tab->GetURL().GetWithEmptyPath() == feedback_url)
      return i;
  }

  return -1;
}

}  // namespace

namespace chrome {

void ShowFeedbackPage(Browser* browser,
                      const std::string& description_template,
                      const std::string& category_tag) {
#if defined(OS_CHROMEOS)
  // Grab the timestamp before we do anything else - this is crucial to help
  // diagnose some hardware issues.
  base::Time now = base::Time::Now();
  std::string timestamp = base::DoubleToString(now.ToDoubleT());
#endif

  // First check if we're already open (we cannot depend on ShowSingletonTab
  // for this functionality since we need to make *sure* we never get
  // instantiated again while we are open - with singleton tabs, that can
  // happen).
  int feedback_tab_index = GetIndexOfFeedbackTab(browser);
  if (feedback_tab_index >= 0) {
    // Do not refresh screenshot, do not create a new tab.
    browser->tab_strip_model()->ActivateTabAt(feedback_tab_index, true);
    return;
  }

  std::vector<unsigned char>* last_screenshot_png =
      FeedbackUtil::GetScreenshotPng();
  last_screenshot_png->clear();

  gfx::NativeWindow native_window;
  gfx::Rect snapshot_bounds;

#if defined(OS_CHROMEOS)
  // For ChromeOS, don't use the browser window but the root window
  // instead to grab the screenshot. We want everything on the screen, not
  // just the current browser.
  native_window = ash::Shell::GetPrimaryRootWindow();
  snapshot_bounds = gfx::Rect(native_window->bounds());
#else
  native_window = browser->window()->GetNativeWindow();
  snapshot_bounds = gfx::Rect(browser->window()->GetBounds().size());
#endif
  bool success = chrome::GrabWindowSnapshotForUser(native_window,
                                                   last_screenshot_png,
                                                   snapshot_bounds);
  FeedbackUtil::SetScreenshotSize(success ? snapshot_bounds : gfx::Rect());

  std::string feedback_url = std::string(chrome::kChromeUIFeedbackURL) + "?" +
      kSessionIDParameter + base::IntToString(browser->session_id().id()) +
      "&" + kTabIndexParameter +
      base::IntToString(browser->tab_strip_model()->active_index()) +
      "&" + kDescriptionParameter +
      net::EscapeUrlEncodedData(description_template, false) + "&" +
      kCategoryTagParameter + net::EscapeUrlEncodedData(category_tag, false);

#if defined(OS_CHROMEOS)
  feedback_url = feedback_url + "&" + kTimestampParameter +
                 net::EscapeUrlEncodedData(timestamp, false);
#endif
  chrome::ShowSingletonTab(browser, GURL(feedback_url));
}

}  // namespace chrome

// The handler for Javascript messages related to the "bug report" dialog
class FeedbackHandler : public WebUIMessageHandler,
                        public base::SupportsWeakPtr<FeedbackHandler> {
 public:
  explicit FeedbackHandler(content::WebContents* tab);
  virtual ~FeedbackHandler();

  // Init work after Attach.  Returns true on success.
  bool Init();

  // WebUIMessageHandler implementation.
  virtual void RegisterMessages() OVERRIDE;

 private:
  void HandleGetDialogDefaults(const ListValue* args);
  void HandleRefreshCurrentScreenshot(const ListValue* args);
#if defined(OS_CHROMEOS)
  void HandleRefreshSavedScreenshots(const ListValue* args);
  void RefreshSavedScreenshotsCallback(
      std::vector<std::string>* saved_screenshots);
  void GetMostRecentScreenshotsDrive(
      const base::FilePath& filepath,
      std::vector<std::string>* saved_screenshots,
      size_t max_saved, base::Closure callback);
#endif
  void HandleSendReport(const ListValue* args);
  void HandleCancel(const ListValue* args);
  void HandleOpenSystemTab(const ListValue* args);

  void SetupScreenshotsSource();
  void ClobberScreenshotsSource();

  void CancelFeedbackCollection();
  void CloseFeedbackTab();

  WebContents* tab_;
  ScreenshotSource* screenshot_source_;

  FeedbackData* feedback_data_;
  std::string target_tab_url_;
#if defined(OS_CHROMEOS)
  // Variables to track SyslogsProvider::RequestSyslogs.
  CancelableTaskTracker::TaskId syslogs_task_id_;
  CancelableTaskTracker syslogs_tracker_;

  // Timestamp of when the feedback request was initiated.
  std::string timestamp_;
#endif

  DISALLOW_COPY_AND_ASSIGN(FeedbackHandler);
};

content::WebUIDataSource* CreateFeedbackUIHTMLSource(bool successful_init) {
  content::WebUIDataSource* source =
      content::WebUIDataSource::Create(chrome::kChromeUIFeedbackHost);
  source->SetUseJsonJSFormatV2();

  source->AddLocalizedString("title", IDS_FEEDBACK_TITLE);
  source->AddLocalizedString("page-title", IDS_FEEDBACK_REPORT_PAGE_TITLE);
  source->AddLocalizedString("page-url", IDS_FEEDBACK_REPORT_URL_LABEL);
  source->AddLocalizedString("description", IDS_FEEDBACK_DESCRIPTION_LABEL);
  source->AddLocalizedString("current-screenshot",
                             IDS_FEEDBACK_SCREENSHOT_LABEL);
  source->AddLocalizedString("saved-screenshot",
                             IDS_FEEDBACK_SAVED_SCREENSHOT_LABEL);
  source->AddLocalizedString("user-email", IDS_FEEDBACK_USER_EMAIL_LABEL);

#if defined(OS_CHROMEOS)
  source->AddLocalizedString("sysinfo",
                             IDS_FEEDBACK_INCLUDE_SYSTEM_INFORMATION_CHKBOX);
  source->AddLocalizedString("currentscreenshots",
                             IDS_FEEDBACK_CURRENT_SCREENSHOTS);
  source->AddLocalizedString("savedscreenshots",
                             IDS_FEEDBACK_SAVED_SCREENSHOTS);
  source->AddLocalizedString("choose-different-screenshot",
                             IDS_FEEDBACK_CHOOSE_DIFFERENT_SCREENSHOT);
  source->AddLocalizedString("choose-original-screenshot",
                             IDS_FEEDBACK_CHOOSE_ORIGINAL_SCREENSHOT);
  source->AddLocalizedString("attach-file-custom-label",
                             IDS_FEEDBACK_ATTACH_FILE_LABEL);
  source->AddLocalizedString("attach-file-note",
                             IDS_FEEDBACK_ATTACH_FILE_NOTE);
  source->AddLocalizedString("attach-file-to-big",
                             IDS_FEEDBACK_ATTACH_FILE_TO_BIG);
  source->AddLocalizedString("reading-file", IDS_FEEDBACK_READING_FILE);
#else
  source->AddLocalizedString("currentscreenshots",
                             IDS_FEEDBACK_INCLUDE_NEW_SCREEN_IMAGE);
#endif
  source->AddLocalizedString("noscreenshot",
                             IDS_FEEDBACK_INCLUDE_NO_SCREENSHOT);

  source->AddLocalizedString("send-report", IDS_FEEDBACK_SEND_REPORT);
  source->AddLocalizedString("cancel", IDS_CANCEL);

  source->AddLocalizedString("no-description", IDS_FEEDBACK_NO_DESCRIPTION);
  source->AddLocalizedString("no-saved-screenshots",
                             IDS_FEEDBACK_NO_SAVED_SCREENSHOTS_HELP);
  source->AddLocalizedString("privacy-note", IDS_FEEDBACK_PRIVACY_NOTE);

  source->SetJsonPath("strings.js");
  source->AddResourcePath("feedback.js", IDR_FEEDBACK_JS);
  source->SetDefaultResource(
      successful_init ? IDR_FEEDBACK_HTML : IDR_FEEDBACK_HTML_INVALID);

  return source;
}

////////////////////////////////////////////////////////////////////////////////
//
// FeedbackHandler
//
////////////////////////////////////////////////////////////////////////////////
FeedbackHandler::FeedbackHandler(WebContents* tab)
    : tab_(tab),
      screenshot_source_(NULL),
      feedback_data_(NULL)
#if defined(OS_CHROMEOS)
      , syslogs_task_id_(CancelableTaskTracker::kBadTaskId)
#endif
{}

FeedbackHandler::~FeedbackHandler() {
  // Just in case we didn't send off feedback_data_ to SendReport
  if (feedback_data_) {
    // If we're deleting the report object, cancel feedback collection first
    CancelFeedbackCollection();
    delete feedback_data_;
  }
  // Make sure we don't leave any screenshot data around.
  FeedbackUtil::ClearScreenshotPng();
}

void FeedbackHandler::ClobberScreenshotsSource() {
  // Re-create our screenshots data source (this clobbers the last source)
  // setting the screenshot to NULL, effectively disabling the source
  // TODO(rkc): Once there is a method to 'remove' a source, change this code
  Profile* profile = Profile::FromBrowserContext(tab_->GetBrowserContext());
  content::URLDataSource::Add(profile, new ScreenshotSource(NULL, profile));

  FeedbackUtil::ClearScreenshotPng();
}

void FeedbackHandler::SetupScreenshotsSource() {
  Profile* profile = Profile::FromBrowserContext(tab_->GetBrowserContext());
  screenshot_source_ =
      new ScreenshotSource(FeedbackUtil::GetScreenshotPng(), profile);
  // Add the source to the data manager.
  content::URLDataSource::Add(profile, screenshot_source_);
}

bool FeedbackHandler::Init() {
  std::string page_url;
  if (tab_->GetController().GetActiveEntry()) {
     page_url = tab_->GetController().GetActiveEntry()->GetURL().spec();
  }

  url_parse::Parsed parts;
  ParseStandardURL(page_url.c_str(), page_url.length(), &parts);

  size_t params_start = page_url.find("?");
  std::string query = page_url.substr(params_start + 1);

  int session_id = -1;
  int index = -1;

  std::vector<std::string> params;
  std::string custom_page_url;
  if (Tokenize(query, std::string("&"), &params)) {
    for (std::vector<std::string>::iterator it = params.begin();
         it != params.end(); ++it) {
      std::string query_str = *it;
      if (StartsWithASCII(query_str, std::string(kSessionIDParameter), true)) {
        ReplaceFirstSubstringAfterOffset(
            &query_str, 0, kSessionIDParameter, "");
        if (!base::StringToInt(query_str, &session_id))
          return false;
        continue;
      }
      if (StartsWithASCII(*it, std::string(kTabIndexParameter), true)) {
        ReplaceFirstSubstringAfterOffset(
            &query_str, 0, kTabIndexParameter, "");
        if (!base::StringToInt(query_str, &index))
          return false;
        continue;
      }
      if (StartsWithASCII(*it, std::string(kCustomPageUrlParameter), true)) {
        ReplaceFirstSubstringAfterOffset(
            &query_str, 0, kCustomPageUrlParameter, "");
        custom_page_url = query_str;
        continue;
      }
#if defined(OS_CHROMEOS)
      if (StartsWithASCII(*it, std::string(kTimestampParameter), true)) {
        ReplaceFirstSubstringAfterOffset(
            &query_str, 0, kTimestampParameter, "");
        timestamp_ = query_str;
        continue;
      }
#endif
    }
  }

  // If we don't have a page url specified, get it from the tab index.
  if (custom_page_url.empty()) {
    if (session_id == -1)
      return false;

    Browser* browser = chrome::FindBrowserWithID(session_id);
    // Sanity checks.
    if (!browser || index >= browser->tab_strip_model()->count())
      return false;

    if (index >= 0) {
      WebContents* target_tab =
          browser->tab_strip_model()->GetWebContentsAt(index);
      if (target_tab)
        target_tab_url_ = target_tab->GetURL().spec();
    }

    // Note: We don't need to setup a screenshot source if we're using a
    // custom page URL since we were invoked from JS/an extension, in which
    // case we don't actually have a screenshot anyway.
    SetupScreenshotsSource();
  } else {
    target_tab_url_ = custom_page_url;
  }

  return true;
}

void FeedbackHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback("getDialogDefaults",
      base::Bind(&FeedbackHandler::HandleGetDialogDefaults,
                 base::Unretained(this)));
  web_ui()->RegisterMessageCallback("refreshCurrentScreenshot",
      base::Bind(&FeedbackHandler::HandleRefreshCurrentScreenshot,
                 base::Unretained(this)));
#if defined(OS_CHROMEOS)
  web_ui()->RegisterMessageCallback("refreshSavedScreenshots",
      base::Bind(&FeedbackHandler::HandleRefreshSavedScreenshots,
                 base::Unretained(this)));
#endif
  web_ui()->RegisterMessageCallback("sendReport",
      base::Bind(&FeedbackHandler::HandleSendReport,
                 base::Unretained(this)));
  web_ui()->RegisterMessageCallback("cancel",
      base::Bind(&FeedbackHandler::HandleCancel,
                 base::Unretained(this)));
  web_ui()->RegisterMessageCallback("openSystemTab",
      base::Bind(&FeedbackHandler::HandleOpenSystemTab,
                 base::Unretained(this)));
}

void FeedbackHandler::HandleGetDialogDefaults(const ListValue*) {
  // Will delete itself when feedback_data_->SendReport() is called.
  feedback_data_ = new FeedbackData();

  // Send back values which the dialog js needs initially.
  DictionaryValue dialog_defaults;

  // Current url.
  dialog_defaults.SetString("currentUrl", target_tab_url_);

  // Are screenshots disabled?
  dialog_defaults.SetBoolean(
      "disableScreenshots",
      g_browser_process->local_state()->GetBoolean(prefs::kDisableScreenshots));

  // User e-mail
  std::string user_email = GetUserEmail();
  dialog_defaults.SetString("userEmail", user_email);

  // Set email checkbox to checked by default for cros, unchecked for Chrome.
  dialog_defaults.SetBoolean(
      "emailCheckboxDefault",
#if defined(OS_CHROMEOS)
      true);
#else
      false);
#endif


#if defined(OS_CHROMEOS)
  // Trigger the request for system information here.
  chromeos::system::SyslogsProvider* provider =
      chromeos::system::SyslogsProvider::GetInstance();
  if (provider) {
    syslogs_task_id_ = provider->RequestSyslogs(
        true,  // don't compress.
        chromeos::system::SyslogsProvider::SYSLOGS_FEEDBACK,
        base::Bind(&FeedbackData::SyslogsComplete,
                   base::Unretained(feedback_data_)),
        &syslogs_tracker_);
  }

  // On ChromeOS if the user's email is blank, it means we don't
  // have a logged in user, hence don't use saved screenshots.
  dialog_defaults.SetBoolean("useSaved", !user_email.empty());
#endif

  web_ui()->CallJavascriptFunction("setupDialogDefaults", dialog_defaults);
}

void FeedbackHandler::HandleRefreshCurrentScreenshot(const ListValue*) {
  std::string current_screenshot(
          std::string(ScreenshotSource::kScreenshotUrlRoot) +
          std::string(ScreenshotSource::kScreenshotCurrent));
  StringValue screenshot(current_screenshot);
  web_ui()->CallJavascriptFunction("setupCurrentScreenshot", screenshot);
}

#if defined(OS_CHROMEOS)
void FeedbackHandler::HandleRefreshSavedScreenshots(const ListValue*) {
  std::vector<std::string>* saved_screenshots = new std::vector<std::string>;
  base::FilePath filepath = DownloadPrefs::FromBrowserContext(
      tab_->GetBrowserContext())->DownloadPath();
  base::Closure refresh_callback = base::Bind(
      &FeedbackHandler::RefreshSavedScreenshotsCallback,
      AsWeakPtr(), base::Owned(saved_screenshots));
  if (drive::util::IsUnderDriveMountPoint(filepath)) {
    GetMostRecentScreenshotsDrive(
        filepath, saved_screenshots, kMaxSavedScreenshots, refresh_callback);
  } else {
    BrowserThread::PostTaskAndReply(
        BrowserThread::FILE, FROM_HERE,
        base::Bind(&FeedbackUI::GetMostRecentScreenshots, filepath,
                   base::Unretained(saved_screenshots), kMaxSavedScreenshots),
        refresh_callback);
  }
}

void FeedbackHandler::RefreshSavedScreenshotsCallback(
    std::vector<std::string>* saved_screenshots) {
  ListValue screenshots_list;
  for (size_t i = 0; i < saved_screenshots->size(); ++i)
    screenshots_list.Append(new StringValue((*saved_screenshots)[i]));
  web_ui()->CallJavascriptFunction("setupSavedScreenshots", screenshots_list);
}

void FeedbackHandler::GetMostRecentScreenshotsDrive(
    const base::FilePath& filepath, std::vector<std::string>* saved_screenshots,
    size_t max_saved, base::Closure callback) {
  drive::DriveFileSystemInterface* file_system =
      drive::DriveSystemServiceFactory::GetForProfile(
          Profile::FromWebUI(web_ui()))->file_system();
  file_system->ReadDirectoryByPath(
      drive::util::ExtractDrivePath(filepath),
      base::Bind(&ReadDirectoryCallback, max_saved, saved_screenshots,
                 callback));
}
#endif


void FeedbackHandler::HandleSendReport(const ListValue* list_value) {
  if (!feedback_data_) {
    LOG(ERROR) << "Bug report hasn't been intialized yet.";
    return;
  }

  ListValue::const_iterator i = list_value->begin();
  std::string page_url;
  (*i++)->GetAsString(&page_url);
  std::string category_tag;
  (*i++)->GetAsString(&category_tag);
  std::string description;
  (*i++)->GetAsString(&description);
  std::string user_email;
  (*i++)->GetAsString(&user_email);
  std::string screenshot_path;
  (*i++)->GetAsString(&screenshot_path);
  screenshot_path.erase(0, strlen(ScreenshotSource::kScreenshotUrlRoot));

  // Get the image to send in the report.
  ScreenshotDataPtr image_ptr;
  if (!screenshot_path.empty() &&  screenshot_source_)
    image_ptr = screenshot_source_->GetCachedScreenshot(screenshot_path);

#if defined(OS_CHROMEOS)
  std::string sys_info_checkbox;
  (*i++)->GetAsString(&sys_info_checkbox);
  bool send_sys_info = (sys_info_checkbox == "true");

  // If we aren't sending the sys_info, cancel the gathering of the syslogs.
  if (!send_sys_info)
    CancelFeedbackCollection();

  std::string attached_filename;
  std::string* attached_filedata = NULL;
  // If we have an attached file, we'll still have more data in the list.
  if (i != list_value->end()) {
    (*i++)->GetAsString(&attached_filename);
    if (base::FilePath::IsSeparator(attached_filename[0])) {
      // We have an attached filepath, not filename, hence we need read this
      // this file in chrome. We won't have any file data, skip over it.
      i++;
    } else {
      std::string encoded_filedata;
      attached_filedata = new std::string;
      (*i++)->GetAsString(&encoded_filedata);
      if (!base::Base64Decode(
          base::StringPiece(encoded_filedata), attached_filedata)) {
        LOG(ERROR) << "Unable to attach file: " << attached_filename;
        // Clear the filename so feedback_util doesn't try to attach the file.
        attached_filename = "";
      }
    }
  }
#endif

  // Update the data in feedback_data_ so it can be sent
  feedback_data_->UpdateData(Profile::FromWebUI(web_ui())
                             , std::string()
                             , page_url
                             , description
                             , user_email
                             , image_ptr
#if defined(OS_CHROMEOS)
                             , send_sys_info
                             , false  // sent_report
                             , timestamp_
                             , attached_filename
                             , attached_filedata
#endif
                             );

#if defined(OS_CHROMEOS)
  // If we don't require sys_info, or we have it, or we never requested it
  // (because libcros failed to load), then send the report now.
  // Otherwise, the report will get sent when we receive sys_info.
  if (!send_sys_info || feedback_data_->sys_info() != NULL ||
      syslogs_task_id_ == CancelableTaskTracker::kBadTaskId) {
    feedback_data_->SendReport();
  }
#else
  feedback_data_->SendReport();
#endif
  // Lose the pointer to the FeedbackData object; the object will delete itself
  // from SendReport, whether we called it, or will be called by the log
  // completion routine.
  feedback_data_ = NULL;

  // Whether we sent the report, or if it will be sent by the Syslogs complete
  // function, close our feedback tab anyway, we have no more use for it.
  CloseFeedbackTab();
}

void FeedbackHandler::HandleCancel(const ListValue*) {
  CloseFeedbackTab();
}

void FeedbackHandler::HandleOpenSystemTab(const ListValue* args) {
#if defined(OS_CHROMEOS)
  web_ui()->GetWebContents()->GetDelegate()->OpenURLFromTab(
      web_ui()->GetWebContents(),
      content::OpenURLParams(GURL(chrome::kChromeUISystemInfoURL),
                             content::Referrer(),
                             NEW_FOREGROUND_TAB,
                             content::PAGE_TRANSITION_LINK,
                             false));
#endif
}

void FeedbackHandler::CancelFeedbackCollection() {
#if defined(OS_CHROMEOS)
  // Canceling a finished task ID or kBadTaskId is a noop.
  syslogs_tracker_.TryCancel(syslogs_task_id_);
#endif
}

void FeedbackHandler::CloseFeedbackTab() {
  ClobberScreenshotsSource();
  tab_->GetDelegate()->CloseContents(tab_);
}

////////////////////////////////////////////////////////////////////////////////
//
// FeedbackUI
//
////////////////////////////////////////////////////////////////////////////////
FeedbackUI::FeedbackUI(content::WebUI* web_ui)
    : WebDialogUI(web_ui) {
  FeedbackHandler* handler = new FeedbackHandler(web_ui->GetWebContents());
  web_ui->AddMessageHandler(handler);

  // The handler's init will determine whether we show the error html page.
  content::WebUIDataSource* html_source =
      CreateFeedbackUIHTMLSource(handler->Init());

  // Set up the chrome://feedback/ source.
  Profile* profile = Profile::FromWebUI(web_ui);
  content::WebUIDataSource::Add(profile, html_source);
}

#if defined(OS_CHROMEOS)
// static
void FeedbackUI::GetMostRecentScreenshots(
    const base::FilePath& filepath,
    std::vector<std::string>* saved_screenshots,
    size_t max_saved) {
  std::string pattern =
      std::string(ScreenshotSource::kScreenshotPrefix) + "*" +
                  ScreenshotSource::kScreenshotSuffix;
  file_util::FileEnumerator screenshots(filepath, false,
                                        file_util::FileEnumerator::FILES,
                                        pattern);
  base::FilePath screenshot = screenshots.Next();

  std::vector<std::string> screenshot_filepaths;
  while (!screenshot.empty()) {
    screenshot_filepaths.push_back(screenshot.BaseName().value());
    screenshot = screenshots.Next();
  }

  size_t sort_size = std::min(max_saved, screenshot_filepaths.size());
  std::partial_sort(screenshot_filepaths.begin(),
                    screenshot_filepaths.begin() + sort_size,
                    screenshot_filepaths.end(),
                    ScreenshotTimestampComp);
  for (size_t i = 0; i < sort_size; ++i)
    saved_screenshots->push_back(
        std::string(ScreenshotSource::kScreenshotUrlRoot) +
        std::string(ScreenshotSource::kScreenshotSaved) +
        screenshot_filepaths[i]);
}
#endif