1 // Copyright (c) 2012 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/app_list/app_list_view_delegate.h" 6 7 #include <vector> 8 9 #include "apps/custom_launcher_page_contents.h" 10 #include "base/callback.h" 11 #include "base/command_line.h" 12 #include "base/files/file_path.h" 13 #include "base/metrics/user_metrics.h" 14 #include "base/stl_util.h" 15 #include "chrome/browser/apps/scoped_keep_alive.h" 16 #include "chrome/browser/browser_process.h" 17 #include "chrome/browser/chrome_notification_types.h" 18 #include "chrome/browser/profiles/profile_info_cache.h" 19 #include "chrome/browser/profiles/profile_manager.h" 20 #include "chrome/browser/search/hotword_service.h" 21 #include "chrome/browser/search/hotword_service_factory.h" 22 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 23 #include "chrome/browser/ui/app_list/app_list_service.h" 24 #include "chrome/browser/ui/app_list/app_list_syncable_service.h" 25 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h" 26 #include "chrome/browser/ui/app_list/search/search_controller.h" 27 #include "chrome/browser/ui/app_list/start_page_service.h" 28 #include "chrome/browser/ui/apps/chrome_app_delegate.h" 29 #include "chrome/browser/ui/browser_finder.h" 30 #include "chrome/browser/ui/chrome_pages.h" 31 #include "chrome/browser/ui/host_desktop.h" 32 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" 33 #include "chrome/browser/web_applications/web_app.h" 34 #include "chrome/common/chrome_switches.h" 35 #include "chrome/common/extensions/extension_constants.h" 36 #include "chrome/common/url_constants.h" 37 #include "components/signin/core/browser/signin_manager.h" 38 #include "content/public/browser/browser_thread.h" 39 #include "content/public/browser/notification_service.h" 40 #include "content/public/browser/page_navigator.h" 41 #include "content/public/browser/user_metrics.h" 42 #include "content/public/browser/web_contents.h" 43 #include "extensions/browser/extension_registry.h" 44 #include "extensions/common/constants.h" 45 #include "extensions/common/extension_set.h" 46 #include "extensions/common/manifest_constants.h" 47 #include "extensions/common/manifest_handlers/launcher_page_info.h" 48 #include "grit/theme_resources.h" 49 #include "ui/app_list/app_list_switches.h" 50 #include "ui/app_list/app_list_view_delegate_observer.h" 51 #include "ui/app_list/search_box_model.h" 52 #include "ui/app_list/speech_ui_model.h" 53 #include "ui/base/resource/resource_bundle.h" 54 #include "ui/views/controls/webview/webview.h" 55 56 #if defined(TOOLKIT_VIEWS) 57 #include "ui/views/controls/webview/webview.h" 58 #endif 59 60 #if defined(USE_AURA) 61 #include "ui/keyboard/keyboard_util.h" 62 #endif 63 64 #if defined(USE_ASH) 65 #include "ash/shell.h" 66 #include "ash/wm/maximize_mode/maximize_mode_controller.h" 67 #include "chrome/browser/ui/ash/app_list/app_sync_ui_state_watcher.h" 68 #endif 69 70 #if defined(OS_WIN) 71 #include "chrome/browser/web_applications/web_app_win.h" 72 #endif 73 74 75 namespace chrome { 76 const char kAppLauncherCategoryTag[] = "AppLauncher"; 77 } // namespace chrome 78 79 namespace { 80 81 const int kAutoLaunchDefaultTimeoutMilliSec = 50; 82 83 #if defined(OS_WIN) 84 void CreateShortcutInWebAppDir( 85 const base::FilePath& app_data_dir, 86 base::Callback<void(const base::FilePath&)> callback, 87 const web_app::ShortcutInfo& info) { 88 content::BrowserThread::PostTaskAndReplyWithResult( 89 content::BrowserThread::FILE, 90 FROM_HERE, 91 base::Bind(web_app::CreateShortcutInWebAppDir, app_data_dir, info), 92 callback); 93 } 94 #endif 95 96 void PopulateUsers(const ProfileInfoCache& profile_info, 97 const base::FilePath& active_profile_path, 98 app_list::AppListViewDelegate::Users* users) { 99 users->clear(); 100 const size_t count = profile_info.GetNumberOfProfiles(); 101 for (size_t i = 0; i < count; ++i) { 102 app_list::AppListViewDelegate::User user; 103 user.name = profile_info.GetNameOfProfileAtIndex(i); 104 user.email = profile_info.GetUserNameOfProfileAtIndex(i); 105 user.profile_path = profile_info.GetPathOfProfileAtIndex(i); 106 user.active = active_profile_path == user.profile_path; 107 users->push_back(user); 108 } 109 } 110 111 // Gets a list of URLs of the custom launcher pages to show in the launcher. 112 // Returns a URL for each installed launcher page. If --custom-launcher-page is 113 // specified and valid, also includes that URL. 114 void GetCustomLauncherPageUrls(content::BrowserContext* browser_context, 115 std::vector<GURL>* urls) { 116 // First, check the command line. 117 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); 118 if (app_list::switches::IsExperimentalAppListEnabled() && 119 command_line->HasSwitch(switches::kCustomLauncherPage)) { 120 GURL custom_launcher_page_url( 121 command_line->GetSwitchValueASCII(switches::kCustomLauncherPage)); 122 123 if (custom_launcher_page_url.SchemeIs(extensions::kExtensionScheme)) { 124 urls->push_back(custom_launcher_page_url); 125 } else { 126 LOG(ERROR) << "Invalid custom launcher page URL: " 127 << custom_launcher_page_url.possibly_invalid_spec(); 128 } 129 } 130 131 // Search the list of installed extensions for ones with 'launcher_page'. 132 extensions::ExtensionRegistry* extension_registry = 133 extensions::ExtensionRegistry::Get(browser_context); 134 const extensions::ExtensionSet& enabled_extensions = 135 extension_registry->enabled_extensions(); 136 for (extensions::ExtensionSet::const_iterator it = enabled_extensions.begin(); 137 it != enabled_extensions.end(); 138 ++it) { 139 const extensions::Extension* extension = it->get(); 140 extensions::LauncherPageInfo* info = 141 extensions::LauncherPageHandler::GetInfo(extension); 142 if (!info) 143 continue; 144 145 urls->push_back(extension->GetResourceURL(info->page)); 146 } 147 } 148 149 } // namespace 150 151 AppListViewDelegate::AppListViewDelegate(AppListControllerDelegate* controller) 152 : controller_(controller), 153 profile_(NULL), 154 model_(NULL), 155 scoped_observer_(this) { 156 CHECK(controller_); 157 // The SigninManagerFactor and the SigninManagers are observed to keep the 158 // profile switcher menu up to date, with the correct list of profiles and the 159 // correct email address (or none for signed out users) for each. 160 SigninManagerFactory::GetInstance()->AddObserver(this); 161 162 // Start observing all already-created SigninManagers. 163 ProfileManager* profile_manager = g_browser_process->profile_manager(); 164 std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles(); 165 for (std::vector<Profile*>::iterator i = profiles.begin(); 166 i != profiles.end(); 167 ++i) { 168 SigninManagerBase* manager = 169 SigninManagerFactory::GetForProfileIfExists(*i); 170 if (manager) { 171 DCHECK(!scoped_observer_.IsObserving(manager)); 172 scoped_observer_.Add(manager); 173 } 174 } 175 176 profile_manager->GetProfileInfoCache().AddObserver(this); 177 speech_ui_.reset(new app_list::SpeechUIModel); 178 179 #if defined(GOOGLE_CHROME_BUILD) 180 speech_ui_->set_logo( 181 *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 182 IDR_APP_LIST_GOOGLE_LOGO_VOICE_SEARCH)); 183 #endif 184 185 registrar_.Add(this, 186 chrome::NOTIFICATION_APP_TERMINATING, 187 content::NotificationService::AllSources()); 188 } 189 190 AppListViewDelegate::~AppListViewDelegate() { 191 // Note that the destructor is not always called. E.g. on Mac, this is owned 192 // by a leaky singleton. Essential shutdown work must be done by observing 193 // chrome::NOTIFICATION_APP_TERMINATING. 194 SetProfile(NULL); 195 g_browser_process->profile_manager()->GetProfileInfoCache().RemoveObserver( 196 this); 197 198 SigninManagerFactory* factory = SigninManagerFactory::GetInstance(); 199 if (factory) 200 factory->RemoveObserver(this); 201 } 202 203 void AppListViewDelegate::SetProfile(Profile* new_profile) { 204 if (profile_ == new_profile) 205 return; 206 207 if (profile_) { 208 // Note: |search_controller_| has a reference to |speech_ui_| so must be 209 // destroyed first. 210 search_controller_.reset(); 211 custom_page_contents_.clear(); 212 app_list::StartPageService* start_page_service = 213 app_list::StartPageService::Get(profile_); 214 if (start_page_service) 215 start_page_service->RemoveObserver(this); 216 #if defined(USE_ASH) 217 app_sync_ui_state_watcher_.reset(); 218 #endif 219 model_ = NULL; 220 } 221 222 profile_ = new_profile; 223 if (!profile_) { 224 speech_ui_->SetSpeechRecognitionState(app_list::SPEECH_RECOGNITION_OFF); 225 return; 226 } 227 228 model_ = 229 app_list::AppListSyncableServiceFactory::GetForProfile(profile_)->model(); 230 231 #if defined(USE_ASH) 232 app_sync_ui_state_watcher_.reset(new AppSyncUIStateWatcher(profile_, model_)); 233 #endif 234 235 SetUpSearchUI(); 236 SetUpProfileSwitcher(); 237 SetUpCustomLauncherPages(); 238 239 // Clear search query. 240 model_->search_box()->SetText(base::string16()); 241 } 242 243 void AppListViewDelegate::SetUpSearchUI() { 244 app_list::StartPageService* start_page_service = 245 app_list::StartPageService::Get(profile_); 246 if (start_page_service) 247 start_page_service->AddObserver(this); 248 249 speech_ui_->SetSpeechRecognitionState(start_page_service 250 ? start_page_service->state() 251 : app_list::SPEECH_RECOGNITION_OFF); 252 253 search_controller_.reset(new app_list::SearchController(profile_, 254 model_->search_box(), 255 model_->results(), 256 speech_ui_.get(), 257 controller_)); 258 } 259 260 void AppListViewDelegate::SetUpProfileSwitcher() { 261 // If a profile change is observed when there is no app list, there is nothing 262 // to update until SetProfile() calls this function again. 263 if (!profile_) 264 return; 265 266 // Don't populate the app list users if we are on the ash desktop. 267 chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow( 268 controller_->GetAppListWindow()); 269 if (desktop == chrome::HOST_DESKTOP_TYPE_ASH) 270 return; 271 272 // Populate the app list users. 273 PopulateUsers(g_browser_process->profile_manager()->GetProfileInfoCache(), 274 profile_->GetPath(), 275 &users_); 276 277 FOR_EACH_OBSERVER( 278 app_list::AppListViewDelegateObserver, observers_, OnProfilesChanged()); 279 } 280 281 void AppListViewDelegate::SetUpCustomLauncherPages() { 282 std::vector<GURL> custom_launcher_page_urls; 283 GetCustomLauncherPageUrls(profile_, &custom_launcher_page_urls); 284 for (std::vector<GURL>::const_iterator it = custom_launcher_page_urls.begin(); 285 it != custom_launcher_page_urls.end(); 286 ++it) { 287 std::string extension_id = it->host(); 288 apps::CustomLauncherPageContents* page_contents = 289 new apps::CustomLauncherPageContents( 290 scoped_ptr<extensions::AppDelegate>( 291 new ChromeAppDelegate(scoped_ptr<ScopedKeepAlive>())), 292 extension_id); 293 page_contents->Initialize(profile_, *it); 294 custom_page_contents_.push_back(page_contents); 295 } 296 } 297 298 void AppListViewDelegate::OnHotwordStateChanged(bool started) { 299 if (started) { 300 if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_READY) { 301 OnSpeechRecognitionStateChanged( 302 app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING); 303 } 304 } else { 305 if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING) 306 OnSpeechRecognitionStateChanged(app_list::SPEECH_RECOGNITION_READY); 307 } 308 } 309 310 void AppListViewDelegate::OnHotwordRecognized() { 311 DCHECK_EQ(app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING, 312 speech_ui_->state()); 313 ToggleSpeechRecognition(); 314 } 315 316 void AppListViewDelegate::SigninManagerCreated(SigninManagerBase* manager) { 317 scoped_observer_.Add(manager); 318 } 319 320 void AppListViewDelegate::SigninManagerShutdown(SigninManagerBase* manager) { 321 if (scoped_observer_.IsObserving(manager)) 322 scoped_observer_.Remove(manager); 323 } 324 325 void AppListViewDelegate::GoogleSigninFailed( 326 const GoogleServiceAuthError& error) { 327 SetUpProfileSwitcher(); 328 } 329 330 void AppListViewDelegate::GoogleSigninSucceeded(const std::string& account_id, 331 const std::string& username, 332 const std::string& password) { 333 SetUpProfileSwitcher(); 334 } 335 336 void AppListViewDelegate::GoogleSignedOut(const std::string& account_id, 337 const std::string& username) { 338 SetUpProfileSwitcher(); 339 } 340 341 void AppListViewDelegate::OnProfileAdded(const base::FilePath& profile_path) { 342 SetUpProfileSwitcher(); 343 } 344 345 void AppListViewDelegate::OnProfileWasRemoved( 346 const base::FilePath& profile_path, 347 const base::string16& profile_name) { 348 SetUpProfileSwitcher(); 349 } 350 351 void AppListViewDelegate::OnProfileNameChanged( 352 const base::FilePath& profile_path, 353 const base::string16& old_profile_name) { 354 SetUpProfileSwitcher(); 355 } 356 357 bool AppListViewDelegate::ForceNativeDesktop() const { 358 return controller_->ForceNativeDesktop(); 359 } 360 361 void AppListViewDelegate::SetProfileByPath(const base::FilePath& profile_path) { 362 DCHECK(model_); 363 // The profile must be loaded before this is called. 364 SetProfile( 365 g_browser_process->profile_manager()->GetProfileByPath(profile_path)); 366 } 367 368 app_list::AppListModel* AppListViewDelegate::GetModel() { 369 return model_; 370 } 371 372 app_list::SpeechUIModel* AppListViewDelegate::GetSpeechUI() { 373 return speech_ui_.get(); 374 } 375 376 void AppListViewDelegate::GetShortcutPathForApp( 377 const std::string& app_id, 378 const base::Callback<void(const base::FilePath&)>& callback) { 379 #if defined(OS_WIN) 380 const extensions::Extension* extension = 381 extensions::ExtensionRegistry::Get(profile_)->GetExtensionById( 382 app_id, extensions::ExtensionRegistry::EVERYTHING); 383 if (!extension) { 384 callback.Run(base::FilePath()); 385 return; 386 } 387 388 base::FilePath app_data_dir( 389 web_app::GetWebAppDataDirectory(profile_->GetPath(), 390 extension->id(), 391 GURL())); 392 393 web_app::GetShortcutInfoForApp( 394 extension, 395 profile_, 396 base::Bind(CreateShortcutInWebAppDir, app_data_dir, callback)); 397 #else 398 callback.Run(base::FilePath()); 399 #endif 400 } 401 402 void AppListViewDelegate::StartSearch() { 403 if (search_controller_) 404 search_controller_->Start(); 405 } 406 407 void AppListViewDelegate::StopSearch() { 408 if (search_controller_) 409 search_controller_->Stop(); 410 } 411 412 void AppListViewDelegate::OpenSearchResult( 413 app_list::SearchResult* result, 414 bool auto_launch, 415 int event_flags) { 416 if (auto_launch) 417 base::RecordAction(base::UserMetricsAction("AppList_AutoLaunched")); 418 search_controller_->OpenResult(result, event_flags); 419 } 420 421 void AppListViewDelegate::InvokeSearchResultAction( 422 app_list::SearchResult* result, 423 int action_index, 424 int event_flags) { 425 search_controller_->InvokeResultAction(result, action_index, event_flags); 426 } 427 428 base::TimeDelta AppListViewDelegate::GetAutoLaunchTimeout() { 429 return auto_launch_timeout_; 430 } 431 432 void AppListViewDelegate::AutoLaunchCanceled() { 433 base::RecordAction(base::UserMetricsAction("AppList_AutoLaunchCanceled")); 434 auto_launch_timeout_ = base::TimeDelta(); 435 } 436 437 void AppListViewDelegate::ViewInitialized() { 438 app_list::StartPageService* service = 439 app_list::StartPageService::Get(profile_); 440 if (service) { 441 service->AppListShown(); 442 if (service->HotwordEnabled()) { 443 HotwordService* hotword_service = 444 HotwordServiceFactory::GetForProfile(profile_); 445 if (hotword_service) 446 hotword_service->RequestHotwordSession(this); 447 } 448 } 449 } 450 451 void AppListViewDelegate::Dismiss() { 452 controller_->DismissView(); 453 } 454 455 void AppListViewDelegate::ViewClosing() { 456 controller_->ViewClosing(); 457 458 if (!profile_) 459 return; 460 461 app_list::StartPageService* service = 462 app_list::StartPageService::Get(profile_); 463 if (service) { 464 service->AppListHidden(); 465 if (service->HotwordEnabled()) { 466 HotwordService* hotword_service = 467 HotwordServiceFactory::GetForProfile(profile_); 468 if (hotword_service) 469 hotword_service->StopHotwordSession(this); 470 } 471 } 472 } 473 474 gfx::ImageSkia AppListViewDelegate::GetWindowIcon() { 475 return controller_->GetWindowIcon(); 476 } 477 478 void AppListViewDelegate::OpenSettings() { 479 const extensions::Extension* extension = 480 extensions::ExtensionRegistry::Get(profile_)->GetExtensionById( 481 extension_misc::kSettingsAppId, 482 extensions::ExtensionRegistry::EVERYTHING); 483 DCHECK(extension); 484 controller_->ActivateApp(profile_, 485 extension, 486 AppListControllerDelegate::LAUNCH_FROM_UNKNOWN, 487 0); 488 } 489 490 void AppListViewDelegate::OpenHelp() { 491 chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow( 492 controller_->GetAppListWindow()); 493 chrome::ScopedTabbedBrowserDisplayer displayer(profile_, desktop); 494 content::OpenURLParams params(GURL(chrome::kAppLauncherHelpURL), 495 content::Referrer(), 496 NEW_FOREGROUND_TAB, 497 ui::PAGE_TRANSITION_LINK, 498 false); 499 displayer.browser()->OpenURL(params); 500 } 501 502 void AppListViewDelegate::OpenFeedback() { 503 chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow( 504 controller_->GetAppListWindow()); 505 Browser* browser = chrome::FindTabbedBrowser(profile_, false, desktop); 506 chrome::ShowFeedbackPage(browser, std::string(), 507 chrome::kAppLauncherCategoryTag); 508 } 509 510 void AppListViewDelegate::ToggleSpeechRecognition() { 511 app_list::StartPageService* service = 512 app_list::StartPageService::Get(profile_); 513 if (service) 514 service->ToggleSpeechRecognition(); 515 } 516 517 void AppListViewDelegate::ShowForProfileByPath( 518 const base::FilePath& profile_path) { 519 controller_->ShowForProfileByPath(profile_path); 520 } 521 522 void AppListViewDelegate::OnSpeechResult(const base::string16& result, 523 bool is_final) { 524 speech_ui_->SetSpeechResult(result, is_final); 525 if (is_final) { 526 auto_launch_timeout_ = base::TimeDelta::FromMilliseconds( 527 kAutoLaunchDefaultTimeoutMilliSec); 528 model_->search_box()->SetText(result); 529 } 530 } 531 532 void AppListViewDelegate::OnSpeechSoundLevelChanged(int16 level) { 533 speech_ui_->UpdateSoundLevel(level); 534 } 535 536 void AppListViewDelegate::OnSpeechRecognitionStateChanged( 537 app_list::SpeechRecognitionState new_state) { 538 speech_ui_->SetSpeechRecognitionState(new_state); 539 540 app_list::StartPageService* service = 541 app_list::StartPageService::Get(profile_); 542 // With the new hotword extension, we need to re-request hotwording after 543 // speech recognition has stopped. 544 if (new_state == app_list::SPEECH_RECOGNITION_READY && 545 HotwordService::IsExperimentalHotwordingEnabled() && 546 service && service->HotwordEnabled()) { 547 HotwordService* hotword_service = 548 HotwordServiceFactory::GetForProfile(profile_); 549 if (hotword_service) { 550 hotword_service->RequestHotwordSession(this); 551 } 552 } 553 } 554 555 #if defined(TOOLKIT_VIEWS) 556 views::View* AppListViewDelegate::CreateStartPageWebView( 557 const gfx::Size& size) { 558 app_list::StartPageService* service = 559 app_list::StartPageService::Get(profile_); 560 if (!service) 561 return NULL; 562 563 content::WebContents* web_contents = service->GetStartPageContents(); 564 if (!web_contents) 565 return NULL; 566 567 DCHECK_EQ(profile_, web_contents->GetBrowserContext()); 568 views::WebView* web_view = new views::WebView( 569 web_contents->GetBrowserContext()); 570 web_view->SetPreferredSize(size); 571 web_view->SetWebContents(web_contents); 572 return web_view; 573 } 574 575 std::vector<views::View*> AppListViewDelegate::CreateCustomPageWebViews( 576 const gfx::Size& size) { 577 std::vector<views::View*> web_views; 578 579 for (ScopedVector<apps::CustomLauncherPageContents>::const_iterator it = 580 custom_page_contents_.begin(); 581 it != custom_page_contents_.end(); 582 ++it) { 583 content::WebContents* web_contents = (*it)->web_contents(); 584 // TODO(mgiuca): DCHECK_EQ(profile_, web_contents->GetBrowserContext()) 585 // after http://crbug.com/392763 resolved. 586 views::WebView* web_view = 587 new views::WebView(web_contents->GetBrowserContext()); 588 web_view->SetPreferredSize(size); 589 web_view->SetWebContents(web_contents); 590 web_views.push_back(web_view); 591 } 592 593 return web_views; 594 } 595 #endif 596 597 bool AppListViewDelegate::IsSpeechRecognitionEnabled() { 598 app_list::StartPageService* service = 599 app_list::StartPageService::Get(profile_); 600 return service && service->GetSpeechRecognitionContents(); 601 } 602 603 const app_list::AppListViewDelegate::Users& 604 AppListViewDelegate::GetUsers() const { 605 return users_; 606 } 607 608 bool AppListViewDelegate::ShouldCenterWindow() const { 609 if (app_list::switches::IsCenteredAppListEnabled()) 610 return true; 611 612 // keyboard depends upon Aura. 613 #if defined(USE_AURA) 614 // If the virtual keyboard is enabled, use the new app list position. The old 615 // position is too tall, and doesn't fit in the left-over screen space. 616 if (keyboard::IsKeyboardEnabled()) 617 return true; 618 #endif 619 620 #if defined(USE_ASH) 621 // If it is at all possible to enter maximize mode in this configuration 622 // (which has a virtual keyboard), we should use the experimental position. 623 // This avoids having the app list change shape and position as the user 624 // enters and exits maximize mode. 625 if (ash::Shell::HasInstance() && 626 ash::Shell::GetInstance() 627 ->maximize_mode_controller() 628 ->CanEnterMaximizeMode()) { 629 return true; 630 } 631 #endif 632 633 return false; 634 } 635 636 void AppListViewDelegate::AddObserver( 637 app_list::AppListViewDelegateObserver* observer) { 638 observers_.AddObserver(observer); 639 } 640 641 void AppListViewDelegate::RemoveObserver( 642 app_list::AppListViewDelegateObserver* observer) { 643 observers_.RemoveObserver(observer); 644 } 645 646 void AppListViewDelegate::Observe(int type, 647 const content::NotificationSource& source, 648 const content::NotificationDetails& details) { 649 switch (type) { 650 case chrome::NOTIFICATION_APP_TERMINATING: 651 FOR_EACH_OBSERVER( 652 app_list::AppListViewDelegateObserver, observers_, OnShutdown()); 653 654 SetProfile(NULL); // Ensures launcher page web contents are torn down. 655 656 // SigninManagerFactory is not a leaky singleton (unlike this class), and 657 // its destructor will check that it has no remaining observers. 658 scoped_observer_.RemoveAll(); 659 SigninManagerFactory::GetInstance()->RemoveObserver(this); 660 break; 661 default: 662 NOTREACHED(); 663 } 664 } 665