// Copyright (c) 2010 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/views/bug_report_view.h" #include "app/combobox_model.h" #include "app/l10n_util.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/file_version_info.h" #include "base/path_service.h" #include "base/utf_string_conversions.h" #include "base/waitable_event.h" #include "chrome/app/chrome_version_info.h" #include "chrome/browser/bug_report_util.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/safe_browsing/safe_browsing_util.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/net/url_fetcher.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "net/base/escape.h" #include "unicode/locid.h" #include "views/controls/button/checkbox.h" #include "views/controls/label.h" #include "views/grid_layout.h" #include "views/standard_layout.h" #include "views/widget/widget.h" #include "views/window/client_view.h" #include "views/window/window.h" #if defined(OS_LINUX) #include "app/x11_util.h" #else #include "app/win_util.h" #endif #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/login/user_manager.h" #endif using views::ColumnSet; using views::GridLayout; // Report a bug data version. static const int kBugReportVersion = 1; static const int kScreenImageRadioGroup = 2; static const char kScreenshotsRelativePath[] = "/Screenshots"; static const char kScreenshotPattern[] = "*.png"; // Number of lines description field can display at one time. static const int kDescriptionLines = 5; class BugReportComboBoxModel : public ComboboxModel { public: BugReportComboBoxModel() {} // ComboboxModel interface. virtual int GetItemCount() { return BugReportUtil::OTHER_PROBLEM + 1; } virtual std::wstring GetItemAt(int index) { return GetItemAtIndex(index); } static std::wstring GetItemAtIndex(int index) { #if defined(OS_CHROMEOS) switch (index) { case BugReportUtil::CONNECTIVITY_ISSUE: return l10n_util::GetString(IDS_BUGREPORT_CONNECTIVITY_ISSUE); case BugReportUtil::SYNC_ISSUE: return l10n_util::GetString(IDS_BUGREPORT_SYNC_ISSUE); case BugReportUtil::CRASH_ISSUE: return l10n_util::GetString(IDS_BUGREPORT_CRASH_ISSUE); case BugReportUtil::PAGE_FORMATTING: return l10n_util::GetString(IDS_BUGREPORT_PAGE_FORMATTING); case BugReportUtil::EXTENSION_ISSUE: return l10n_util::GetString(IDS_BUGREPORT_EXTENSION_ISSUE); case BugReportUtil::SUSPEND_ISSUE: return l10n_util::GetString(IDS_BUGREPORT_SUSPEND_ISSUE); case BugReportUtil::PHISHING_PAGE: return l10n_util::GetString(IDS_BUGREPORT_PHISHING_PAGE); case BugReportUtil::OTHER_PROBLEM: return l10n_util::GetString(IDS_BUGREPORT_OTHER_PROBLEM); default: NOTREACHED(); return std::wstring(); } #else switch (index) { case BugReportUtil::PAGE_WONT_LOAD: return l10n_util::GetString(IDS_BUGREPORT_PAGE_WONT_LOAD); case BugReportUtil::PAGE_LOOKS_ODD: return l10n_util::GetString(IDS_BUGREPORT_PAGE_LOOKS_ODD); case BugReportUtil::PHISHING_PAGE: return l10n_util::GetString(IDS_BUGREPORT_PHISHING_PAGE); case BugReportUtil::CANT_SIGN_IN: return l10n_util::GetString(IDS_BUGREPORT_CANT_SIGN_IN); case BugReportUtil::CHROME_MISBEHAVES: return l10n_util::GetString(IDS_BUGREPORT_CHROME_MISBEHAVES); case BugReportUtil::SOMETHING_MISSING: return l10n_util::GetString(IDS_BUGREPORT_SOMETHING_MISSING); case BugReportUtil::BROWSER_CRASH: return l10n_util::GetString(IDS_BUGREPORT_BROWSER_CRASH); case BugReportUtil::OTHER_PROBLEM: return l10n_util::GetString(IDS_BUGREPORT_OTHER_PROBLEM); default: NOTREACHED(); return std::wstring(); } #endif } private: DISALLOW_COPY_AND_ASSIGN(BugReportComboBoxModel); }; namespace { #if defined(OS_CHROMEOS) class LastScreenshotTask : public Task { public: LastScreenshotTask(std::string* image_str, base::WaitableEvent* task_waitable) : image_str_(image_str), task_waitable_(task_waitable) { } private: void Run() { FilePath fileshelf_path; // TODO(rkc): Change this to use FilePath.Append() once the cros // issue with with it is fixed if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &fileshelf_path)) { *image_str_ = ""; task_waitable_->Signal(); } FilePath screenshots_path(fileshelf_path.value() + std::string(kScreenshotsRelativePath)); file_util::FileEnumerator screenshots(screenshots_path, false, file_util::FileEnumerator::FILES, std::string(kScreenshotPattern)); FilePath screenshot = screenshots.Next(); FilePath latest(""); time_t last_mtime = 0; while (!screenshot.empty()) { file_util::FileEnumerator::FindInfo info; screenshots.GetFindInfo(&info); if (info.stat.st_mtime > last_mtime) { last_mtime = info.stat.st_mtime; latest = screenshot; } screenshot = screenshots.Next(); } if (!file_util::ReadFileToString(latest, image_str_)) *image_str_ = ""; task_waitable_->Signal(); } private: std::string* image_str_; base::WaitableEvent* task_waitable_; }; #endif bool GetLastScreenshot(std::string* image_str) { #if defined(OS_CHROMEOS) base::WaitableEvent task_waitable(true, false); ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, new LastScreenshotTask(image_str, &task_waitable)); task_waitable.Wait(); if (*image_str == "") return false; else return true; #else return false; #endif } } // namespace namespace browser { // Global "display this dialog" function declared in browser_dialogs.h. void ShowBugReportView(views::Window* parent, Profile* profile, TabContents* tab) { BugReportView* view = new BugReportView(profile, tab); // Get the size of the parent window to capture screenshot dimensions gfx::Rect screen_size = parent->GetBounds(); view->set_screen_size(screen_size); // Grab an exact snapshot of the window that the user is seeing (i.e. as // rendered--do not re-render, and include windowed plugins). std::vector *screenshot_png = new std::vector; #if defined(OS_LINUX) x11_util::GrabWindowSnapshot(parent->GetNativeWindow(), screenshot_png); #else win_util::GrabWindowSnapshot(parent->GetNativeWindow(), screenshot_png); #endif // The BugReportView takes ownership of the image data, and will dispose of // it in its destructor view->set_captured_image(screenshot_png); #if defined(OS_CHROMEOS) // Get last screenshot taken std::string image_str; bool have_last_image = false; if (GetLastScreenshot(&image_str)) { // reuse screenshot_png; previous pointer now owned by BugReportView screenshot_png = new std::vector(image_str.begin(), image_str.end()); have_last_image = true; } else { // else set it to be an empty vector screenshot_png = new std::vector; } view->set_last_image(screenshot_png); // Create and show the dialog views::Window::CreateChromeWindow(parent->GetNativeWindow(), gfx::Rect(), view)->Show(); if (!have_last_image) view->DisableLastImageRadio(); #endif } } // namespace browser // BugReportView - create and submit a bug report from the user. // This is separate from crash reporting, which is handled by Breakpad. BugReportView::BugReportView(Profile* profile, TabContents* tab) : include_page_source_checkbox_(NULL), include_page_image_checkbox_(NULL), profile_(profile), tab_(tab), problem_type_(0) { DCHECK(profile); SetupControl(); // We want to use the URL of the current committed entry (the current URL may // actually be the pending one). if (tab->controller().GetActiveEntry()) { page_url_text_->SetText(UTF8ToUTF16( tab->controller().GetActiveEntry()->url().spec())); } #if defined(OS_CHROMEOS) // Get and set the gaia e-mail chromeos::UserManager* manager = chromeos::UserManager::Get(); if (!manager) { user_email_text_->SetText(UTF8ToUTF16(std::string(""))); } else { const std::string& email = manager->logged_in_user().email(); user_email_text_->SetText(UTF8ToUTF16(email)); if (!email.empty()) user_email_text_->SetEnabled(false); } #endif // Retrieve the application version info. scoped_ptr version_info( chrome_app::GetChromeVersionInfo()); if (version_info.get()) { version_ = version_info->product_name() + L" - " + version_info->file_version() + L" (" + version_info->last_change() + L")"; } FilePath tmpfilename; #if defined(OS_CHROMEOS) chromeos::SyslogsLibrary* syslogs_lib = chromeos::CrosLibrary::Get()->GetSyslogsLibrary(); if (syslogs_lib) { sys_info_.reset(syslogs_lib->GetSyslogs(&tmpfilename)); } #endif } BugReportView::~BugReportView() { } void BugReportView::SetupControl() { bug_type_model_.reset(new BugReportComboBoxModel); // Adds all controls. bug_type_label_ = new views::Label( l10n_util::GetString(IDS_BUGREPORT_BUG_TYPE)); bug_type_combo_ = new views::Combobox(bug_type_model_.get()); bug_type_combo_->set_listener(this); bug_type_combo_->SetAccessibleName(bug_type_label_->GetText()); page_title_label_ = new views::Label( l10n_util::GetString(IDS_BUGREPORT_REPORT_PAGE_TITLE)); page_title_text_ = new views::Label(UTF16ToWideHack(tab_->GetTitle())); page_url_label_ = new views::Label( l10n_util::GetString(IDS_BUGREPORT_REPORT_URL_LABEL)); // page_url_text_'s text (if any) is filled in after dialog creation. page_url_text_ = new views::Textfield; page_url_text_->SetController(this); page_url_text_->SetAccessibleName(page_url_label_->GetText()); #if defined(OS_CHROMEOS) user_email_label_ = new views::Label( l10n_util::GetString(IDS_BUGREPORT_USER_EMAIL_LABEL)); // user_email_text_'s text (if any) is filled in after dialog creation. user_email_text_ = new views::Textfield; user_email_text_->SetController(this); user_email_text_->SetAccessibleName(user_email_label_->GetText()); #endif description_label_ = new views::Label( l10n_util::GetString(IDS_BUGREPORT_DESCRIPTION_LABEL)); description_text_ = new views::Textfield(views::Textfield::STYLE_MULTILINE); description_text_->SetHeightInLines(kDescriptionLines); description_text_->SetAccessibleName(description_label_->GetText()); include_page_source_checkbox_ = new views::Checkbox( l10n_util::GetString(IDS_BUGREPORT_INCLUDE_PAGE_SOURCE_CHKBOX)); include_page_source_checkbox_->SetChecked(true); #if defined(OS_CHROMEOS) include_last_screen_image_radio_ = new views::RadioButton( l10n_util::GetString(IDS_BUGREPORT_INCLUDE_LAST_SCREEN_IMAGE), kScreenImageRadioGroup); include_new_screen_image_radio_ = new views::RadioButton( l10n_util::GetString(IDS_BUGREPORT_INCLUDE_NEW_SCREEN_IMAGE), kScreenImageRadioGroup); include_no_screen_image_radio_ = new views::RadioButton( l10n_util::GetString(IDS_BUGREPORT_INCLUDE_NO_SCREEN_IMAGE), kScreenImageRadioGroup); system_information_url_control_ = new views::Link( l10n_util::GetString(IDS_BUGREPORT_SYSTEM_INFORMATION_URL_TEXT)); system_information_url_control_->SetController(this); include_new_screen_image_radio_->SetChecked(true); #endif include_page_image_checkbox_ = new views::Checkbox( l10n_util::GetString(IDS_BUGREPORT_INCLUDE_PAGE_IMAGE_CHKBOX)); include_page_image_checkbox_->SetChecked(true); // Arranges controls by using GridLayout. const int column_set_id = 0; GridLayout* layout = CreatePanelGridLayout(this); SetLayoutManager(layout); ColumnSet* column_set = layout->AddColumnSet(column_set_id); column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, GridLayout::USE_PREF, 0, 0); column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing * 2); column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, GridLayout::USE_PREF, 0, 0); // Page Title and text. layout->StartRow(0, column_set_id); layout->AddView(page_title_label_); layout->AddView(page_title_text_, 1, 1, GridLayout::LEADING, GridLayout::FILL); layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); // Bug type and combo box. layout->StartRow(0, column_set_id); layout->AddView(bug_type_label_, 1, 1, GridLayout::LEADING, GridLayout::FILL); layout->AddView(bug_type_combo_); layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); // Page URL and text field. layout->StartRow(0, column_set_id); layout->AddView(page_url_label_); layout->AddView(page_url_text_); layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); // Description label and text field. layout->StartRow(0, column_set_id); layout->AddView(description_label_, 1, 1, GridLayout::LEADING, GridLayout::LEADING); layout->AddView(description_text_, 1, 1, GridLayout::FILL, GridLayout::LEADING); #if defined(OS_CHROMEOS) layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); // Page URL and text field. layout->StartRow(0, column_set_id); layout->AddView(user_email_label_); layout->AddView(user_email_text_); #endif layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); // Checkboxes. // The include page source checkbox is hidden until we can make it work. // layout->StartRow(0, column_set_id); // layout->SkipColumns(1); // layout->AddView(include_page_source_checkbox_); // layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); layout->StartRow(0, column_set_id); layout->SkipColumns(1); #if defined(OS_CHROMEOS) // Radio boxes to select last screen shot or, // new screenshot layout->AddView(include_new_screen_image_radio_); layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); // last screenshot taken layout->StartRow(0, column_set_id); layout->SkipColumns(1); layout->AddView(include_last_screen_image_radio_); layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); // no screenshot layout->StartRow(0, column_set_id); layout->SkipColumns(1); layout->AddView(include_no_screen_image_radio_); layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); layout->StartRow(0, column_set_id); layout->SkipColumns(1); layout->AddView(system_information_url_control_, 1, 1, GridLayout::LEADING, GridLayout::CENTER); #else if (include_page_image_checkbox_) { layout->StartRow(0, column_set_id); layout->SkipColumns(1); layout->AddView(include_page_image_checkbox_); } #endif layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); } gfx::Size BugReportView::GetPreferredSize() { gfx::Size size = views::Window::GetLocalizedContentsSize( IDS_BUGREPORT_DIALOG_WIDTH_CHARS, #if defined(OS_CHROMEOS) IDS_CHROMEOS_BUGREPORT_DIALOG_HEIGHT_LINES); #else IDS_BUGREPORT_DIALOG_HEIGHT_LINES); #endif return size; } void BugReportView::UpdateReportingControls(bool is_phishing_report) { // page source, screen/page images, system information // are not needed if it's a phishing report include_page_source_checkbox_->SetEnabled(!is_phishing_report); include_page_source_checkbox_->SetChecked(!is_phishing_report); #if defined(OS_CHROMEOS) include_new_screen_image_radio_->SetEnabled(!is_phishing_report); if (!last_image_->empty()) include_last_screen_image_radio_->SetEnabled(!is_phishing_report); include_no_screen_image_radio_->SetEnabled(!is_phishing_report); #else if (include_page_image_checkbox_) { include_page_image_checkbox_->SetEnabled(!is_phishing_report); include_page_image_checkbox_->SetChecked(!is_phishing_report); } #endif } void BugReportView::ItemChanged(views::Combobox* combobox, int prev_index, int new_index) { if (new_index == prev_index) return; problem_type_ = new_index; bool is_phishing_report = new_index == BugReportUtil::PHISHING_PAGE; description_text_->SetEnabled(!is_phishing_report); description_text_->SetReadOnly(is_phishing_report); if (is_phishing_report) { old_report_text_ = UTF16ToWide(description_text_->text()); description_text_->SetText(string16()); } else if (!old_report_text_.empty()) { description_text_->SetText(WideToUTF16Hack(old_report_text_)); old_report_text_.clear(); } UpdateReportingControls(is_phishing_report); GetDialogClientView()->UpdateDialogButtons(); } void BugReportView::ContentsChanged(views::Textfield* sender, const string16& new_contents) { } bool BugReportView::HandleKeystroke(views::Textfield* sender, const views::Textfield::Keystroke& key) { return false; } std::wstring BugReportView::GetDialogButtonLabel( MessageBoxFlags::DialogButton button) const { if (button == MessageBoxFlags::DIALOGBUTTON_OK) { if (problem_type_ == BugReportUtil::PHISHING_PAGE) return l10n_util::GetString(IDS_BUGREPORT_SEND_PHISHING_REPORT); else return l10n_util::GetString(IDS_BUGREPORT_SEND_REPORT); } else { return std::wstring(); } } int BugReportView::GetDefaultDialogButton() const { return MessageBoxFlags::DIALOGBUTTON_NONE; } bool BugReportView::CanResize() const { return false; } bool BugReportView::CanMaximize() const { return false; } bool BugReportView::IsAlwaysOnTop() const { return false; } bool BugReportView::HasAlwaysOnTopMenu() const { return false; } bool BugReportView::IsModal() const { return true; } std::wstring BugReportView::GetWindowTitle() const { return l10n_util::GetString(IDS_BUGREPORT_TITLE); } bool BugReportView::Accept() { if (IsDialogButtonEnabled(MessageBoxFlags::DIALOGBUTTON_OK)) { if (problem_type_ == BugReportUtil::PHISHING_PAGE) { BugReportUtil::ReportPhishing(tab_, UTF16ToUTF8(page_url_text_->text())); } else { char* image_data = NULL; size_t image_data_size = 0; #if defined(OS_CHROMEOS) if (include_new_screen_image_radio_->checked() && !captured_image_->empty()) { image_data = reinterpret_cast(&captured_image_->front()); image_data_size = captured_image_->size(); } else if (include_last_screen_image_radio_->checked() && !last_image_->empty()) { image_data = reinterpret_cast(&last_image_->front()); image_data_size = last_image_->size(); } #else if (include_page_image_checkbox_->checked() && captured_image_.get() && !captured_image_->empty()) { image_data = reinterpret_cast(&captured_image_->front()); image_data_size = captured_image_->size(); } #endif #if defined(OS_CHROMEOS) BugReportUtil::SendReport(profile_, WideToUTF8(page_title_text_->GetText()), problem_type_, UTF16ToUTF8(page_url_text_->text()), UTF16ToUTF8(user_email_text_->text()), UTF16ToUTF8(description_text_->text()), image_data, image_data_size, screen_size_.width(), screen_size_.height(), WideToUTF8(bug_type_combo_->model()->GetItemAt(problem_type_)), sys_info_.get()); #else BugReportUtil::SendReport(profile_, WideToUTF8(page_title_text_->GetText()), problem_type_, UTF16ToUTF8(page_url_text_->text()), std::string(), UTF16ToUTF8(description_text_->text()), image_data, image_data_size, screen_size_.width(), screen_size_.height()); #endif } } return true; } #if defined(OS_CHROMEOS) void BugReportView::LinkActivated(views::Link* source, int event_flags) { GURL url; if (source == system_information_url_control_) { url = GURL(chrome::kAboutSystemURL); } else { NOTREACHED() << "Unknown link source"; return; } Browser* browser = BrowserList::GetLastActive(); if (browser) browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK); } #endif views::View* BugReportView::GetContentsView() { return this; }