1 // Copyright (c) 2011 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/chromeos/login/eula_view.h" 6 7 #include <signal.h> 8 #include <sys/types.h> 9 #include <string> 10 11 #include "base/basictypes.h" 12 #include "base/message_loop.h" 13 #include "base/task.h" 14 #include "base/utf_string_conversions.h" 15 #include "base/values.h" 16 #include "chrome/browser/browser_process.h" 17 #include "chrome/browser/chromeos/cros/cros_library.h" 18 #include "chrome/browser/chromeos/cros/cryptohome_library.h" 19 #include "chrome/browser/chromeos/customization_document.h" 20 #include "chrome/browser/chromeos/login/background_view.h" 21 #include "chrome/browser/chromeos/login/help_app_launcher.h" 22 #include "chrome/browser/chromeos/login/helper.h" 23 #include "chrome/browser/chromeos/login/login_utils.h" 24 #include "chrome/browser/chromeos/login/network_screen_delegate.h" 25 #include "chrome/browser/chromeos/login/rounded_rect_painter.h" 26 #include "chrome/browser/chromeos/login/wizard_controller.h" 27 #include "chrome/browser/profiles/profile_manager.h" 28 #include "chrome/browser/ui/views/dom_view.h" 29 #include "chrome/browser/ui/views/window.h" 30 #include "chrome/common/url_constants.h" 31 #include "content/browser/site_instance.h" 32 #include "content/browser/tab_contents/tab_contents.h" 33 #include "content/common/native_web_keyboard_event.h" 34 #include "grit/chromium_strings.h" 35 #include "grit/generated_resources.h" 36 #include "grit/locale_settings.h" 37 #include "grit/theme_resources.h" 38 #include "ui/base/l10n/l10n_util.h" 39 #include "ui/base/resource/resource_bundle.h" 40 #include "views/controls/button/checkbox.h" 41 #include "views/controls/button/native_button_gtk.h" 42 #include "views/controls/label.h" 43 #include "views/controls/throbber.h" 44 #include "views/events/event.h" 45 #include "views/layout/grid_layout.h" 46 #include "views/layout/layout_constants.h" 47 #include "views/layout/layout_manager.h" 48 #include "views/widget/widget_gtk.h" 49 #include "views/window/dialog_delegate.h" 50 #include "views/window/window.h" 51 52 using views::WidgetGtk; 53 54 namespace { 55 56 const int kBorderSize = 10; 57 const int kCheckboxWidth = 26; 58 const int kLastButtonHorizontalMargin = 10; 59 const int kMargin = 20; 60 const int kTextMargin = 10; 61 const int kTpmCheckIntervalMs = 500; 62 63 // TODO(glotov): this URL should be changed to actual Google ChromeOS EULA. 64 // See crbug.com/4647 65 const char kGoogleEulaUrl[] = "about:terms"; 66 67 enum kLayoutColumnsets { 68 SINGLE_CONTROL_ROW, 69 SINGLE_CONTROL_WITH_SHIFT_ROW, 70 SINGLE_LINK_WITH_SHIFT_ROW, 71 LAST_ROW 72 }; 73 74 // Helper class that disables using native label for subclassed GTK control. 75 class EULANativeCheckboxGtk : public views::NativeCheckboxGtk { 76 public: 77 explicit EULANativeCheckboxGtk(views::Checkbox* checkbox) 78 : views::NativeCheckboxGtk(checkbox) { 79 set_fast_resize(true); 80 } 81 virtual ~EULANativeCheckboxGtk() { } 82 virtual bool UsesNativeLabel() const { return false; } 83 virtual void UpdateLabel() { } 84 }; 85 86 // views::Checkbox specialization that uses its internal views::Label 87 // instead of native one. We need this because native label does not 88 // support multiline property and we need it for certain languages. 89 class EULACheckbox : public views::Checkbox { 90 public: 91 EULACheckbox() { } 92 virtual ~EULACheckbox() { } 93 94 protected: 95 virtual views::NativeButtonWrapper* CreateWrapper() { 96 views::NativeButtonWrapper* native_wrapper = 97 new EULANativeCheckboxGtk(this); 98 native_wrapper->UpdateChecked(); 99 return native_wrapper; 100 } 101 }; 102 103 // A simple LayoutManager that causes the associated view's one child to be 104 // sized to match the bounds of its parent except the bounds, if set. 105 struct FillLayoutWithBorder : public views::LayoutManager { 106 // Overridden from LayoutManager: 107 virtual void Layout(views::View* host) { 108 DCHECK(host->has_children()); 109 host->GetChildViewAt(0)->SetBoundsRect(host->GetContentsBounds()); 110 } 111 virtual gfx::Size GetPreferredSize(views::View* host) { 112 return gfx::Size(host->width(), host->height()); 113 } 114 }; 115 116 // System security setting dialog. 117 class TpmInfoView : public views::View, 118 public views::DialogDelegate { 119 public: 120 explicit TpmInfoView(std::string* password) 121 : ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)), 122 password_(password) { 123 DCHECK(password_); 124 } 125 126 void Init(); 127 128 protected: 129 // views::DialogDelegate overrides: 130 virtual bool Accept() { return true; } 131 virtual bool IsModal() const { return true; } 132 virtual views::View* GetContentsView() { return this; } 133 virtual int GetDialogButtons() const { 134 return MessageBoxFlags::DIALOGBUTTON_OK; 135 } 136 137 // views::View overrides: 138 virtual std::wstring GetWindowTitle() const { 139 return UTF16ToWide( 140 l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING)); 141 } 142 143 gfx::Size GetPreferredSize() { 144 return gfx::Size(views::Window::GetLocalizedContentsSize( 145 IDS_TPM_INFO_DIALOG_WIDTH_CHARS, 146 IDS_TPM_INFO_DIALOG_HEIGHT_LINES)); 147 } 148 149 private: 150 void PullPassword(); 151 152 ScopedRunnableMethodFactory<TpmInfoView> runnable_method_factory_; 153 154 // Holds pointer to the password storage. 155 std::string* password_; 156 157 views::Label* busy_label_; 158 views::Label* password_label_; 159 views::Throbber* throbber_; 160 161 DISALLOW_COPY_AND_ASSIGN(TpmInfoView); 162 }; 163 164 void TpmInfoView::Init() { 165 views::GridLayout* layout = views::GridLayout::CreatePanel(this); 166 SetLayoutManager(layout); 167 views::ColumnSet* column_set = layout->AddColumnSet(0); 168 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, 169 views::GridLayout::USE_PREF, 0, 0); 170 layout->StartRow(0, 0); 171 views::Label* label = new views::Label(UTF16ToWide( 172 l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING_DESCRIPTION))); 173 label->SetMultiLine(true); 174 label->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 175 layout->AddView(label); 176 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 177 178 layout->StartRow(0, 0); 179 label = new views::Label(UTF16ToWide(l10n_util::GetStringUTF16( 180 IDS_EULA_SYSTEM_SECURITY_SETTING_DESCRIPTION_KEY))); 181 label->SetMultiLine(true); 182 label->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 183 layout->AddView(label); 184 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 185 186 column_set = layout->AddColumnSet(1); 187 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, 188 views::GridLayout::USE_PREF, 0, 0); 189 layout->StartRow(0, 1); 190 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 191 gfx::Font password_font = 192 rb.GetFont(ResourceBundle::MediumFont).DeriveFont(0, gfx::Font::BOLD); 193 // Password will be set later. 194 password_label_ = new views::Label(L"", password_font); 195 password_label_->SetVisible(false); 196 layout->AddView(password_label_); 197 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 198 199 column_set = layout->AddColumnSet(2); 200 column_set->AddPaddingColumn(1, 0); 201 // Resize of the throbber and label is not allowed, since we want they to be 202 // placed in the center. 203 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 204 views::GridLayout::USE_PREF, 0, 0); 205 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 206 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 207 views::GridLayout::USE_PREF, 0, 0); 208 column_set->AddPaddingColumn(1, 0); 209 // Border padding columns should have the same width. It guaranties that 210 // throbber and label will be placed in the center. 211 column_set->LinkColumnSizes(0, 4, -1); 212 213 layout->StartRow(0, 2); 214 throbber_ = chromeos::CreateDefaultThrobber(); 215 throbber_->Start(); 216 layout->AddView(throbber_); 217 busy_label_ = new views::Label( 218 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_TPM_BUSY))); 219 layout->AddView(busy_label_); 220 layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing); 221 222 PullPassword(); 223 } 224 225 void TpmInfoView::PullPassword() { 226 // Since this method is also called directly. 227 runnable_method_factory_.RevokeAll(); 228 229 chromeos::CryptohomeLibrary* cryptohome = 230 chromeos::CrosLibrary::Get()->GetCryptohomeLibrary(); 231 232 bool password_acquired = false; 233 if (password_->empty() && cryptohome->TpmIsReady()) { 234 password_acquired = cryptohome->TpmGetPassword(password_); 235 if (!password_acquired) { 236 password_->clear(); 237 } else if (password_->empty()) { 238 // For a fresh OOBE flow TPM is uninitialized, 239 // ownership process is started at the EULA screen, 240 // password is cleared after EULA is accepted. 241 LOG(ERROR) << "TPM returned an empty password."; 242 } 243 } 244 if (password_->empty() && !password_acquired) { 245 // Password hasn't been acquired, reschedule pulling. 246 MessageLoop::current()->PostDelayedTask( 247 FROM_HERE, 248 runnable_method_factory_.NewRunnableMethod(&TpmInfoView::PullPassword), 249 kTpmCheckIntervalMs); 250 } else { 251 password_label_->SetText(ASCIIToWide(*password_)); 252 password_label_->SetVisible(true); 253 busy_label_->SetVisible(false); 254 throbber_->Stop(); 255 throbber_->SetVisible(false); 256 } 257 } 258 259 } // namespace 260 261 namespace chromeos { 262 263 //////////////////////////////////////////////////////////////////////////////// 264 // EulaView, public: 265 266 EulaView::EulaView(chromeos::ScreenObserver* observer) 267 : google_eula_label_(NULL), 268 google_eula_view_(NULL), 269 usage_statistics_checkbox_(NULL), 270 learn_more_link_(NULL), 271 oem_eula_label_(NULL), 272 oem_eula_view_(NULL), 273 system_security_settings_link_(NULL), 274 back_button_(NULL), 275 continue_button_(NULL), 276 observer_(observer), 277 bubble_(NULL) { 278 } 279 280 EulaView::~EulaView() { 281 // bubble_ will be set to NULL in callback. 282 if (bubble_) 283 bubble_->Close(); 284 } 285 286 // Convenience function to set layout's columnsets for this screen. 287 static void SetUpGridLayout(views::GridLayout* layout) { 288 static const int kPadding = kBorderSize + kMargin; 289 views::ColumnSet* column_set = layout->AddColumnSet(SINGLE_CONTROL_ROW); 290 column_set->AddPaddingColumn(0, kPadding); 291 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, 292 views::GridLayout::USE_PREF, 0, 0); 293 column_set->AddPaddingColumn(0, kPadding); 294 295 column_set = layout->AddColumnSet(SINGLE_CONTROL_WITH_SHIFT_ROW); 296 column_set->AddPaddingColumn(0, kPadding + kTextMargin); 297 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, 298 views::GridLayout::USE_PREF, 0, 0); 299 column_set->AddPaddingColumn(0, kPadding); 300 301 column_set = layout->AddColumnSet(SINGLE_LINK_WITH_SHIFT_ROW); 302 column_set->AddPaddingColumn(0, kPadding + kTextMargin + kCheckboxWidth); 303 column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1, 304 views::GridLayout::USE_PREF, 0, 0); 305 column_set->AddPaddingColumn(0, kPadding); 306 307 column_set = layout->AddColumnSet(LAST_ROW); 308 column_set->AddPaddingColumn(0, kPadding + kTextMargin); 309 column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1, 310 views::GridLayout::USE_PREF, 0, 0); 311 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 312 views::GridLayout::USE_PREF, 0, 0); 313 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 314 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 315 views::GridLayout::USE_PREF, 0, 0); 316 column_set->AddPaddingColumn(0, kLastButtonHorizontalMargin + kBorderSize); 317 } 318 319 // Convenience function. Returns URL of the OEM EULA page that should be 320 // displayed using current locale and manifest. Returns empty URL otherwise. 321 static GURL GetOemEulaPagePath() { 322 const StartupCustomizationDocument* customization = 323 StartupCustomizationDocument::GetInstance(); 324 if (customization->IsReady()) { 325 std::string locale = customization->initial_locale(); 326 std::string eula_page = customization->GetEULAPage(locale); 327 if (!eula_page.empty()) 328 return GURL(eula_page); 329 330 VLOG(1) << "No eula found for locale: " << locale; 331 } else { 332 LOG(ERROR) << "No manifest found."; 333 } 334 return GURL(); 335 } 336 337 void EulaView::Init() { 338 // First, command to own the TPM. 339 if (chromeos::CrosLibrary::Get()->EnsureLoaded()) { 340 chromeos::CrosLibrary::Get()-> 341 GetCryptohomeLibrary()->TpmCanAttemptOwnership(); 342 } else { 343 LOG(ERROR) << "Cros library not loaded. " 344 << "We must have disabled the link that led here."; 345 } 346 347 // Use rounded rect background. 348 views::Painter* painter = CreateWizardPainter( 349 &BorderDefinition::kScreenBorder); 350 set_background( 351 views::Background::CreateBackgroundPainter(true, painter)); 352 353 // Layout created controls. 354 views::GridLayout* layout = new views::GridLayout(this); 355 SetLayoutManager(layout); 356 SetUpGridLayout(layout); 357 358 static const int kPadding = kBorderSize + kMargin; 359 layout->AddPaddingRow(0, kPadding); 360 layout->StartRow(0, SINGLE_CONTROL_ROW); 361 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 362 gfx::Font label_font = 363 rb.GetFont(ResourceBundle::MediumFont).DeriveFont(0, gfx::Font::NORMAL); 364 google_eula_label_ = new views::Label(std::wstring(), label_font); 365 layout->AddView(google_eula_label_, 1, 1, 366 views::GridLayout::LEADING, views::GridLayout::FILL); 367 368 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 369 layout->StartRow(1, SINGLE_CONTROL_ROW); 370 views::View* box_view = new views::View(); 371 box_view->set_border(views::Border::CreateSolidBorder(1, SK_ColorBLACK)); 372 box_view->SetLayoutManager(new FillLayoutWithBorder()); 373 layout->AddView(box_view); 374 375 google_eula_view_ = new DOMView(); 376 box_view->AddChildView(google_eula_view_); 377 378 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 379 layout->StartRow(0, SINGLE_CONTROL_WITH_SHIFT_ROW); 380 usage_statistics_checkbox_ = new EULACheckbox(); 381 usage_statistics_checkbox_->SetMultiLine(true); 382 usage_statistics_checkbox_->SetChecked( 383 observer_->usage_statistics_reporting()); 384 layout->AddView(usage_statistics_checkbox_); 385 386 layout->StartRow(0, SINGLE_LINK_WITH_SHIFT_ROW); 387 learn_more_link_ = new views::Link(); 388 learn_more_link_->SetController(this); 389 layout->AddView(learn_more_link_); 390 391 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 392 layout->StartRow(0, SINGLE_CONTROL_ROW); 393 oem_eula_label_ = new views::Label(std::wstring(), label_font); 394 layout->AddView(oem_eula_label_, 1, 1, 395 views::GridLayout::LEADING, views::GridLayout::FILL); 396 397 oem_eula_page_ = GetOemEulaPagePath(); 398 if (!oem_eula_page_.is_empty()) { 399 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 400 layout->StartRow(1, SINGLE_CONTROL_ROW); 401 box_view = new views::View(); 402 box_view->SetLayoutManager(new FillLayoutWithBorder()); 403 box_view->set_border(views::Border::CreateSolidBorder(1, SK_ColorBLACK)); 404 layout->AddView(box_view); 405 406 oem_eula_view_ = new DOMView(); 407 box_view->AddChildView(oem_eula_view_); 408 } 409 410 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 411 layout->StartRow(0, LAST_ROW); 412 system_security_settings_link_ = new views::Link(); 413 system_security_settings_link_->SetController(this); 414 415 if (!chromeos::CrosLibrary::Get()->EnsureLoaded() || 416 !chromeos::CrosLibrary::Get()->GetCryptohomeLibrary()-> 417 TpmIsEnabled()) { 418 system_security_settings_link_->SetEnabled(false); 419 } 420 421 layout->AddView(system_security_settings_link_); 422 423 back_button_ = new login::WideButton(this, std::wstring()); 424 layout->AddView(back_button_); 425 426 continue_button_ = new login::WideButton(this, std::wstring()); 427 layout->AddView(continue_button_); 428 layout->AddPaddingRow(0, kPadding); 429 430 UpdateLocalizedStrings(); 431 } 432 433 void EulaView::UpdateLocalizedStrings() { 434 // Load Google EULA and its title. 435 LoadEulaView(google_eula_view_, google_eula_label_, GURL(kGoogleEulaUrl)); 436 437 // Load OEM EULA and its title. 438 if (!oem_eula_page_.is_empty()) 439 LoadEulaView(oem_eula_view_, oem_eula_label_, oem_eula_page_); 440 441 // Set tooltip for usage statistics checkbox if the metric is unmanaged. 442 if (!usage_statistics_checkbox_->IsEnabled()) { 443 usage_statistics_checkbox_->SetTooltipText( 444 UTF16ToWide(l10n_util::GetStringUTF16(IDS_OPTION_DISABLED_BY_POLICY))); 445 } 446 447 // Set tooltip for system security settings link if TPM is disabled. 448 if (!system_security_settings_link_->IsEnabled()) { 449 system_security_settings_link_->SetTooltipText( 450 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_TPM_DISABLED))); 451 } 452 453 // Load other labels from resources. 454 usage_statistics_checkbox_->SetLabel( 455 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_CHECKBOX_ENABLE_LOGGING))); 456 learn_more_link_->SetText( 457 UTF16ToWide(l10n_util::GetStringUTF16(IDS_LEARN_MORE))); 458 system_security_settings_link_->SetText( 459 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_SYSTEM_SECURITY_SETTING))); 460 continue_button_->SetLabel(UTF16ToWide( 461 l10n_util::GetStringUTF16(IDS_EULA_ACCEPT_AND_CONTINUE_BUTTON))); 462 back_button_->SetLabel( 463 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EULA_BACK_BUTTON))); 464 } 465 466 //////////////////////////////////////////////////////////////////////////////// 467 // EulaView, protected, views::View implementation: 468 469 void EulaView::OnLocaleChanged() { 470 UpdateLocalizedStrings(); 471 Layout(); 472 } 473 474 //////////////////////////////////////////////////////////////////////////////// 475 // views::ButtonListener implementation: 476 477 void EulaView::ButtonPressed(views::Button* sender, const views::Event& event) { 478 if (usage_statistics_checkbox_) { 479 observer_->set_usage_statistics_reporting( 480 usage_statistics_checkbox_->checked()); 481 } 482 if (sender == continue_button_) { 483 observer_->OnExit(ScreenObserver::EULA_ACCEPTED); 484 } else if (sender == back_button_) { 485 observer_->OnExit(ScreenObserver::EULA_BACK); 486 } 487 } 488 489 //////////////////////////////////////////////////////////////////////////////// 490 // views::LinkController implementation: 491 492 void EulaView::LinkActivated(views::Link* source, int event_flags) { 493 gfx::NativeWindow parent_window = 494 LoginUtils::Get()->GetBackgroundView()->GetNativeWindow(); 495 if (source == learn_more_link_) { 496 if (!help_app_.get()) 497 help_app_ = new HelpAppLauncher(parent_window); 498 help_app_->ShowHelpTopic(HelpAppLauncher::HELP_STATS_USAGE); 499 } else if (source == system_security_settings_link_) { 500 TpmInfoView* view = new TpmInfoView(&tpm_password_); 501 view->Init(); 502 views::Window* window = browser::CreateViewsWindow(parent_window, 503 gfx::Rect(), 504 view); 505 window->SetIsAlwaysOnTop(true); 506 window->Show(); 507 } 508 } 509 510 //////////////////////////////////////////////////////////////////////////////// 511 // TabContentsDelegate implementation: 512 513 // Convenience function. Queries |eula_view| for HTML title and, if it 514 // is ready, assigns it to |eula_label| and returns true so the caller 515 // view calls Layout(). 516 static bool PublishTitleIfReady(const TabContents* contents, 517 DOMView* eula_view, 518 views::Label* eula_label) { 519 if (contents != eula_view->tab_contents()) 520 return false; 521 eula_label->SetText(UTF16ToWide(eula_view->tab_contents()->GetTitle())); 522 return true; 523 } 524 525 void EulaView::NavigationStateChanged(const TabContents* contents, 526 unsigned changed_flags) { 527 if (changed_flags & TabContents::INVALIDATE_TITLE) { 528 if (PublishTitleIfReady(contents, google_eula_view_, google_eula_label_) || 529 PublishTitleIfReady(contents, oem_eula_view_, oem_eula_label_)) { 530 Layout(); 531 } 532 } 533 } 534 535 void EulaView::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) { 536 views::Widget* widget = GetWidget(); 537 if (widget && event.os_event && !event.skip_in_browser) { 538 views::KeyEvent views_event(reinterpret_cast<GdkEvent*>(event.os_event)); 539 static_cast<views::WidgetGtk*>(widget)->HandleKeyboardEvent(views_event); 540 } 541 } 542 543 //////////////////////////////////////////////////////////////////////////////// 544 // EulaView, private: 545 546 void EulaView::LoadEulaView(DOMView* eula_view, 547 views::Label* eula_label, 548 const GURL& eula_url) { 549 Profile* profile = ProfileManager::GetDefaultProfile(); 550 eula_view->Init(profile, 551 SiteInstance::CreateSiteInstanceForURL(profile, eula_url)); 552 eula_view->LoadURL(eula_url); 553 eula_view->tab_contents()->set_delegate(this); 554 } 555 556 //////////////////////////////////////////////////////////////////////////////// 557 // EulaView, private, views::View implementation: 558 559 bool EulaView::OnKeyPressed(const views::KeyEvent&) { 560 // Close message bubble if shown. bubble_ will be set to NULL in callback. 561 if (bubble_) { 562 bubble_->Close(); 563 return true; 564 } 565 return false; 566 } 567 568 } // namespace chromeos 569