session_crashed_bubble_view.cc revision 0de6073388f4e2780db8536178b129cd8f6ab386
1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/session_crashed_bubble_view.h" 6 7#include <vector> 8 9#include "base/prefs/pref_service.h" 10#include "chrome/browser/browser_process.h" 11#include "chrome/browser/chrome_notification_types.h" 12#include "chrome/browser/sessions/session_restore.h" 13#include "chrome/browser/ui/options/options_util.h" 14#include "chrome/browser/ui/startup/session_crashed_bubble.h" 15#include "chrome/browser/ui/startup/startup_browser_creator_impl.h" 16#include "chrome/browser/ui/tabs/tab_strip_model.h" 17#include "chrome/browser/ui/views/frame/browser_view.h" 18#include "chrome/browser/ui/views/toolbar/toolbar_view.h" 19#include "chrome/common/pref_names.h" 20#include "chrome/common/url_constants.h" 21#include "chrome/installer/util/google_update_settings.h" 22#include "content/public/browser/browser_context.h" 23#include "content/public/browser/notification_source.h" 24#include "content/public/browser/web_contents.h" 25#include "grit/chromium_strings.h" 26#include "grit/generated_resources.h" 27#include "grit/google_chrome_strings.h" 28#include "grit/ui_resources.h" 29#include "ui/base/l10n/l10n_util.h" 30#include "ui/base/resource/resource_bundle.h" 31#include "ui/views/controls/button/checkbox.h" 32#include "ui/views/controls/button/label_button.h" 33#include "ui/views/controls/label.h" 34#include "ui/views/controls/separator.h" 35#include "ui/views/controls/styled_label.h" 36#include "ui/views/layout/grid_layout.h" 37#include "ui/views/layout/layout_constants.h" 38#include "ui/views/widget/widget.h" 39 40using views::GridLayout; 41 42namespace { 43 44// Fixed width of the column holding the description label of the bubble. 45const int kWidthOfDescriptionText = 320; 46 47// Distance between checkbox and the text to the right of it. 48const int kCheckboxTextDistance = 4; 49 50// Margins width for the top rows to compensate for the bottom panel for which 51// we don't want any margin. 52const int kMarginWidth = 12; 53const int kMarginHeight = kMarginWidth; 54 55// The color of the background of the sub panel to offer UMA optin. 56const SkColor kLightGrayBackgroundColor = 0xFFF0F0F0; 57const SkColor kWhiteBackgroundColor = 0xFFFFFFFF; 58 59bool ShouldOfferMetricsReporting() { 60// Stats collection only applies to Google Chrome builds. 61#if defined(GOOGLE_CHROME_BUILD) 62 // Only show metrics reporting option if user didn't already consent to it. 63 if (GoogleUpdateSettings::GetCollectStatsConsent()) 64 return false; 65 return g_browser_process->local_state()->FindPreference( 66 prefs::kMetricsReportingEnabled)->IsUserModifiable(); 67#else 68 return false; 69#endif // defined(GOOGLE_CHROME_BUILD) 70} 71 72} // namespace 73 74// static 75void SessionCrashedBubbleView::Show(Browser* browser) { 76 if (browser->profile()->IsOffTheRecord()) 77 return; 78 79 views::View* anchor_view = 80 BrowserView::GetBrowserViewForBrowser(browser)->toolbar()->app_menu(); 81 content::WebContents* web_contents = 82 browser->tab_strip_model()->GetActiveWebContents(); 83 SessionCrashedBubbleView* crash_bubble = 84 new SessionCrashedBubbleView(anchor_view, browser, web_contents); 85 views::BubbleDelegateView::CreateBubble(crash_bubble)->Show(); 86} 87 88SessionCrashedBubbleView::SessionCrashedBubbleView( 89 views::View* anchor_view, 90 Browser* browser, 91 content::WebContents* web_contents) 92 : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT), 93 content::WebContentsObserver(web_contents), 94 browser_(browser), 95 web_contents_(web_contents), 96 restore_button_(NULL), 97 close_(NULL), 98 uma_option_(NULL), 99 started_navigation_(false) { 100 set_close_on_deactivate(false); 101 registrar_.Add( 102 this, 103 chrome::NOTIFICATION_TAB_CLOSING, 104 content::Source<content::NavigationController>(&( 105 web_contents->GetController()))); 106 browser->tab_strip_model()->AddObserver(this); 107} 108 109SessionCrashedBubbleView::~SessionCrashedBubbleView() { 110 browser_->tab_strip_model()->RemoveObserver(this); 111} 112 113views::View* SessionCrashedBubbleView::GetInitiallyFocusedView() { 114 return restore_button_; 115} 116 117void SessionCrashedBubbleView::Init() { 118 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 119 120 // Close button. 121 close_ = new views::LabelButton(this, base::string16()); 122 close_->SetImage(views::CustomButton::STATE_NORMAL, 123 *rb->GetImageNamed(IDR_CLOSE_2).ToImageSkia()); 124 close_->SetImage(views::CustomButton::STATE_HOVERED, 125 *rb->GetImageNamed(IDR_CLOSE_2_H).ToImageSkia()); 126 close_->SetImage(views::CustomButton::STATE_PRESSED, 127 *rb->GetImageNamed(IDR_CLOSE_2_P).ToImageSkia()); 128 close_->SetSize(close_->GetPreferredSize()); 129 close_->SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 0)); 130 131 // Bubble title label. 132 views::Label* title_label = new views::Label( 133 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE)); 134 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 135 title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::BoldFont)); 136 137 // Description text label. 138 views::Label* text_label = new views::Label( 139 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE)); 140 text_label->SetMultiLine(true); 141 text_label->SetLineHeight(20); 142 text_label->SetEnabledColor(SK_ColorDKGRAY); 143 text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 144 145 // Restore button. 146 restore_button_ = new views::LabelButton( 147 this, l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON)); 148 restore_button_->SetStyle(views::Button::STYLE_BUTTON); 149 restore_button_->SetIsDefault(true); 150 restore_button_->SetFontList(rb->GetFontList(ui::ResourceBundle::BoldFont)); 151 152 GridLayout* layout = new GridLayout(this); 153 SetLayoutManager(layout); 154 155 // Title and close button row. 156 const int kTitleColumnSetId = 0; 157 views::ColumnSet* cs = layout->AddColumnSet(kTitleColumnSetId); 158 cs->AddPaddingColumn(0, kMarginWidth); 159 cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, 160 GridLayout::USE_PREF, 0, 0); 161 cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing); 162 cs->AddColumn(GridLayout::TRAILING, GridLayout::LEADING, 0, 163 GridLayout::USE_PREF, 0, 0); 164 165 // Text row. 166 const int kTextColumnSetId = 1; 167 cs = layout->AddColumnSet(kTextColumnSetId); 168 cs->AddPaddingColumn(0, kMarginWidth); 169 cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, 170 GridLayout::FIXED, kWidthOfDescriptionText, 0); 171 172 // Restore button row 173 const int kButtonColumnSetId = 2; 174 cs = layout->AddColumnSet(kButtonColumnSetId); 175 cs->AddPaddingColumn(0, kMarginWidth); 176 cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing); 177 cs->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0, 178 GridLayout::USE_PREF, 0, 0); 179 cs->AddPaddingColumn(0, kMarginWidth); 180 181 layout->AddPaddingRow(0, kMarginHeight); 182 layout->StartRow(0, kTitleColumnSetId); 183 layout->AddView(title_label); 184 layout->AddView(close_); 185 layout->AddPaddingRow(0, kMarginHeight); 186 187 layout->StartRow(0, kTextColumnSetId); 188 layout->AddView(text_label); 189 layout->AddPaddingRow(0, kMarginHeight); 190 191 layout->StartRow(0, kButtonColumnSetId); 192 layout->AddView(restore_button_); 193 layout->AddPaddingRow(0, kMarginHeight); 194 195 // Metrics reporting option. 196 if (ShouldOfferMetricsReporting()) 197 CreateUmaOptinView(layout); 198 199 set_color(kWhiteBackgroundColor); 200 set_margins(gfx::Insets()); 201 Layout(); 202} 203 204void SessionCrashedBubbleView::CreateUmaOptinView(GridLayout* layout) { 205 // Checkbox for metric reporting setting. 206 // Since the text to the right of the checkbox can't be a simple string (needs 207 // a hyperlink in it), this checkbox contains an empty string as its label, 208 // and the real text will be added as a separate view. 209 uma_option_ = new views::Checkbox(base::string16()); 210 uma_option_->SetTextColor(views::Button::STATE_NORMAL, SK_ColorGRAY); 211 uma_option_->SetChecked(false); 212 uma_option_->set_background( 213 views::Background::CreateSolidBackground(kLightGrayBackgroundColor)); 214 uma_option_->set_listener(this); 215 216 // The text to the right of the checkbox. 217 size_t offset; 218 base::string16 link_text = 219 l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT); 220 base::string16 uma_text = l10n_util::GetStringFUTF16( 221 IDS_SESSION_CRASHED_VIEW_UMA_OPTIN, 222 link_text, 223 &offset); 224 views::StyledLabel* uma_label = new views::StyledLabel(uma_text, this); 225 uma_label->set_background( 226 views::Background::CreateSolidBackground(kLightGrayBackgroundColor)); 227 views::StyledLabel::RangeStyleInfo link_style = 228 views::StyledLabel::RangeStyleInfo::CreateForLink(); 229 link_style.font_style = gfx::Font::NORMAL; 230 uma_label->AddStyleRange(gfx::Range(offset, offset + link_text.length()), 231 link_style); 232 views::StyledLabel::RangeStyleInfo uma_style; 233 uma_style.color = SK_ColorGRAY; 234 gfx::Range before_link_range(0, offset); 235 if (!before_link_range.is_empty()) 236 uma_label->AddStyleRange(before_link_range, uma_style); 237 gfx::Range after_link_range(offset + link_text.length(), uma_text.length()); 238 if (!after_link_range.is_empty()) 239 uma_label->AddStyleRange(after_link_range, uma_style); 240 241 // We use a border instead of padding so that the background color reach 242 // the edges of the bubble. 243 uma_option_->SetBorder( 244 views::Border::CreateSolidSidedBorder(0, kMarginWidth, 0, 0, 245 kLightGrayBackgroundColor)); 246 uma_label->SetBorder( 247 views::Border::CreateSolidSidedBorder( 248 kMarginHeight, kCheckboxTextDistance, kMarginHeight, kMarginWidth, 249 kLightGrayBackgroundColor)); 250 251 // Separator. 252 const int kSeparatorColumnSetId = 3; 253 views::ColumnSet* cs = layout->AddColumnSet(kSeparatorColumnSetId); 254 cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, 255 GridLayout::FIXED, kWidthOfDescriptionText + kMarginWidth, 0); 256 257 // Reporting row. 258 const int kReportColumnSetId = 4; 259 cs = layout->AddColumnSet(kReportColumnSetId); 260 cs->AddColumn(GridLayout::CENTER, GridLayout::FILL, 0, 261 GridLayout::USE_PREF, 0, 0); 262 cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, 263 GridLayout::FIXED, kWidthOfDescriptionText, 0); 264 265 layout->StartRow(0, kSeparatorColumnSetId); 266 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 267 layout->StartRow(0, kReportColumnSetId); 268 layout->AddView(uma_option_); 269 layout->AddView(uma_label); 270} 271 272void SessionCrashedBubbleView::ButtonPressed(views::Button* sender, 273 const ui::Event& event) { 274 DCHECK(sender); 275 if (sender == restore_button_) 276 RestorePreviousSession(sender); 277 else if (sender == close_) 278 CloseBubble(); 279} 280 281void SessionCrashedBubbleView::StyledLabelLinkClicked(const gfx::Range& range, 282 int event_flags) { 283 browser_->OpenURL(content::OpenURLParams( 284 GURL("https://support.google.com/chrome/answer/96817"), 285 content::Referrer(), 286 NEW_FOREGROUND_TAB, 287 content::PAGE_TRANSITION_LINK, 288 false)); 289} 290 291void SessionCrashedBubbleView::DidStartNavigationToPendingEntry( 292 const GURL& url, 293 content::NavigationController::ReloadType reload_type) { 294 started_navigation_ = true; 295} 296 297void SessionCrashedBubbleView::DidFinishLoad( 298 int64 frame_id, 299 const GURL& validated_url, 300 bool is_main_frame, 301 content::RenderViewHost* render_view_host) { 302 if (started_navigation_) 303 CloseBubble(); 304} 305 306void SessionCrashedBubbleView::WasShown() { 307 GetWidget()->Show(); 308} 309 310void SessionCrashedBubbleView::WasHidden() { 311 GetWidget()->Hide(); 312} 313 314void SessionCrashedBubbleView::Observe( 315 int type, 316 const content::NotificationSource& source, 317 const content::NotificationDetails& details) { 318 if (type == chrome::NOTIFICATION_TAB_CLOSING) 319 CloseBubble(); 320} 321 322void SessionCrashedBubbleView::TabDetachedAt(content::WebContents* contents, 323 int index) { 324 if (web_contents_ == contents) 325 CloseBubble(); 326} 327 328void SessionCrashedBubbleView::RestorePreviousSession(views::Button* sender) { 329 SessionRestore::RestoreSessionAfterCrash(browser_); 330 331 // Record user's choice for opting in to UMA. 332 // There's no opting-out choice in the crash restore bubble. 333 if (uma_option_ && uma_option_->checked()) 334 OptionsUtil::ResolveMetricsReportingEnabled(true); 335 CloseBubble(); 336} 337 338void SessionCrashedBubbleView::CloseBubble() { 339 GetWidget()->Close(); 340} 341 342bool ShowSessionCrashedBubble(Browser* browser) { 343 SessionCrashedBubbleView::Show(browser); 344 return true; 345} 346