diff options
author | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-21 19:57:06 +0000 |
---|---|---|
committer | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-21 19:57:06 +0000 |
commit | 32c440643c4676ed450353c1811588bbd59415bb (patch) | |
tree | 9078c683d56b9e3ffd427af2abbe00e94d5bfea3 | |
parent | 500d24e7c84a4bb55315dcb4dd612e3d3800b379 (diff) | |
download | chromium_src-32c440643c4676ed450353c1811588bbd59415bb.zip chromium_src-32c440643c4676ed450353c1811588bbd59415bb.tar.gz chromium_src-32c440643c4676ed450353c1811588bbd59415bb.tar.bz2 |
Revert 218674 "Remove PerBrowser launcher"
> Remove PerBrowser launcher
>
> Remove per-browser.* and related tests.
> Remove IsPerAppLauncher() interface in LauncherDelegate.
> Remove ash-disable-per-app-launcher switch.
> Change class name from ChromeLauncherControllerPerApp to ChromeLauncherController.
>
> In the next CL, BrowserLauncherItemController will be replaced with new browser status monitor only for monitoring browser and tab status.
>
> R=skuhne@chromium.org,jamescook@chromium.org
> BUG=169303
> TEST=unit_tests, browser_tests
>
> Review URL: https://chromiumcodereview.appspot.com/22887015
TBR=simon.hong81@gmail.com
Review URL: https://codereview.chromium.org/22903020
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@218808 0039d316-1c4b-4281-b951-d872f2087c98
38 files changed, 7185 insertions, 3152 deletions
diff --git a/ash/ash_switches.cc b/ash/ash_switches.cc index c046bd1..acb106f 100644 --- a/ash/ash_switches.cc +++ b/ash/ash_switches.cc @@ -66,6 +66,9 @@ const char kAshDisableDisplayChangeLimiter[] = // If present new lock animations are enabled. const char kAshDisableNewLockAnimations[] = "ash-disable-new-lock-animations"; +// Disable the per application grouping version of the launcher. +const char kAshDisablePerAppLauncher[] = "ash-disable-per-app-launcher"; + // Disable immersive fullscreen mode, regardless of default setting. const char kAshDisableImmersiveFullscreen[] = "ash-disable-immersive-fullscreen"; diff --git a/ash/ash_switches.h b/ash/ash_switches.h index b86d3ff..5fe137c 100644 --- a/ash/ash_switches.h +++ b/ash/ash_switches.h @@ -34,6 +34,7 @@ ASH_EXPORT extern const char kAshDisableAutoWindowPlacement[]; ASH_EXPORT extern const char kAshDisableDisplayChangeLimiter[]; ASH_EXPORT extern const char kAshDisableImmersiveFullscreen[]; ASH_EXPORT extern const char kAshDisableNewLockAnimations[]; +ASH_EXPORT extern const char kAshDisablePerAppLauncher[]; ASH_EXPORT extern const char kAshDisableDragAndDropAppListToLauncher[]; #if defined(OS_CHROMEOS) ASH_EXPORT extern const char kAshDisableSoftwareMirroring[]; diff --git a/ash/launcher/launcher.cc b/ash/launcher/launcher.cc index 008646de..5bd1262 100644 --- a/ash/launcher/launcher.cc +++ b/ash/launcher/launcher.cc @@ -169,7 +169,10 @@ void Launcher::LaunchAppIndexAt(int item_index) { // There are two ways how found_index can be valid: a.) the nth item was // found (which is true when indexes_left is -1) or b.) the last item was // requested (which is true when index was passed in as a negative number). - if (found_index >= 0 && (indexes_left == -1 || item_index < 0)) { + if (found_index >= 0 && (indexes_left == -1 || item_index < 0) && + (delegate_->IsPerAppLauncher() || + (items[found_index].status == ash::STATUS_RUNNING || + items[found_index].status == ash::STATUS_CLOSED))) { // Then set this one as active (or advance to the next item of its kind). ActivateLauncherItem(found_index); } diff --git a/ash/launcher/launcher.h b/ash/launcher/launcher.h index c3acf44..6781058 100644 --- a/ash/launcher/launcher.h +++ b/ash/launcher/launcher.h @@ -101,6 +101,8 @@ class ASH_EXPORT Launcher { // TODO(sky): remove this! internal::LauncherView* GetLauncherViewForTest(); + LauncherDelegate* delegate() { return delegate_; } + ShelfWidget* shelf_widget() { return shelf_widget_; } // Set the bounds of the launcher view. diff --git a/ash/launcher/launcher_delegate.h b/ash/launcher/launcher_delegate.h index 322df38..f835aac 100644 --- a/ash/launcher/launcher_delegate.h +++ b/ash/launcher/launcher_delegate.h @@ -46,7 +46,7 @@ class ASH_EXPORT LauncherDelegate { // and has an instance of |views::View| as the event target // but not |aura::Window|. If the |event| is of type KeyEvent, it is assumed // that this was triggered by keyboard action (Alt+<number>) and special - // handling might happen. + // handling might happen (PerApp launcher). virtual void ItemSelected(const LauncherItem& item, const ui::Event& event) = 0; @@ -92,6 +92,9 @@ class ASH_EXPORT LauncherDelegate { // exists. virtual void OnLauncherDestroyed(Launcher* launcher) = 0; + // True if the running launcher is the per application launcher. + virtual bool IsPerAppLauncher() = 0; + // Get the launcher ID from an application ID. virtual LauncherID GetLauncherIDForAppID(const std::string& app_id) = 0; diff --git a/ash/shell/launcher_delegate_impl.cc b/ash/shell/launcher_delegate_impl.cc index ac0aeee..51f6171 100644 --- a/ash/shell/launcher_delegate_impl.cc +++ b/ash/shell/launcher_delegate_impl.cc @@ -64,6 +64,10 @@ void LauncherDelegateImpl::OnLauncherCreated(Launcher* launcher) { void LauncherDelegateImpl::OnLauncherDestroyed(Launcher* launcher) { } +bool LauncherDelegateImpl::IsPerAppLauncher() { + return false; +} + LauncherID LauncherDelegateImpl::GetLauncherIDForAppID( const std::string& app_id) { return 0; diff --git a/ash/shell/launcher_delegate_impl.h b/ash/shell/launcher_delegate_impl.h index 86a3579..4788029 100644 --- a/ash/shell/launcher_delegate_impl.h +++ b/ash/shell/launcher_delegate_impl.h @@ -39,6 +39,7 @@ class LauncherDelegateImpl : public ash::LauncherDelegate { virtual bool ShouldShowTooltip(const LauncherItem& item) OVERRIDE; virtual void OnLauncherCreated(Launcher* launcher) OVERRIDE; virtual void OnLauncherDestroyed(Launcher* launcher) OVERRIDE; + virtual bool IsPerAppLauncher() OVERRIDE; virtual LauncherID GetLauncherIDForAppID(const std::string& app_id) OVERRIDE; virtual void PinAppWithID(const std::string& app_id) OVERRIDE; virtual bool IsAppPinned(const std::string& app_id) OVERRIDE; diff --git a/ash/test/test_launcher_delegate.cc b/ash/test/test_launcher_delegate.cc index ed3e517..0830d32 100644 --- a/ash/test/test_launcher_delegate.cc +++ b/ash/test/test_launcher_delegate.cc @@ -126,6 +126,10 @@ void TestLauncherDelegate::OnLauncherCreated(Launcher* launcher) { void TestLauncherDelegate::OnLauncherDestroyed(Launcher* launcher) { } +bool TestLauncherDelegate::IsPerAppLauncher() { + return true; +} + LauncherID TestLauncherDelegate::GetLauncherIDForAppID( const std::string& app_id) { return 0; diff --git a/ash/test/test_launcher_delegate.h b/ash/test/test_launcher_delegate.h index 06d0cde..6214b8d 100644 --- a/ash/test/test_launcher_delegate.h +++ b/ash/test/test_launcher_delegate.h @@ -51,6 +51,7 @@ class TestLauncherDelegate : public LauncherDelegate, virtual bool ShouldShowTooltip(const LauncherItem& item) OVERRIDE; virtual void OnLauncherCreated(Launcher* launcher) OVERRIDE; virtual void OnLauncherDestroyed(Launcher* launcher) OVERRIDE; + virtual bool IsPerAppLauncher() OVERRIDE; virtual LauncherID GetLauncherIDForAppID(const std::string& app_id) OVERRIDE; virtual void PinAppWithID(const std::string& app_id) OVERRIDE; virtual bool IsAppPinned(const std::string& app_id) OVERRIDE; diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index f7a3caa..ab09e5a 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -5971,6 +5971,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_ASH_AUTO_MAXIMIZING_DESCRIPTION" desc="Description for the option to disable the auto window maximizing functionality."> Disable automatic window maximization for browser / app windows if they are started the first time. </message> + <message name="IDS_FLAGS_ASH_DISABLE_PER_APP_LAUNCHER_NAME" desc="Name for the option to enable/disable the per application sorting launcher functionality."> + Disable per application sorting in the launcher. + </message> + <message name="IDS_FLAGS_ASH_DISABLE_PER_APP_LAUNCHER_DESCRIPTION" desc="Description for the option to enable/disable the per application sorting launcher functionality."> + Disable the per application sorting mode of the launcher. + </message> <message name="IDS_FLAGS_DISABLE_GESTURE_REQUIREMENT_FOR_MEDIA_PLAYBACK_NAME" desc="Title for the flag to disable gesture requiment for media playback"> Disable gesture requirement for media playback. </message> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 0334972..399d30a 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -783,6 +783,13 @@ const Experiment kExperiments[] = { kOsWin | kOsLinux | kOsCrOS, SINGLE_VALUE_TYPE(ash::switches::kAshDisableAutoWindowPlacement) }, + { + "ash-disable-per-app-launcher", + IDS_FLAGS_ASH_DISABLE_PER_APP_LAUNCHER_NAME, + IDS_FLAGS_ASH_DISABLE_PER_APP_LAUNCHER_DESCRIPTION, + kOsWin | kOsLinux | kOsCrOS, + SINGLE_VALUE_TYPE(ash::switches::kAshDisablePerAppLauncher) + }, #endif { "per-tile-painting", diff --git a/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc index bdb625f..7cf4554 100644 --- a/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc +++ b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.cc @@ -12,6 +12,7 @@ #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" @@ -39,9 +40,9 @@ const int kClickSuppressionInMS = 1000; // item with the appropriate LauncherItemController type). AppShortcutLauncherItemController::AppShortcutLauncherItemController( const std::string& app_id, - ChromeLauncherController* controller) + ChromeLauncherControllerPerApp* controller) : LauncherItemController(TYPE_SHORTCUT, app_id, controller), - chrome_launcher_controller_(controller) { + app_controller_(controller) { // To detect V1 applications we use their domain and match them against the // used URL. This will also work with applications like Google Drive. const Extension* extension = @@ -67,7 +68,7 @@ bool AppShortcutLauncherItemController::IsCurrentlyShownInWindow( browser ? browser->tab_strip_model()->GetActiveWebContents() : NULL; std::vector<content::WebContents*> content = - chrome_launcher_controller_->GetV1ApplicationsFromAppId(app_id()); + app_controller_->GetV1ApplicationsFromAppId(app_id()); std::vector<content::WebContents*>::const_iterator iter = std::find(content.begin(), content.end(), active_content_of_window); @@ -76,14 +77,13 @@ bool AppShortcutLauncherItemController::IsCurrentlyShownInWindow( } bool AppShortcutLauncherItemController::IsOpen() const { - return !chrome_launcher_controller_-> - GetV1ApplicationsFromAppId(app_id()).empty(); + return !app_controller_->GetV1ApplicationsFromAppId(app_id()).empty(); } bool AppShortcutLauncherItemController::IsVisible() const { // Return true if any browser window associated with the app is visible. std::vector<content::WebContents*> content = - chrome_launcher_controller_->GetV1ApplicationsFromAppId(app_id()); + app_controller_->GetV1ApplicationsFromAppId(app_id()); for (size_t i = 0; i < content.size(); i++) { Browser* browser = chrome::FindBrowserWithWebContents(content[i]); if (browser && browser->window()->GetNativeWindow()->IsVisible()) @@ -93,7 +93,7 @@ bool AppShortcutLauncherItemController::IsVisible() const { } void AppShortcutLauncherItemController::Launch(int event_flags) { - launcher_controller()->LaunchApp(app_id(), event_flags); + app_controller_->LaunchApp(app_id(), event_flags); } void AppShortcutLauncherItemController::Activate() { @@ -118,7 +118,7 @@ void AppShortcutLauncherItemController::Activate() { void AppShortcutLauncherItemController::Close() { // Close all running 'programs' of this type. std::vector<content::WebContents*> content = - launcher_controller()->GetV1ApplicationsFromAppId(app_id()); + app_controller_->GetV1ApplicationsFromAppId(app_id()); for (size_t i = 0; i < content.size(); i++) { Browser* browser = chrome::FindBrowserWithWebContents(content[i]); if (!browser) @@ -161,8 +161,8 @@ AppShortcutLauncherItemController::GetApplicationList(int event_flags) { for (size_t i = 0; i < content_list.size(); i++) { content::WebContents* web_contents = content_list[i]; // Get the icon. - gfx::Image app_icon = launcher_controller()->GetAppListIcon(web_contents); - string16 title = launcher_controller()->GetAppListTitle(web_contents); + gfx::Image app_icon = app_controller_->GetAppListIcon(web_contents); + string16 title = app_controller_->GetAppListTitle(web_contents); items.push_back(new ChromeLauncherAppMenuItemTab( title, &app_icon, web_contents, i == 0)); } @@ -265,8 +265,8 @@ bool AppShortcutLauncherItemController::WebContentMatchesApp( refocus_pattern.MatchesURL(tab_url)) || (extension->OverlapsWithOrigin(tab_url) && extension->web_extent().MatchesURL(tab_url)) || - launcher_controller()->IsWebContentHandledByApplication(web_contents, - app_id())); + launcher_controller()->GetPerAppInterface()-> + IsWebContentHandledByApplication(web_contents, app_id())); } void AppShortcutLauncherItemController::ActivateContent( @@ -279,8 +279,7 @@ void AppShortcutLauncherItemController::ActivateContent( int old_index = tab_strip->active_index(); if (index != old_index) tab_strip->ActivateTabAt(index, false); - launcher_controller()->ActivateWindowOrMinimizeIfActive( - browser->window(), + app_controller_->ActivateWindowOrMinimizeIfActive(browser->window(), index == old_index && GetRunningApplications().size() == 1); } @@ -313,8 +312,7 @@ bool AppShortcutLauncherItemController::AdvanceToNextApp() { } bool AppShortcutLauncherItemController::IsV2App() { - const Extension* extension = - launcher_controller()->GetExtensionForAppID(app_id()); + const Extension* extension = app_controller_->GetExtensionForAppID(app_id()); return extension && extension->is_platform_app(); } diff --git a/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h index bf8ab68..bae7e3c 100644 --- a/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h +++ b/chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h @@ -8,10 +8,8 @@ #include <string> #include "base/time/time.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" -#include "url/gurl.h" - -class URLPattern; namespace aura { class Window; @@ -29,7 +27,7 @@ class ChromeLauncherController; class AppShortcutLauncherItemController : public LauncherItemController { public: AppShortcutLauncherItemController(const std::string& app_id, - ChromeLauncherController* controller); + ChromeLauncherControllerPerApp* controller); virtual ~AppShortcutLauncherItemController(); @@ -81,13 +79,12 @@ class AppShortcutLauncherItemController : public LauncherItemController { bool AllowNextLaunchAttempt(); GURL refocus_url_; + ChromeLauncherControllerPerApp* app_controller_; // Since V2 applications can be undetectable after launching, this timer is // keeping track of the last launch attempt. base::Time last_launch_attempt_; - ChromeLauncherController* chrome_launcher_controller_; - DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController); }; diff --git a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc index 04668bb..a2c958e 100644 --- a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc +++ b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller.cc @@ -63,11 +63,17 @@ void BrowserLauncherItemController::Init() { ash::LauncherItemStatus app_status = ash::wm::IsActiveWindow(window_) ? ash::STATUS_ACTIVE : ash::STATUS_RUNNING; - if (type() != TYPE_TABBED && type() != TYPE_WINDOWED_APP) + if (type() != TYPE_TABBED && type() != TYPE_WINDOWED_APP) { launcher_controller()->CreateAppLauncherItem(this, app_id(), app_status); - else if (type() == TYPE_WINDOWED_APP) - launcher_controller()->LockV1AppWithID(LauncherItemController::app_id()); - + } else { + launcher_controller()->CreateTabbedLauncherItem( + this, + is_incognito_ ? ChromeLauncherController::STATE_INCOGNITO : + ChromeLauncherController::STATE_NOT_INCOGNITO, + app_status); + if (type() == TYPE_WINDOWED_APP) + launcher_controller()->LockV1AppWithID(LauncherItemController::app_id()); + } // In testing scenarios we can get tab strips with no active contents. if (tab_model_->active_index() != TabStripModel::kNoTab) UpdateLauncher(tab_model_->GetActiveWebContents()); @@ -86,7 +92,8 @@ BrowserLauncherItemController* BrowserLauncherItemController::Create( type = TYPE_TABBED; if (!browser->is_type_tabbed() && browser->is_type_popup() && - browser->is_app()) { + browser->is_app() && + ChromeLauncherController::instance()->GetPerAppInterface()) { app_id = web_app::GetExtensionIdFromApplicationName( browser->app_name()); // Only allow this for known applications. Some unit tests for example @@ -173,6 +180,11 @@ void BrowserLauncherItemController::OnRemoved() { void BrowserLauncherItemController::LauncherItemChanged( int index, const ash::LauncherItem& old_item) { + if (!launcher_controller()->GetPerAppInterface() && + launcher_model()->items()[index].status == ash::STATUS_ACTIVE && + old_item.status == ash::STATUS_RUNNING) { + Activate(); + } } ChromeLauncherAppMenuItems @@ -189,8 +201,9 @@ void BrowserLauncherItemController::ActiveTabChanged( int reason) { // Update immediately on a tab change. if (old_contents && - TabStripModel::kNoTab != - tab_model_->GetIndexOfWebContents(old_contents)) + (!launcher_controller()->GetPerAppInterface() || + TabStripModel::kNoTab != + tab_model_->GetIndexOfWebContents(old_contents))) UpdateAppState(old_contents); UpdateAppState(new_contents); UpdateLauncher(new_contents); @@ -244,15 +257,60 @@ void BrowserLauncherItemController::OnWindowPropertyChanged( } void BrowserLauncherItemController::UpdateItemStatus() { + if (launcher_controller()->GetPerAppInterface()) + return; + + ash::LauncherItemStatus status; + if (ash::wm::IsActiveWindow(window_)) { + // Clear attention state if active. + if (window_->GetProperty(aura::client::kDrawAttentionKey)) + window_->SetProperty(aura::client::kDrawAttentionKey, false); + status = ash::STATUS_ACTIVE; + } else if (window_->GetProperty(aura::client::kDrawAttentionKey)) { + status = ash::STATUS_ATTENTION; + } else { + status = ash::STATUS_RUNNING; + } + launcher_controller()->SetItemStatus(launcher_id(), status); } void BrowserLauncherItemController::UpdateLauncher(content::WebContents* tab) { + if (launcher_controller()->GetPerAppInterface()) + return; + + if (type() == TYPE_APP_PANEL) + return; // Maintained entirely by ChromeLauncherController. + + if (!tab) + return; // Assume the window is going to be closed if there are no tabs. + + int item_index = launcher_model()->ItemIndexByID(launcher_id()); + if (item_index == -1) + return; + + ash::LauncherItem item = launcher_model()->items()[item_index]; + DCHECK_EQ(TYPE_TABBED, type()); + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + FaviconTabHelper* favicon_tab_helper = + FaviconTabHelper::FromWebContents(tab); + if (favicon_tab_helper->ShouldDisplayFavicon()) { + item.image = favicon_tab_helper->GetFavicon().AsImageSkia(); + if (item.image.isNull()) { + item.image = *rb.GetImageSkiaNamed(IDR_DEFAULT_FAVICON); + } + } else { + item.image = *rb.GetImageSkiaNamed(IDR_DEFAULT_FAVICON); + } + launcher_model()->Set(item_index, item); } void BrowserLauncherItemController::UpdateAppState(content::WebContents* tab) { ChromeLauncherController::AppState app_state; - if (tab_model_->GetActiveWebContents() == tab) { + if (!launcher_controller()->GetPerAppInterface() && + tab_model_->GetIndexOfWebContents(tab) == TabStripModel::kNoTab) { + app_state = ChromeLauncherController::APP_STATE_REMOVED; + } else if (tab_model_->GetActiveWebContents() == tab) { if (ash::wm::IsActiveWindow(window_)) app_state = ChromeLauncherController::APP_STATE_WINDOW_ACTIVE; else diff --git a/chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc new file mode 100644 index 0000000..0f20bc2 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc @@ -0,0 +1,543 @@ +// 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/ash/launcher/browser_launcher_item_controller.h" + +#include <map> +#include <string> + +#include "ash/ash_switches.h" +#include "ash/launcher/launcher_model.h" +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/favicon/favicon_tab_helper.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/browser/web_contents.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/aura/client/activation_change_observer.h" +#include "ui/aura/client/activation_delegate.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/test_activation_client.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/base/events/event.h" + +// TODO(skuhne): Remove this module together with the +// browser_launcher_item_controller.* when the old launcher goes away. + +namespace { + +// Test implementation of AppTabHelper. +class AppTabHelperImpl : public ChromeLauncherController::AppTabHelper { + public: + AppTabHelperImpl() {} + virtual ~AppTabHelperImpl() {} + + // Sets the id for the specified tab. The id is removed if Remove() is + // invoked. + void SetAppID(content::WebContents* tab, const std::string& id) { + tab_id_map_[tab] = id; + } + + // Returns true if there is an id registered for |tab|. + bool HasAppID(content::WebContents* tab) const { + return tab_id_map_.find(tab) != tab_id_map_.end(); + } + + // AppTabHelper implementation: + virtual std::string GetAppID(content::WebContents* tab) OVERRIDE { + return tab_id_map_.find(tab) != tab_id_map_.end() ? tab_id_map_[tab] : + std::string(); + } + + virtual bool IsValidID(const std::string& id) OVERRIDE { + for (TabToStringMap::const_iterator i = tab_id_map_.begin(); + i != tab_id_map_.end(); ++i) { + if (i->second == id) + return true; + } + return false; + } + + private: + typedef std::map<content::WebContents*, std::string> TabToStringMap; + + TabToStringMap tab_id_map_; + + DISALLOW_COPY_AND_ASSIGN(AppTabHelperImpl); +}; + +// Test implementation of AppIconLoader. +class AppIconLoaderImpl : public extensions::AppIconLoader { + public: + AppIconLoaderImpl() : fetch_count_(0) {} + virtual ~AppIconLoaderImpl() {} + + // Returns the number of times FetchImage() has been invoked and resets the + // count to 0. + int GetAndClearFetchCount() { + int value = fetch_count_; + fetch_count_ = 0; + return value; + } + + // AppIconLoader implementation: + virtual void FetchImage(const std::string& id) OVERRIDE { + fetch_count_++; + } + virtual void ClearImage(const std::string& id) OVERRIDE { + } + virtual void UpdateImage(const std::string& id) OVERRIDE { + } + + private: + int fetch_count_; + + DISALLOW_COPY_AND_ASSIGN(AppIconLoaderImpl); +}; + +// Test implementation of TabStripModelDelegate. +class TabHelperTabStripModelDelegate : public TestTabStripModelDelegate { + public: + TabHelperTabStripModelDelegate() {} + virtual ~TabHelperTabStripModelDelegate() {} + + virtual void WillAddWebContents(content::WebContents* contents) OVERRIDE { + // BrowserLauncherItemController assumes that all WebContents passed to it + // have attached an extensions::TabHelper and a FaviconTabHelper. The + // TestTabStripModelDelegate adds an extensions::TabHelper. + TestTabStripModelDelegate::WillAddWebContents(contents); + FaviconTabHelper::CreateForWebContents(contents); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TabHelperTabStripModelDelegate); +}; + +} // namespace + +// TODO(skuhne): Several of these unit tests need to be moved into a new home +// when the old launcher & the browser launcher item controller are removed +// (several of these tests are not testing the BrowserLauncherItemController - +// but the LauncherController framework). +class LauncherItemControllerPerAppTest + : public ChromeRenderViewHostTestHarness { + public: + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + + activation_client_.reset( + new aura::test::TestActivationClient(root_window())); + launcher_model_.reset(new ash::LauncherModel); + launcher_delegate_.reset( + ChromeLauncherController::CreateInstance(profile(), + launcher_model_.get())); + app_tab_helper_ = new AppTabHelperImpl; + app_icon_loader_ = new AppIconLoaderImpl; + launcher_delegate_->SetAppTabHelperForTest(app_tab_helper_); + launcher_delegate_->SetAppIconLoaderForTest(app_icon_loader_); + launcher_delegate_->Init(); + } + + virtual void TearDown() OVERRIDE { + launcher_delegate_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); + } + + protected: + // Contains all the objects needed to create a BrowserLauncherItemController. + struct State : public aura::client::ActivationDelegate, + public aura::client::ActivationChangeObserver { + public: + State(LauncherItemControllerPerAppTest* test, + const std::string& app_id, + BrowserLauncherItemController::Type launcher_type) + : launcher_test(test), + window(NULL), + tab_strip(&tab_strip_delegate, test->profile()), + updater(launcher_type, + &window, + &tab_strip, + test->launcher_delegate_.get(), + app_id) { + window.Init(ui::LAYER_NOT_DRAWN); + launcher_test->root_window()->AddChild(&window); + launcher_test->activation_client_->ActivateWindow(&window); + aura::client::SetActivationDelegate(&window, this); + aura::client::SetActivationChangeObserver(&window, this); + updater.Init(); + } + + ash::LauncherItem GetUpdaterItem() { + ash::LauncherID launcher_id = + BrowserLauncherItemController::TestApi(&updater).item_id(); + int index = launcher_test->launcher_model_->ItemIndexByID(launcher_id); + return launcher_test->launcher_model_->items()[index]; + } + + // aura::client::ActivationDelegate overrides. + virtual bool ShouldActivate() const OVERRIDE { + return true; + } + + // aura::client::ActivationChangeObserver overrides: + virtual void OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) OVERRIDE { + DCHECK(&window == gained_active || &window == lost_active); + updater.BrowserActivationStateChanged(); + } + + LauncherItemControllerPerAppTest* launcher_test; + aura::Window window; + TabHelperTabStripModelDelegate tab_strip_delegate; + TabStripModel tab_strip; + BrowserLauncherItemController updater; + + private: + DISALLOW_COPY_AND_ASSIGN(State); + }; + + const std::string& GetAppID(ash::LauncherID id) const { + return launcher_delegate_->GetAppIdFromLauncherIdForTest(id); + } + + void ResetAppTabHelper() { + launcher_delegate_->SetAppTabHelperForTest(app_tab_helper_); + } + + void ResetAppIconLoader() { + launcher_delegate_->SetAppIconLoaderForTest(app_icon_loader_); + } + + void UnpinAppsWithID(const std::string& app_id) { + launcher_delegate_->UnpinAppsWithID(app_id); + } + + const ash::LauncherItem& GetItem(BrowserLauncherItemController* updater) { + int index = launcher_model_->ItemIndexByID( + BrowserLauncherItemController::TestApi(updater).item_id()); + return launcher_model_->items()[index]; + } + + scoped_ptr<ash::LauncherModel> launcher_model_; + scoped_ptr<ChromeLauncherController> launcher_delegate_; + + // Owned by BrowserLauncherItemController. + AppTabHelperImpl* app_tab_helper_; + AppIconLoaderImpl* app_icon_loader_; + + scoped_ptr<aura::test::TestActivationClient> activation_client_; +}; + +// Verify that the launcher item positions are persisted and restored. +TEST_F(LauncherItemControllerPerAppTest, PersistLauncherItemPositions) { + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, + launcher_model_->items()[0].type); + EXPECT_EQ(ash::TYPE_APP_LIST, + launcher_model_->items()[1].type); + scoped_ptr<content::WebContents> tab1(CreateTestWebContents()); + scoped_ptr<content::WebContents> tab2(CreateTestWebContents()); + app_tab_helper_->SetAppID(tab1.get(), "1"); + app_tab_helper_->SetAppID(tab1.get(), "2"); + + EXPECT_FALSE(launcher_delegate_->IsAppPinned("1")); + launcher_delegate_->PinAppWithID("1"); + EXPECT_TRUE(launcher_delegate_->IsAppPinned("1")); + launcher_delegate_->PinAppWithID("2"); + + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, + launcher_model_->items()[0].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[1].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[2].type); + EXPECT_EQ(ash::TYPE_APP_LIST, + launcher_model_->items()[3].type); + + launcher_model_->Move(0, 2); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[0].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[1].type); + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, + launcher_model_->items()[2].type); + EXPECT_EQ(ash::TYPE_APP_LIST, + launcher_model_->items()[3].type); + + launcher_delegate_.reset(); + launcher_model_.reset(new ash::LauncherModel); + launcher_delegate_.reset( + ChromeLauncherController::CreateInstance(profile(), + launcher_model_.get())); + app_tab_helper_ = new AppTabHelperImpl; + app_tab_helper_->SetAppID(tab1.get(), "1"); + app_tab_helper_->SetAppID(tab2.get(), "2"); + ResetAppTabHelper(); + + launcher_delegate_->Init(); + + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[0].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[1].type); + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, + launcher_model_->items()[2].type); + EXPECT_EQ(ash::TYPE_APP_LIST, + launcher_model_->items()[3].type); +} + +class BrowserLauncherItemControllerTest + : public LauncherItemControllerPerAppTest { + public: + BrowserLauncherItemControllerTest() {} + + virtual void SetUp() OVERRIDE { + CommandLine::ForCurrentProcess()->AppendSwitch( + ash::switches::kAshDisablePerAppLauncher); + + LauncherItemControllerPerAppTest::SetUp(); + } + + virtual void TearDown() OVERRIDE { + LauncherItemControllerPerAppTest::TearDown(); + } +}; + +// Verifies a new launcher item is added for TYPE_TABBED. +TEST_F(BrowserLauncherItemControllerTest, TabbedSetup) { + size_t initial_size = launcher_model_->items().size(); + { + scoped_ptr<content::WebContents> web_contents(CreateTestWebContents()); + State state(this, std::string(), + BrowserLauncherItemController::TYPE_TABBED); + + // There should be one more item. + ASSERT_EQ(initial_size + 1, launcher_model_->items().size()); + // New item should be added at the end. + EXPECT_EQ(ash::TYPE_TABBED, state.GetUpdaterItem().type); + } + + // Deleting the BrowserLauncherItemController should have removed the item. + ASSERT_EQ(initial_size, launcher_model_->items().size()); + + // Do the same, but this time add the tab first. + { + scoped_ptr<content::WebContents> web_contents(CreateTestWebContents()); + + TabHelperTabStripModelDelegate tab_strip_delegate; + TabStripModel tab_strip(&tab_strip_delegate, profile()); + tab_strip.InsertWebContentsAt(0, + web_contents.get(), + TabStripModel::ADD_ACTIVE); + aura::Window window(NULL); + window.Init(ui::LAYER_NOT_DRAWN); + root_window()->AddChild(&window); + BrowserLauncherItemController updater( + LauncherItemController::TYPE_TABBED, + &window, &tab_strip, launcher_delegate_.get(), + std::string()); + updater.Init(); + + // There should be one more item. + ASSERT_EQ(initial_size + 1, launcher_model_->items().size()); + // New item should be added at the end. + EXPECT_EQ(ash::TYPE_TABBED, GetItem(&updater).type); + } +} + +// Verifies pinned apps are persisted and restored. +TEST_F(BrowserLauncherItemControllerTest, PersistPinned) { + size_t initial_size = launcher_model_->items().size(); + scoped_ptr<content::WebContents> tab1(CreateTestWebContents()); + + app_tab_helper_->SetAppID(tab1.get(), "1"); + + app_icon_loader_->GetAndClearFetchCount(); + launcher_delegate_->PinAppWithID("1"); + ash::LauncherID id = launcher_delegate_->GetLauncherIDForAppID("1"); + int app_index = launcher_model_->ItemIndexByID(id); + EXPECT_GT(app_icon_loader_->GetAndClearFetchCount(), 0); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[app_index].type); + EXPECT_TRUE(launcher_delegate_->IsAppPinned("1")); + EXPECT_FALSE(launcher_delegate_->IsAppPinned("0")); + EXPECT_EQ(initial_size + 1, launcher_model_->items().size()); + + launcher_delegate_.reset(); + launcher_model_.reset(new ash::LauncherModel); + launcher_delegate_.reset( + ChromeLauncherController::CreateInstance(profile(), + launcher_model_.get())); + app_tab_helper_ = new AppTabHelperImpl; + app_tab_helper_->SetAppID(tab1.get(), "1"); + ResetAppTabHelper(); + app_icon_loader_ = new AppIconLoaderImpl; + ResetAppIconLoader(); + launcher_delegate_->Init(); + EXPECT_GT(app_icon_loader_->GetAndClearFetchCount(), 0); + ASSERT_EQ(initial_size + 1, launcher_model_->items().size()); + EXPECT_TRUE(launcher_delegate_->IsAppPinned("1")); + EXPECT_FALSE(launcher_delegate_->IsAppPinned("0")); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[app_index].type); + + UnpinAppsWithID("1"); + ASSERT_EQ(initial_size, launcher_model_->items().size()); +} + +// Verify that launcher item positions are persisted and restored. +TEST_F(BrowserLauncherItemControllerTest, + PersistLauncherItemPositionsPerBrowser) { + int browser_shortcut_index = 0; + int app_list_index = 1; + + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, + launcher_model_->items()[browser_shortcut_index].type); + EXPECT_EQ(ash::TYPE_APP_LIST, + launcher_model_->items()[app_list_index].type); + + scoped_ptr<content::WebContents> tab1(CreateTestWebContents()); + scoped_ptr<content::WebContents> tab2(CreateTestWebContents()); + + app_tab_helper_->SetAppID(tab1.get(), "1"); + app_tab_helper_->SetAppID(tab2.get(), "2"); + + app_icon_loader_->GetAndClearFetchCount(); + launcher_delegate_->PinAppWithID("1"); + ash::LauncherID id = launcher_delegate_->GetLauncherIDForAppID("1"); + int app1_index = launcher_model_->ItemIndexByID(id); + + launcher_delegate_->PinAppWithID("2"); + id = launcher_delegate_->GetLauncherIDForAppID("2"); + int app2_index = launcher_model_->ItemIndexByID(id); + + launcher_model_->Move(browser_shortcut_index, app1_index); + + browser_shortcut_index = 1; + app1_index = 0; + + EXPECT_GT(app_icon_loader_->GetAndClearFetchCount(), 0); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[app1_index].type); + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, + launcher_model_->items()[browser_shortcut_index].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[app2_index].type); + + launcher_delegate_.reset(); + launcher_model_.reset(new ash::LauncherModel); + launcher_delegate_.reset( + ChromeLauncherController::CreateInstance(profile(), + launcher_model_.get())); + + app_tab_helper_ = new AppTabHelperImpl; + app_tab_helper_->SetAppID(tab1.get(), "1"); + app_tab_helper_->SetAppID(tab2.get(), "2"); + ResetAppTabHelper(); + app_icon_loader_ = new AppIconLoaderImpl; + ResetAppIconLoader(); + launcher_delegate_->Init(); + + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[app1_index].type); + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, + launcher_model_->items()[browser_shortcut_index].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, + launcher_model_->items()[app2_index].type); +} + +// Confirm that tabbed browsers handle activation correctly. +TEST_F(BrowserLauncherItemControllerTest, ActivateBrowsers) { + State state1(this, std::string(), BrowserLauncherItemController::TYPE_TABBED); + + // First browser is active. + EXPECT_EQ(ash::STATUS_ACTIVE, state1.GetUpdaterItem().status); + + { + // Both running. + State state2(this, std::string(), + BrowserLauncherItemController::TYPE_TABBED); + EXPECT_EQ(ash::STATUS_ACTIVE, state2.GetUpdaterItem().status); + EXPECT_EQ(ash::STATUS_RUNNING, state1.GetUpdaterItem().status); + + // Make first browser active again. + activation_client_->ActivateWindow(&state1.window); + EXPECT_EQ(ash::STATUS_ACTIVE, state1.GetUpdaterItem().status); + EXPECT_EQ(ash::STATUS_RUNNING, state2.GetUpdaterItem().status); + + // And back to second. + activation_client_->ActivateWindow(&state2.window); + EXPECT_EQ(ash::STATUS_ACTIVE, state2.GetUpdaterItem().status); + EXPECT_EQ(ash::STATUS_RUNNING, state1.GetUpdaterItem().status); + } + + // First browser should be active again after second is closed. + EXPECT_EQ(ash::STATUS_ACTIVE, state1.GetUpdaterItem().status); +} + +// Confirm that window activation works through the model. +TEST_F(BrowserLauncherItemControllerTest, SwitchDirectlyToApp) { + State state1(this, std::string(), + BrowserLauncherItemController::TYPE_TABBED); + int index1 = launcher_model_->ItemIndexByID(state1.GetUpdaterItem().id); + + // Second app is active and first is inactive. + State state2(this, std::string(), + BrowserLauncherItemController::TYPE_TABBED); + int index2 = launcher_model_->ItemIndexByID(state2.GetUpdaterItem().id); + + EXPECT_EQ(ash::STATUS_RUNNING, state1.GetUpdaterItem().status); + EXPECT_EQ(ash::STATUS_ACTIVE, state2.GetUpdaterItem().status); + EXPECT_EQ(&state2.window, activation_client_->GetActiveWindow()); + + // Test that we can properly switch to the first item. + ash::LauncherItem new_item1(launcher_model_->items()[index1]); + new_item1.status = ash::STATUS_ACTIVE; + launcher_model_->Set(index1, new_item1); + EXPECT_EQ(ash::STATUS_ACTIVE, launcher_model_->items()[index1].status); + EXPECT_EQ(ash::STATUS_RUNNING, launcher_model_->items()[index2].status); + EXPECT_EQ(&state1.window, activation_client_->GetActiveWindow()); + + // And to the second item active. + ash::LauncherItem new_item2(launcher_model_->items()[index2]); + new_item2.status = ash::STATUS_ACTIVE; + launcher_model_->Set(index2, new_item2); + EXPECT_EQ(ash::STATUS_RUNNING, launcher_model_->items()[index1].status); + EXPECT_EQ(ash::STATUS_ACTIVE, launcher_model_->items()[index2].status); + EXPECT_EQ(&state2.window, activation_client_->GetActiveWindow()); +} + +// Test attention states of windows. +TEST_F(BrowserLauncherItemControllerTest, FlashWindow) { + // App panel first + State app_state(this, "1", BrowserLauncherItemController::TYPE_APP_PANEL); + EXPECT_EQ(ash::STATUS_ACTIVE, app_state.GetUpdaterItem().status); + + // Active windows don't show attention. + app_state.window.SetProperty(aura::client::kDrawAttentionKey, true); + EXPECT_EQ(ash::STATUS_ACTIVE, app_state.GetUpdaterItem().status); + + // Then browser window + State browser_state( + this, std::string(), BrowserLauncherItemController::TYPE_TABBED); + // First browser is active. + EXPECT_EQ(ash::STATUS_ACTIVE, browser_state.GetUpdaterItem().status); + EXPECT_EQ(ash::STATUS_RUNNING, app_state.GetUpdaterItem().status); + + // App window should go to attention state. + app_state.window.SetProperty(aura::client::kDrawAttentionKey, true); + EXPECT_EQ(ash::STATUS_ATTENTION, app_state.GetUpdaterItem().status); + + // Activating app window should clear attention state. + activation_client_->ActivateWindow(&app_state.window); + EXPECT_EQ(ash::STATUS_ACTIVE, app_state.GetUpdaterItem().status); +} diff --git a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc index 2f00201..f732149 100644 --- a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc +++ b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc @@ -13,7 +13,7 @@ #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" -#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_list.h" @@ -36,11 +36,12 @@ #endif BrowserShortcutLauncherItemController::BrowserShortcutLauncherItemController( - ChromeLauncherController* launcher_controller, + ChromeLauncherControllerPerApp* launcher_controller, Profile* profile) : LauncherItemController(TYPE_SHORTCUT, extension_misc::kChromeAppId, launcher_controller), + app_controller_(launcher_controller), profile_(profile) { } @@ -157,7 +158,7 @@ BrowserShortcutLauncherItemController::GetApplicationList(int event_flags) { continue; if (browser->is_type_tabbed()) found_tabbed_browser = true; - else if (!launcher_controller()->IsBrowserRepresentedInBrowserList(browser)) + else if (!app_controller_->IsBrowserRepresentedInBrowserList(browser)) continue; TabStripModel* tab_strip = browser->tab_strip_model(); if (tab_strip->active_index() == -1) @@ -173,9 +174,8 @@ BrowserShortcutLauncherItemController::GetApplicationList(int event_flags) { for (int index = 0; index < tab_strip->count(); ++index) { content::WebContents* web_contents = tab_strip->GetWebContentsAt(index); - gfx::Image app_icon = - launcher_controller()->GetAppListIcon(web_contents); - string16 title = launcher_controller()->GetAppListTitle(web_contents); + gfx::Image app_icon = app_controller_->GetAppListIcon(web_contents); + string16 title = app_controller_->GetAppListTitle(web_contents); // Check if we need to insert a separator in front. bool leading_separator = !index; items.push_back(new ChromeLauncherAppMenuItemTab( @@ -223,7 +223,7 @@ void BrowserShortcutLauncherItemController::ActivateOrAdvanceToNextBrowser() { for (BrowserList::const_iterator it = ash_browser_list->begin(); it != ash_browser_list->end(); ++it) { - if (launcher_controller()->IsBrowserRepresentedInBrowserList(*it)) + if (app_controller_->IsBrowserRepresentedInBrowserList(*it)) items.push_back(*it); } // If there are no suitable browsers we create a new one. @@ -254,7 +254,7 @@ void BrowserShortcutLauncherItemController::ActivateOrAdvanceToNextBrowser() { true, chrome::HOST_DESKTOP_TYPE_ASH); if (!browser || - !launcher_controller()->IsBrowserRepresentedInBrowserList(browser)) + !app_controller_->IsBrowserRepresentedInBrowserList(browser)) browser = items[0]; } } diff --git a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h index c7583f8..3418ba6 100644 --- a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h +++ b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h @@ -16,14 +16,15 @@ class Image; } class Browser; -class ChromeLauncherController; +class ChromeLauncherControllerPerApp; class Profile; // Item controller for an browser shortcut. class BrowserShortcutLauncherItemController : public LauncherItemController { public: - BrowserShortcutLauncherItemController(ChromeLauncherController* controller, - Profile* profile); + BrowserShortcutLauncherItemController( + ChromeLauncherControllerPerApp* controller, + Profile* profile); virtual ~BrowserShortcutLauncherItemController(); @@ -58,6 +59,8 @@ class BrowserShortcutLauncherItemController : public LauncherItemController { // Activate a browser - or advance to the next one on the list. void ActivateOrAdvanceToNextBrowser(); + ChromeLauncherControllerPerApp* app_controller_; + Profile* profile_; DISALLOW_COPY_AND_ASSIGN(BrowserShortcutLauncherItemController); diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc index 9ca859d..a2c4907 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.cc @@ -6,12 +6,12 @@ #include "ash/wm/window_util.h" #include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.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/tabs/tab_strip_model.h" #include "content/public/browser/notification_service.h" -#include "ui/base/events/event_constants.h" ChromeLauncherAppMenuItemBrowser::ChromeLauncherAppMenuItemBrowser( const string16 title, diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc index 294c2f1..1aaf17d 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.cc @@ -5,11 +5,11 @@ #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" #include "ash/wm/window_util.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.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/tabs/tab_strip_model.h" -#include "ui/base/events/event_constants.h" ChromeLauncherAppMenuItemTab::ChromeLauncherAppMenuItemTab( const string16 title, diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc index fe6546c..d85c90b 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.cc @@ -4,13 +4,13 @@ #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h" -#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" ChromeLauncherAppMenuItemV2App::ChromeLauncherAppMenuItemV2App( const string16 title, const gfx::Image* icon, const std::string& app_id, - ChromeLauncherController* launcher_controller, + ChromeLauncherControllerPerApp* launcher_controller, int app_index, bool has_leading_separator) : ChromeLauncherAppMenuItem(title, icon, has_leading_separator), diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h index c195b0e..391ae26 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h @@ -13,7 +13,7 @@ namespace gfx { class image; } -class ChromeLauncherController; +class ChromeLauncherControllerPerApp; // A menu item controller for a running V2 application. It gets created when an // application list gets created. It's main purpose is to add the activation @@ -24,7 +24,7 @@ class ChromeLauncherAppMenuItemV2App : public ChromeLauncherAppMenuItem { const string16 title, const gfx::Image* icon, const std::string& app_id, - ChromeLauncherController* launcher_controller, + ChromeLauncherControllerPerApp* launcher_controller, int app_index, bool has_leading_separator); virtual bool IsEnabled() const OVERRIDE; @@ -32,7 +32,7 @@ class ChromeLauncherAppMenuItemV2App : public ChromeLauncherAppMenuItem { private: // The owning class which can be used to validate the controller. - ChromeLauncherController* launcher_controller_; + ChromeLauncherControllerPerApp* launcher_controller_; // The application ID. const std::string app_id_; diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc index 973ded6..442f2e8 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc @@ -4,286 +4,14 @@ #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" -#include <vector> - #include "ash/ash_switches.h" -#include "ash/launcher/launcher.h" -#include "ash/launcher/launcher_model.h" -#include "ash/launcher/launcher_util.h" -#include "ash/root_window_controller.h" -#include "ash/shelf/shelf_layout_manager.h" -#include "ash/shelf/shelf_widget.h" -#include "ash/shell.h" -#include "ash/wm/window_util.h" #include "base/command_line.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/utf_string_conversions.h" -#include "base/values.h" -#include "chrome/browser/app_mode/app_mode_utils.h" -#include "chrome/browser/chrome_notification_types.h" -#include "chrome/browser/defaults.h" -#include "chrome/browser/extensions/app_icon_loader_impl.h" -#include "chrome/browser/extensions/extension_service.h" -#include "chrome/browser/extensions/extension_system.h" -#include "chrome/browser/favicon/favicon_tab_helper.h" -#include "chrome/browser/prefs/incognito_mode_prefs.h" -#include "chrome/browser/prefs/pref_service_syncable.h" -#include "chrome/browser/prefs/scoped_user_pref_update.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/profiles/profile_manager.h" -#include "chrome/browser/ui/ash/app_sync_ui_state.h" -#include "chrome/browser/ui/ash/chrome_launcher_prefs.h" -#include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h" -#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h" -#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" -#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h" -#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" -#include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h" -#include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h" -#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h" -#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" -#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" -#include "chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_commands.h" -#include "chrome/browser/ui/browser_finder.h" -#include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/browser_tabstrip.h" -#include "chrome/browser/ui/browser_window.h" -#include "chrome/browser/ui/extensions/application_launch.h" -#include "chrome/browser/ui/extensions/extension_enable_flow.h" -#include "chrome/browser/ui/host_desktop.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/browser/web_applications/web_app.h" -#include "chrome/common/chrome_switches.h" -#include "chrome/common/extensions/extension.h" -#include "chrome/common/extensions/manifest_handlers/icons_handler.h" -#include "chrome/common/pref_names.h" -#include "chrome/common/url_constants.h" -#include "content/public/browser/navigation_entry.h" -#include "content/public/browser/notification_registrar.h" -#include "content/public/browser/notification_service.h" -#include "content/public/browser/web_contents.h" -#include "extensions/common/extension_resource.h" -#include "extensions/common/url_pattern.h" -#include "grit/ash_resources.h" -#include "grit/chromium_strings.h" -#include "grit/generated_resources.h" -#include "grit/theme_resources.h" -#include "grit/ui_resources.h" -#include "ui/aura/root_window.h" -#include "ui/aura/window.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/views/corewm/window_animations.h" - -#if defined(OS_CHROMEOS) -#include "chrome/browser/chromeos/login/default_pinned_apps_field_trial.h" -#endif - -using extensions::Extension; -using extension_misc::kGmailAppId; -using content::WebContents; +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h" -// static +// statics ChromeLauncherController* ChromeLauncherController::instance_ = NULL; -namespace { - -std::string GetPrefKeyForRootWindow(aura::RootWindow* root_window) { - gfx::Display display = gfx::Screen::GetScreenFor( - root_window)->GetDisplayNearestWindow(root_window); - DCHECK(display.is_valid()); - - return base::Int64ToString(display.id()); -} - -void UpdatePerDisplayPref(PrefService* pref_service, - aura::RootWindow* root_window, - const char* pref_key, - const std::string& value) { - std::string key = GetPrefKeyForRootWindow(root_window); - if (key.empty()) - return; - - DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences); - base::DictionaryValue* shelf_prefs = update.Get(); - base::DictionaryValue* prefs = NULL; - if (!shelf_prefs->GetDictionary(key, &prefs)) { - prefs = new base::DictionaryValue(); - shelf_prefs->Set(key, prefs); - } - prefs->SetStringWithoutPathExpansion(pref_key, value); -} - -// Returns a pref value in |pref_service| for the display of |root_window|. The -// pref value is stored in |local_path| and |path|, but |pref_service| may have -// per-display preferences and the value can be specified by policy. Here is -// the priority: -// * A value managed by policy. This is a single value that applies to all -// displays. -// * A user-set value for the specified display. -// * A user-set value in |local_path| or |path|, if no per-display settings are -// ever specified (see http://crbug.com/173719 for why). |local_path| is -// preferred. See comment in |kShelfAlignment| as to why we consider two -// prefs and why |local_path| is preferred. -// * A value recommended by policy. This is a single value that applies to all -// root windows. -// * The default value for |local_path| if the value is not recommended by -// policy. -std::string GetPrefForRootWindow(PrefService* pref_service, - aura::RootWindow* root_window, - const char* local_path, - const char* path) { - const PrefService::Preference* local_pref = - pref_service->FindPreference(local_path); - const std::string value(pref_service->GetString(local_path)); - if (local_pref->IsManaged()) - return value; - - std::string pref_key = GetPrefKeyForRootWindow(root_window); - bool has_per_display_prefs = false; - if (!pref_key.empty()) { - const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary( - prefs::kShelfPreferences); - const base::DictionaryValue* display_pref = NULL; - std::string per_display_value; - if (shelf_prefs->GetDictionary(pref_key, &display_pref) && - display_pref->GetString(path, &per_display_value)) - return per_display_value; - - // If the pref for the specified display is not found, scan the whole prefs - // and check if the prefs for other display is already specified. - std::string unused_value; - for (base::DictionaryValue::Iterator iter(*shelf_prefs); - !iter.IsAtEnd(); iter.Advance()) { - const base::DictionaryValue* display_pref = NULL; - if (iter.value().GetAsDictionary(&display_pref) && - display_pref->GetString(path, &unused_value)) { - has_per_display_prefs = true; - break; - } - } - } - - if (local_pref->IsRecommended() || !has_per_display_prefs) - return value; - - const base::Value* default_value = - pref_service->GetDefaultPrefValue(local_path); - std::string default_string; - default_value->GetAsString(&default_string); - return default_string; -} - -// If prefs have synced and no user-set value exists at |local_path|, the value -// from |synced_path| is copied to |local_path|. -void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service, - const char* local_path, - const char* synced_path) { - if (!pref_service->FindPreference(local_path)->HasUserSetting() && - pref_service->IsSyncing()) { - // First time the user is using this machine, propagate from remote to - // local. - pref_service->SetString(local_path, pref_service->GetString(synced_path)); - } -} - -} // namespace - -ChromeLauncherController::ChromeLauncherController( - Profile* profile, - ash::LauncherModel* model) - : model_(model), - profile_(profile), - app_sync_ui_state_(NULL), - ignore_persist_pinned_state_change_(false) { - if (!profile_) { - // Use the original profile as on chromeos we may get a temporary off the - // record profile. - profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile(); - - app_sync_ui_state_ = AppSyncUIState::Get(profile_); - if (app_sync_ui_state_) - app_sync_ui_state_->AddObserver(this); - } - - model_->AddObserver(this); - BrowserList::AddObserver(this); - // Right now ash::Shell isn't created for tests. - // TODO(mukai): Allows it to observe display change and write tests. - if (ash::Shell::HasInstance()) - ash::Shell::GetInstance()->display_controller()->AddObserver(this); - // TODO(stevenjb): Find a better owner for shell_window_controller_? - shell_window_controller_.reset(new ShellWindowLauncherController(this)); - app_tab_helper_.reset(new LauncherAppTabHelper(profile_)); - app_icon_loader_.reset(new extensions::AppIconLoaderImpl( - profile_, extension_misc::EXTENSION_ICON_SMALL, this)); - - notification_registrar_.Add(this, - chrome::NOTIFICATION_EXTENSION_LOADED, - content::Source<Profile>(profile_)); - notification_registrar_.Add(this, - chrome::NOTIFICATION_EXTENSION_UNLOADED, - content::Source<Profile>(profile_)); - pref_change_registrar_.Init(profile_->GetPrefs()); - pref_change_registrar_.Add( - prefs::kPinnedLauncherApps, - base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref, - base::Unretained(this))); - pref_change_registrar_.Add( - prefs::kShelfAlignmentLocal, - base::Bind(&ChromeLauncherController::SetShelfAlignmentFromPrefs, - base::Unretained(this))); - pref_change_registrar_.Add( - prefs::kShelfAutoHideBehaviorLocal, - base::Bind(&ChromeLauncherController:: - SetShelfAutoHideBehaviorFromPrefs, - base::Unretained(this))); - pref_change_registrar_.Add( - prefs::kShelfPreferences, - base::Bind(&ChromeLauncherController::SetShelfBehaviorsFromPrefs, - base::Unretained(this))); -} - -ChromeLauncherController::~ChromeLauncherController() { - // Reset the shell window controller here since it has a weak pointer to this. - shell_window_controller_.reset(); - - for (std::set<ash::Launcher*>::iterator iter = launchers_.begin(); - iter != launchers_.end(); - ++iter) - (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this); - - model_->RemoveObserver(this); - BrowserList::RemoveObserver(this); - if (ash::Shell::HasInstance()) - ash::Shell::GetInstance()->display_controller()->RemoveObserver(this); - for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); - i != id_to_item_controller_map_.end(); ++i) { - i->second->OnRemoved(); - // TODO(skuhne): After getting rid of the old launcher, get also rid of the - // BrowserLauncherItemController (since it is only used for activation - // tracking at that point. - int index = model_->ItemIndexByID(i->first); - // A "browser proxy" is not known to the model and this removal does - // therefore not need to be propagated to the model. - if (index != -1 && - model_->items()[index].type != ash::TYPE_BROWSER_SHORTCUT) - model_->RemoveItemAt(index); - } - - if (ash::Shell::HasInstance()) - ash::Shell::GetInstance()->RemoveShellObserver(this); - - if (app_sync_ui_state_) - app_sync_ui_state_->RemoveObserver(this); - - PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this); - - if (instance_ == this) - instance_ = NULL; -} - // static ChromeLauncherController* ChromeLauncherController::CreateInstance( Profile* profile, @@ -291,1368 +19,21 @@ ChromeLauncherController* ChromeLauncherController::CreateInstance( // We do not check here for re-creation of the ChromeLauncherController since // it appears that it might be intentional that the ChromeLauncherController // can be re-created. - instance_ = new ChromeLauncherController(profile, model); - return instance_; -} - -void ChromeLauncherController::Init() { - UpdateAppLaunchersFromPref(); - CreateBrowserShortcutLauncherItem(); - - // TODO(sky): update unit test so that this test isn't necessary. - if (ash::Shell::HasInstance()) { - SetShelfAutoHideBehaviorFromPrefs(); - SetShelfAlignmentFromPrefs(); - PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); - if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() || - !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)-> - HasUserSetting()) { - // This causes OnIsSyncingChanged to be called when the value of - // PrefService::IsSyncing() changes. - prefs->AddObserver(this); - } - ash::Shell::GetInstance()->AddShellObserver(this); - } -} - -ash::LauncherID ChromeLauncherController::CreateAppLauncherItem( - LauncherItemController* controller, - const std::string& app_id, - ash::LauncherItemStatus status) { - CHECK(controller); - int index = 0; - // Panels are inserted on the left so as not to push all existing panels over. - if (controller->GetLauncherItemType() != ash::TYPE_APP_PANEL) { - index = model_->item_count(); - // For the alternate shelf layout increment the index (after the app icon) - if (ash::switches::UseAlternateShelfLayout()) - ++index; - } - return InsertAppLauncherItem(controller, - app_id, - status, - index, - controller->GetLauncherItemType()); -} - -void ChromeLauncherController::SetItemStatus( - ash::LauncherID id, - ash::LauncherItemStatus status) { - int index = model_->ItemIndexByID(id); - // Since ordinary browser windows are not registered, we might get a negative - // index here. - if (index >= 0) { - ash::LauncherItem item = model_->items()[index]; - item.status = status; - model_->Set(index, item); - - if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT) - return; - } - UpdateBrowserItemStatus(); -} - -void ChromeLauncherController::SetItemController( - ash::LauncherID id, - LauncherItemController* controller) { - CHECK(controller); - IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); - CHECK(iter != id_to_item_controller_map_.end()); - iter->second->OnRemoved(); - iter->second = controller; - controller->set_launcher_id(id); -} - -void ChromeLauncherController::CloseLauncherItem(ash::LauncherID id) { - CHECK(id); - if (IsPinned(id)) { - // Create a new shortcut controller. - IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); - CHECK(iter != id_to_item_controller_map_.end()); - SetItemStatus(id, ash::STATUS_CLOSED); - std::string app_id = iter->second->app_id(); - iter->second->OnRemoved(); - iter->second = new AppShortcutLauncherItemController(app_id, this); - iter->second->set_launcher_id(id); - } else { - LauncherItemClosed(id); - } -} - -void ChromeLauncherController::Pin(ash::LauncherID id) { - DCHECK(HasItemController(id)); - - int index = model_->ItemIndexByID(id); - DCHECK_GE(index, 0); - - ash::LauncherItem item = model_->items()[index]; - - if (item.type == ash::TYPE_PLATFORM_APP || - item.type == ash::TYPE_WINDOWED_APP) { - item.type = ash::TYPE_APP_SHORTCUT; - model_->Set(index, item); - } else if (item.type != ash::TYPE_APP_SHORTCUT) { - return; - } - - if (CanPin()) - PersistPinnedState(); -} - -void ChromeLauncherController::Unpin(ash::LauncherID id) { - DCHECK(HasItemController(id)); - - LauncherItemController* controller = id_to_item_controller_map_[id]; - if (controller->type() == LauncherItemController::TYPE_APP) { - int index = model_->ItemIndexByID(id); - DCHECK_GE(index, 0); - ash::LauncherItem item = model_->items()[index]; - item.type = ash::TYPE_PLATFORM_APP; - model_->Set(index, item); - } else { - // Prevent the removal of items upon unpin if it is locked by a running - // windowed V1 app. - if (!controller->locked()) { - LauncherItemClosed(id); - } else { - int index = model_->ItemIndexByID(id); - DCHECK_GE(index, 0); - ash::LauncherItem item = model_->items()[index]; - item.type = ash::TYPE_WINDOWED_APP; - model_->Set(index, item); - } - } - if (CanPin()) - PersistPinnedState(); -} - -bool ChromeLauncherController::IsPinned(ash::LauncherID id) { - int index = model_->ItemIndexByID(id); - if (index < 0) - return false; - ash::LauncherItemType type = model_->items()[index].type; - return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_SHORTCUT); -} - -void ChromeLauncherController::TogglePinned(ash::LauncherID id) { - if (!HasItemController(id)) - return; // May happen if item closed with menu open. - - if (IsPinned(id)) - Unpin(id); - else - Pin(id); -} - -bool ChromeLauncherController::IsPinnable(ash::LauncherID id) const { - int index = model_->ItemIndexByID(id); - if (index == -1) - return false; - - ash::LauncherItemType type = model_->items()[index].type; - return ((type == ash::TYPE_APP_SHORTCUT || - type == ash::TYPE_PLATFORM_APP || - type == ash::TYPE_WINDOWED_APP) && - CanPin()); -} - -void ChromeLauncherController::LockV1AppWithID( - const std::string& app_id) { - ash::LauncherID id = GetLauncherIDForAppID(app_id); - if (!IsPinned(id) && !IsWindowedAppInLauncher(app_id)) { - CreateAppShortcutLauncherItemWithType(app_id, - model_->item_count(), - ash::TYPE_WINDOWED_APP); - id = GetLauncherIDForAppID(app_id); - } - CHECK(id); - id_to_item_controller_map_[id]->lock(); -} - -void ChromeLauncherController::UnlockV1AppWithID( - const std::string& app_id) { - ash::LauncherID id = GetLauncherIDForAppID(app_id); - CHECK(IsPinned(id) || IsWindowedAppInLauncher(app_id)); - CHECK(id); - LauncherItemController* controller = id_to_item_controller_map_[id]; - controller->unlock(); - if (!controller->locked() && !IsPinned(id)) - CloseLauncherItem(id); -} - -void ChromeLauncherController::Launch(ash::LauncherID id, - int event_flags) { - if (!HasItemController(id)) - return; // In case invoked from menu and item closed while menu up. - id_to_item_controller_map_[id]->Launch(event_flags); -} - -void ChromeLauncherController::Close(ash::LauncherID id) { - if (!HasItemController(id)) - return; // May happen if menu closed. - id_to_item_controller_map_[id]->Close(); -} - -bool ChromeLauncherController::IsOpen(ash::LauncherID id) { - if (!HasItemController(id)) - return false; - return id_to_item_controller_map_[id]->IsOpen(); -} - -bool ChromeLauncherController::IsPlatformApp(ash::LauncherID id) { - if (!HasItemController(id)) - return false; - - std::string app_id = GetAppIDForLauncherID(id); - const Extension* extension = GetExtensionForAppID(app_id); - // An extension can be synced / updated at any time and therefore not be - // available. - return extension ? extension->is_platform_app() : false; -} - -void ChromeLauncherController::LaunchApp(const std::string& app_id, - int event_flags) { - // |extension| could be NULL when it is being unloaded for updating. - const Extension* extension = GetExtensionForAppID(app_id); - if (!extension) - return; - - const ExtensionService* service = - extensions::ExtensionSystem::Get(profile_)->extension_service(); - if (!service->IsExtensionEnabledForLauncher(app_id)) { - // Do nothing if there is already a running enable flow. - if (extension_enable_flow_) - return; - - extension_enable_flow_.reset( - new ExtensionEnableFlow(profile_, app_id, this)); - extension_enable_flow_->StartForNativeWindow(NULL); - return; - } - - chrome::OpenApplication(chrome::AppLaunchParams(GetProfileForNewWindows(), - extension, - event_flags)); -} - -void ChromeLauncherController::ActivateApp(const std::string& app_id, - int event_flags) { - // If there is an existing non-shortcut controller for this app, open it. - ash::LauncherID id = GetLauncherIDForAppID(app_id); - if (id) { - LauncherItemController* controller = id_to_item_controller_map_[id]; - controller->Activate(); - return; - } - - // Create a temporary application launcher item and use it to see if there are - // running instances. - scoped_ptr<AppShortcutLauncherItemController> app_controller( - new AppShortcutLauncherItemController(app_id, this)); - if (!app_controller->GetRunningApplications().empty()) - app_controller->Activate(); - else - LaunchApp(app_id, event_flags); -} - -extensions::ExtensionPrefs::LaunchType - ChromeLauncherController::GetLaunchType(ash::LauncherID id) { - DCHECK(HasItemController(id)); - - const Extension* extension = GetExtensionForAppID( - id_to_item_controller_map_[id]->app_id()); - - // An extension can be unloaded/updated/unavailable at any time. - if (!extension) - return extensions::ExtensionPrefs::LAUNCH_DEFAULT; - - return profile_->GetExtensionService()->extension_prefs()->GetLaunchType( - extension, - extensions::ExtensionPrefs::LAUNCH_DEFAULT); -} - -std::string ChromeLauncherController::GetAppID(content::WebContents* tab) { - return app_tab_helper_->GetAppID(tab); -} - -ash::LauncherID ChromeLauncherController::GetLauncherIDForAppID( - const std::string& app_id) { - for (IDToItemControllerMap::const_iterator i = - id_to_item_controller_map_.begin(); - i != id_to_item_controller_map_.end(); ++i) { - if (i->second->type() == LauncherItemController::TYPE_APP_PANEL) - continue; // Don't include panels - if (i->second->app_id() == app_id) - return i->first; - } - return 0; -} - -std::string ChromeLauncherController::GetAppIDForLauncherID( - ash::LauncherID id) { - CHECK(HasItemController(id)); - return id_to_item_controller_map_[id]->app_id(); -} - -void ChromeLauncherController::SetAppImage(const std::string& id, - const gfx::ImageSkia& image) { - // TODO: need to get this working for shortcuts. - - for (IDToItemControllerMap::const_iterator i = - id_to_item_controller_map_.begin(); - i != id_to_item_controller_map_.end(); ++i) { - LauncherItemController* controller = i->second; - if (controller->app_id() != id) - continue; - if (controller->image_set_by_controller()) - continue; - int index = model_->ItemIndexByID(i->first); - if (index == -1) - continue; - ash::LauncherItem item = model_->items()[index]; - item.image = image; - model_->Set(index, item); - // It's possible we're waiting on more than one item, so don't break. - } -} - -void ChromeLauncherController::OnAutoHideBehaviorChanged( - aura::RootWindow* root_window, - ash::ShelfAutoHideBehavior new_behavior) { - SetShelfAutoHideBehaviorPrefs(new_behavior, root_window); -} - -void ChromeLauncherController::SetLauncherItemImage( - ash::LauncherID launcher_id, - const gfx::ImageSkia& image) { - int index = model_->ItemIndexByID(launcher_id); - if (index == -1) - return; - ash::LauncherItem item = model_->items()[index]; - item.image = image; - model_->Set(index, item); -} - -bool ChromeLauncherController::IsAppPinned(const std::string& app_id) { - for (IDToItemControllerMap::const_iterator i = - id_to_item_controller_map_.begin(); - i != id_to_item_controller_map_.end(); ++i) { - if (IsPinned(i->first) && i->second->app_id() == app_id) - return true; - } - return false; -} - -bool ChromeLauncherController::IsWindowedAppInLauncher( - const std::string& app_id) { - int index = model_->ItemIndexByID(GetLauncherIDForAppID(app_id)); - if (index < 0) - return false; - - ash::LauncherItemType type = model_->items()[index].type; - return type == ash::TYPE_WINDOWED_APP; -} - -void ChromeLauncherController::PinAppWithID(const std::string& app_id) { - if (CanPin()) - DoPinAppWithID(app_id); - else - NOTREACHED(); -} - -void ChromeLauncherController::SetLaunchType( - ash::LauncherID id, - extensions::ExtensionPrefs::LaunchType launch_type) { - if (!HasItemController(id)) - return; - - profile_->GetExtensionService()->extension_prefs()->SetLaunchType( - id_to_item_controller_map_[id]->app_id(), launch_type); -} - -void ChromeLauncherController::UnpinAppsWithID(const std::string& app_id) { - if (CanPin()) - DoUnpinAppsWithID(app_id); + if (!CommandLine::ForCurrentProcess()->HasSwitch( + ash::switches::kAshDisablePerAppLauncher)) + instance_ = new ChromeLauncherControllerPerApp(profile, model); else - NOTREACHED(); -} - -bool ChromeLauncherController::IsLoggedInAsGuest() { - return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord(); -} - -void ChromeLauncherController::CreateNewWindow() { - chrome::NewEmptyWindow( - GetProfileForNewWindows(), chrome::HOST_DESKTOP_TYPE_ASH); -} - -void ChromeLauncherController::CreateNewIncognitoWindow() { - chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile(), - chrome::HOST_DESKTOP_TYPE_ASH); -} - -bool ChromeLauncherController::CanPin() const { - const PrefService::Preference* pref = - profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps); - return pref && pref->IsUserModifiable(); -} - -void ChromeLauncherController::PersistPinnedState() { - if (ignore_persist_pinned_state_change_) - return; - // It is a coding error to call PersistPinnedState() if the pinned apps are - // not user-editable. The code should check earlier and not perform any - // modification actions that trigger persisting the state. - if (!CanPin()) { - NOTREACHED() << "Can't pin but pinned state being updated"; - return; - } - - // Mutating kPinnedLauncherApps is going to notify us and trigger us to - // process the change. We don't want that to happen so remove ourselves as a - // listener. - pref_change_registrar_.Remove(prefs::kPinnedLauncherApps); - { - ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); - updater->Clear(); - for (size_t i = 0; i < model_->items().size(); ++i) { - if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { - ash::LauncherID id = model_->items()[i].id; - if (HasItemController(id) && IsPinned(id)) { - base::DictionaryValue* app_value = ash::CreateAppDict( - id_to_item_controller_map_[id]->app_id()); - if (app_value) - updater->Append(app_value); - } - } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) { - PersistChromeItemIndex(i); - } - } - } - pref_change_registrar_.Add( - prefs::kPinnedLauncherApps, - base::Bind(&ChromeLauncherController::UpdateAppLaunchersFromPref, - base::Unretained(this))); -} - -ash::LauncherModel* ChromeLauncherController::model() { - return model_; -} - -Profile* ChromeLauncherController::profile() { - return profile_; -} - -ash::ShelfAutoHideBehavior ChromeLauncherController::GetShelfAutoHideBehavior( - aura::RootWindow* root_window) const { - // Don't show the shelf in app mode. - if (chrome::IsRunningInAppMode()) - return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN; - - // See comment in |kShelfAlignment| as to why we consider two prefs. - const std::string behavior_value( - GetPrefForRootWindow(profile_->GetPrefs(), - root_window, - prefs::kShelfAutoHideBehaviorLocal, - prefs::kShelfAutoHideBehavior)); - - // Note: To maintain sync compatibility with old images of chrome/chromeos - // the set of values that may be encountered includes the now-extinct - // "Default" as well as "Never" and "Always", "Default" should now - // be treated as "Never" (http://crbug.com/146773). - if (behavior_value == ash::kShelfAutoHideBehaviorAlways) - return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; - return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER; -} - -bool ChromeLauncherController::CanUserModifyShelfAutoHideBehavior( - aura::RootWindow* root_window) const { - return profile_->GetPrefs()-> - FindPreference(prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable(); -} - -void ChromeLauncherController::ToggleShelfAutoHideBehavior( - aura::RootWindow* root_window) { - ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) == - ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ? - ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER : - ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; - SetShelfAutoHideBehaviorPrefs(behavior, root_window); - return; -} - -void ChromeLauncherController::RemoveTabFromRunningApp( - WebContents* tab, - const std::string& app_id) { - web_contents_to_app_id_.erase(tab); - AppIDToWebContentsListMap::iterator i_app_id = - app_id_to_web_contents_list_.find(app_id); - if (i_app_id != app_id_to_web_contents_list_.end()) { - WebContentsList* tab_list = &i_app_id->second; - tab_list->remove(tab); - if (tab_list->empty()) { - app_id_to_web_contents_list_.erase(i_app_id); - i_app_id = app_id_to_web_contents_list_.end(); - ash::LauncherID id = GetLauncherIDForAppID(app_id); - if (id) - SetItemStatus(id, ash::STATUS_CLOSED); - } - } -} - -void ChromeLauncherController::UpdateAppState(content::WebContents* contents, - AppState app_state) { - std::string app_id = GetAppID(contents); - - // Check if the gMail app is loaded and it matches the given content. - // This special treatment is needed to address crbug.com/234268. - if (app_id.empty() && ContentCanBeHandledByGmailApp(contents)) - app_id = kGmailAppId; - - // Check the old |app_id| for a tab. If the contents has changed we need to - // remove it from the previous app. - if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) { - std::string last_app_id = web_contents_to_app_id_[contents]; - if (last_app_id != app_id) - RemoveTabFromRunningApp(contents, last_app_id); - } - - if (app_id.empty()) { - // Even if there is no application running, we should update the activation - // state of the associated browser. - UpdateBrowserItemStatus(); - return; - } - - web_contents_to_app_id_[contents] = app_id; - - if (app_state == APP_STATE_REMOVED) { - // The tab has gone away. - RemoveTabFromRunningApp(contents, app_id); - } else { - WebContentsList& tab_list(app_id_to_web_contents_list_[app_id]); - - if (app_state == APP_STATE_INACTIVE) { - WebContentsList::const_iterator i_tab = - std::find(tab_list.begin(), tab_list.end(), contents); - if (i_tab == tab_list.end()) - tab_list.push_back(contents); - if (i_tab != tab_list.begin()) { - // Going inactive, but wasn't the front tab, indicating that a new - // tab has already become active. - return; - } - } else { - tab_list.remove(contents); - tab_list.push_front(contents); - } - ash::LauncherID id = GetLauncherIDForAppID(app_id); - if (id) { - // If the window is active, mark the app as active. - SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ? - ash::STATUS_ACTIVE : ash::STATUS_RUNNING); - } - } - UpdateBrowserItemStatus(); -} - -void ChromeLauncherController::SetRefocusURLPatternForTest(ash::LauncherID id, - const GURL& url) { - DCHECK(HasItemController(id)); - LauncherItemController* controller = id_to_item_controller_map_[id]; - - int index = model_->ItemIndexByID(id); - if (index == -1) { - NOTREACHED() << "Invalid launcher id"; - return; - } - - ash::LauncherItemType type = model_->items()[index].type; - if (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_WINDOWED_APP) { - AppShortcutLauncherItemController* app_controller = - static_cast<AppShortcutLauncherItemController*>(controller); - app_controller->set_refocus_url(url); - } else { - NOTREACHED() << "Invalid launcher type"; - } -} - -const Extension* ChromeLauncherController::GetExtensionForAppID( - const std::string& app_id) const { - // Some unit tests do not have a real extension. - return (profile_->GetExtensionService()) ? - profile_->GetExtensionService()->GetInstalledExtension(app_id) : NULL; -} - -void ChromeLauncherController::ActivateWindowOrMinimizeIfActive( - ui::BaseWindow* window, - bool allow_minimize) { - if (window->IsActive() && allow_minimize) { - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableMinimizeOnSecondLauncherItemClick)) { - AnimateWindow(window->GetNativeWindow(), - views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE); - } else { - window->Minimize(); - } - } else { - window->Show(); - window->Activate(); - } -} - -void ChromeLauncherController::ItemSelected(const ash::LauncherItem& item, - const ui::Event& event) { - DCHECK(HasItemController(item.id)); - LauncherItemController* item_controller = id_to_item_controller_map_[item.id]; -#if defined(OS_CHROMEOS) - if (!item_controller->app_id().empty()) { - chromeos::default_pinned_apps_field_trial::RecordShelfAppClick( - item_controller->app_id()); - } -#endif - item_controller->Clicked(event); -} - -string16 ChromeLauncherController::GetTitle(const ash::LauncherItem& item) { - DCHECK(HasItemController(item.id)); - return id_to_item_controller_map_[item.id]->GetTitle(); -} - -ui::MenuModel* ChromeLauncherController::CreateContextMenu( - const ash::LauncherItem& item, - aura::RootWindow* root_window) { - return new LauncherContextMenu(this, &item, root_window); -} - -ash::LauncherMenuModel* ChromeLauncherController::CreateApplicationMenu( - const ash::LauncherItem& item, - int event_flags) { - return new LauncherApplicationMenuItemModel(GetApplicationList(item, - event_flags)); -} - -ash::LauncherID ChromeLauncherController::GetIDByWindow(aura::Window* window) { - int browser_index = ash::launcher::GetBrowserItemIndex(*model_); - DCHECK_GE(browser_index, 0); - ash::LauncherID browser_id = model_->items()[browser_index].id; - - IDToItemControllerMap::const_iterator i = id_to_item_controller_map_.begin(); - for (; i != id_to_item_controller_map_.end(); ++i) { - // Since a |window| can be used by multiple applications, an explicit - // application always gets chosen over the generic browser. - if (i->first != browser_id && i->second->IsCurrentlyShownInWindow(window)) - return i->first; - } - - if (i == id_to_item_controller_map_.end() && - GetBrowserShortcutLauncherItemController()-> - IsCurrentlyShownInWindow(window)) - return browser_id; - - return 0; + instance_ = new ChromeLauncherControllerPerBrowser(profile, model); + return instance_; } -bool ChromeLauncherController::IsDraggable(const ash::LauncherItem& item) { - return (item.type == ash::TYPE_APP_SHORTCUT || - item.type == ash::TYPE_WINDOWED_APP) ? CanPin() : true; +ChromeLauncherController::~ChromeLauncherController() { + if (instance_ == this) + instance_ = NULL; } -bool ChromeLauncherController::ShouldShowTooltip( - const ash::LauncherItem& item) { - if (item.type == ash::TYPE_APP_PANEL && - id_to_item_controller_map_[item.id]->IsVisible()) +bool ChromeLauncherController::IsPerAppLauncher() { + if (!instance_) return false; - return true; -} - -void ChromeLauncherController::OnLauncherCreated(ash::Launcher* launcher) { - launchers_.insert(launcher); - launcher->shelf_widget()->shelf_layout_manager()->AddObserver(this); -} - -void ChromeLauncherController::OnLauncherDestroyed(ash::Launcher* launcher) { - launchers_.erase(launcher); - // RemoveObserver is not called here, since by the time this method is called - // Launcher is already in its destructor. -} - -void ChromeLauncherController::LauncherItemAdded(int index) { -} - -void ChromeLauncherController::LauncherItemRemoved(int index, - ash::LauncherID id) { -} - -void ChromeLauncherController::LauncherItemMoved(int start_index, - int target_index) { - ash::LauncherID id = model_->items()[target_index].id; - if (HasItemController(id) && IsPinned(id)) - PersistPinnedState(); -} - -void ChromeLauncherController::LauncherItemChanged( - int index, - const ash::LauncherItem& old_item) { - ash::LauncherID id = model_->items()[index].id; - DCHECK(HasItemController(id)); - id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item); -} - -void ChromeLauncherController::LauncherStatusChanged() { -} - -void ChromeLauncherController::Observe( - int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - switch (type) { - case chrome::NOTIFICATION_EXTENSION_LOADED: { - const Extension* extension = - content::Details<const Extension>(details).ptr(); - if (IsAppPinned(extension->id())) { - // Clear and re-fetch to ensure icon is up-to-date. - app_icon_loader_->ClearImage(extension->id()); - app_icon_loader_->FetchImage(extension->id()); - } - - UpdateAppLaunchersFromPref(); - break; - } - case chrome::NOTIFICATION_EXTENSION_UNLOADED: { - const content::Details<extensions::UnloadedExtensionInfo>& unload_info( - details); - const Extension* extension = unload_info->extension; - const std::string& id = extension->id(); - // Since we might have windowed apps of this type which might have - // outstanding locks which needs to be removed. - if (GetLauncherIDForAppID(id) && - unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) { - CloseWindowedAppsFromRemovedExtension(id); - } - - if (IsAppPinned(id)) { - if (unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) { - DoUnpinAppsWithID(id); - app_icon_loader_->ClearImage(id); - } else { - app_icon_loader_->UpdateImage(id); - } - } - break; - } - default: - NOTREACHED() << "Unexpected notification type=" << type; - } -} - -void ChromeLauncherController::OnShelfAlignmentChanged( - aura::RootWindow* root_window) { - const char* pref_value = NULL; - switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) { - case ash::SHELF_ALIGNMENT_BOTTOM: - pref_value = ash::kShelfAlignmentBottom; - break; - case ash::SHELF_ALIGNMENT_LEFT: - pref_value = ash::kShelfAlignmentLeft; - break; - case ash::SHELF_ALIGNMENT_RIGHT: - pref_value = ash::kShelfAlignmentRight; - break; - case ash::SHELF_ALIGNMENT_TOP: - pref_value = ash::kShelfAlignmentTop; - } - - UpdatePerDisplayPref( - profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value); - - if (root_window == ash::Shell::GetPrimaryRootWindow()) { - // See comment in |kShelfAlignment| about why we have two prefs here. - profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value); - profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value); - } -} - -void ChromeLauncherController::OnDisplayConfigurationChanging() { -} - -void ChromeLauncherController::OnDisplayConfigurationChanged() { - SetShelfBehaviorsFromPrefs(); -} - -void ChromeLauncherController::OnIsSyncingChanged() { - PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); - MaybePropagatePrefToLocal(prefs, - prefs::kShelfAlignmentLocal, - prefs::kShelfAlignment); - MaybePropagatePrefToLocal(prefs, - prefs::kShelfAutoHideBehaviorLocal, - prefs::kShelfAutoHideBehavior); -} - -void ChromeLauncherController::OnAppSyncUIStatusChanged() { - if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING) - model_->SetStatus(ash::LauncherModel::STATUS_LOADING); - else - model_->SetStatus(ash::LauncherModel::STATUS_NORMAL); -} - -void ChromeLauncherController::ExtensionEnableFlowFinished() { - LaunchApp(extension_enable_flow_->extension_id(), ui::EF_NONE); - extension_enable_flow_.reset(); -} - -void ChromeLauncherController::ExtensionEnableFlowAborted(bool user_initiated) { - extension_enable_flow_.reset(); -} - -ChromeLauncherAppMenuItems ChromeLauncherController::GetApplicationList( - const ash::LauncherItem& item, - int event_flags) { - // Make sure that there is a controller associated with the id and that the - // extension itself is a valid application and not a panel. - if (!HasItemController(item.id) || - !GetLauncherIDForAppID(id_to_item_controller_map_[item.id]->app_id())) - return ChromeLauncherAppMenuItems().Pass(); - - return id_to_item_controller_map_[item.id]->GetApplicationList(event_flags); -} - -std::vector<content::WebContents*> -ChromeLauncherController::GetV1ApplicationsFromAppId(std::string app_id) { - ash::LauncherID id = GetLauncherIDForAppID(app_id); - - // If there is no such an item pinned to the launcher, no menu gets created. - if (id) { - LauncherItemController* controller = id_to_item_controller_map_[id]; - DCHECK(controller); - if (controller->type() == LauncherItemController::TYPE_SHORTCUT) - return GetV1ApplicationsFromController(controller); - } - return std::vector<content::WebContents*>(); -} - -void ChromeLauncherController::ActivateShellApp(const std::string& app_id, - int index) { - ash::LauncherID id = GetLauncherIDForAppID(app_id); - if (id) { - LauncherItemController* controller = id_to_item_controller_map_[id]; - if (controller->type() == LauncherItemController::TYPE_APP) { - ShellWindowLauncherItemController* shell_window_controller = - static_cast<ShellWindowLauncherItemController*>(controller); - shell_window_controller->ActivateIndexedApp(index); - } - } -} - -bool ChromeLauncherController::IsWebContentHandledByApplication( - content::WebContents* web_contents, - const std::string& app_id) { - if ((web_contents_to_app_id_.find(web_contents) != - web_contents_to_app_id_.end()) && - (web_contents_to_app_id_[web_contents] == app_id)) - return true; - return (app_id == kGmailAppId && ContentCanBeHandledByGmailApp(web_contents)); -} - -bool ChromeLauncherController::ContentCanBeHandledByGmailApp( - content::WebContents* web_contents) { - ash::LauncherID id = GetLauncherIDForAppID(kGmailAppId); - if (id) { - const GURL url = web_contents->GetURL(); - // We need to extend the application matching for the gMail app beyond the - // manifest file's specification. This is required because of the namespace - // overlap with the offline app ("/mail/mu/"). - if (!MatchPattern(url.path(), "/mail/mu/*") && - MatchPattern(url.path(), "/mail/*") && - GetExtensionForAppID(kGmailAppId) && - GetExtensionForAppID(kGmailAppId)->OverlapsWithOrigin(url)) - return true; - } - return false; -} - -gfx::Image ChromeLauncherController::GetAppListIcon( - content::WebContents* web_contents) const { - ResourceBundle& rb = ResourceBundle::GetSharedInstance(); - if (IsIncognito(web_contents)) - return rb.GetImageNamed(IDR_AURA_LAUNCHER_LIST_INCOGNITO_BROWSER); - FaviconTabHelper* favicon_tab_helper = - FaviconTabHelper::FromWebContents(web_contents); - gfx::Image result = favicon_tab_helper->GetFavicon(); - if (result.IsEmpty()) - return rb.GetImageNamed(IDR_DEFAULT_FAVICON); - return result; -} - -string16 ChromeLauncherController::GetAppListTitle( - content::WebContents* web_contents) const { - string16 title = web_contents->GetTitle(); - if (!title.empty()) - return title; - WebContentsToAppIDMap::const_iterator iter = - web_contents_to_app_id_.find(web_contents); - if (iter != web_contents_to_app_id_.end()) { - std::string app_id = iter->second; - const extensions::Extension* extension = GetExtensionForAppID(app_id); - if (extension) - return UTF8ToUTF16(extension->name()); - } - return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE); -} - -void ChromeLauncherController::OnBrowserRemoved(Browser* browser) { - // When called by a unit test it is possible that there is no shell. - // In that case, the following function should not get called. - if (ash::Shell::HasInstance()) - UpdateBrowserItemStatus(); -} - -ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItem( - const std::string& app_id, - int index) { - return CreateAppShortcutLauncherItemWithType(app_id, - index, - ash::TYPE_APP_SHORTCUT); -} - -void ChromeLauncherController::SetAppTabHelperForTest(AppTabHelper* helper) { - app_tab_helper_.reset(helper); -} - -void ChromeLauncherController::SetAppIconLoaderForTest( - extensions::AppIconLoader* loader) { - app_icon_loader_.reset(loader); -} - -const std::string& ChromeLauncherController::GetAppIdFromLauncherIdForTest( - ash::LauncherID id) { - return id_to_item_controller_map_[id]->app_id(); -} - -ash::LauncherID ChromeLauncherController::CreateAppShortcutLauncherItemWithType( - const std::string& app_id, - int index, - ash::LauncherItemType launcher_item_type) { - AppShortcutLauncherItemController* controller = - new AppShortcutLauncherItemController(app_id, this); - ash::LauncherID launcher_id = InsertAppLauncherItem( - controller, app_id, ash::STATUS_CLOSED, index, launcher_item_type); - return launcher_id; -} - -void ChromeLauncherController::UpdateBrowserItemStatus() { - // Determine the new browser's active state and change if necessary. - size_t browser_index = ash::launcher::GetBrowserItemIndex(*model_); - DCHECK_GE(browser_index, 0u); - ash::LauncherItem browser_item = model_->items()[browser_index]; - ash::LauncherItemStatus browser_status = ash::STATUS_CLOSED; - - aura::Window* window = ash::wm::GetActiveWindow(); - if (window) { - // Check if the active browser / tab is a browser which is not an app, - // a windowed app, a popup or any other item which is not a browser of - // interest. - Browser* browser = chrome::FindBrowserWithWindow(window); - if (IsBrowserRepresentedInBrowserList(browser)) { - browser_status = ash::STATUS_ACTIVE; - const ash::LauncherItems& items = model_->items(); - // If another launcher item has claimed to be active, we don't. - for (size_t i = 0; - i < items.size() && browser_status == ash::STATUS_ACTIVE; ++i) { - if (i != browser_index && items[i].status == ash::STATUS_ACTIVE) - browser_status = ash::STATUS_RUNNING; - } - } - } - - if (browser_status == ash::STATUS_CLOSED) { - const BrowserList* ash_browser_list = - BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); - for (BrowserList::const_reverse_iterator it = - ash_browser_list->begin_last_active(); - it != ash_browser_list->end_last_active() && - browser_status == ash::STATUS_CLOSED; ++it) { - if (IsBrowserRepresentedInBrowserList(*it)) - browser_status = ash::STATUS_RUNNING; - } - } - - if (browser_status != browser_item.status) { - browser_item.status = browser_status; - model_->Set(browser_index, browser_item); - } -} - -Profile* ChromeLauncherController::GetProfileForNewWindows() { - return ProfileManager::GetDefaultProfileOrOffTheRecord(); -} - -void ChromeLauncherController::LauncherItemClosed(ash::LauncherID id) { - IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); - CHECK(iter != id_to_item_controller_map_.end()); - CHECK(iter->second); - app_icon_loader_->ClearImage(iter->second->app_id()); - iter->second->OnRemoved(); - id_to_item_controller_map_.erase(iter); - int index = model_->ItemIndexByID(id); - // A "browser proxy" is not known to the model and this removal does - // therefore not need to be propagated to the model. - if (index != -1) - model_->RemoveItemAt(index); -} - -void ChromeLauncherController::DoPinAppWithID(const std::string& app_id) { - // If there is an item, do nothing and return. - if (IsAppPinned(app_id)) - return; - - ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id); - if (launcher_id) { - // App item exists, pin it - Pin(launcher_id); - } else { - // Otherwise, create a shortcut item for it. - CreateAppShortcutLauncherItem(app_id, model_->item_count()); - if (CanPin()) - PersistPinnedState(); - } -} - -void ChromeLauncherController::DoUnpinAppsWithID(const std::string& app_id) { - for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); - i != id_to_item_controller_map_.end(); ) { - IDToItemControllerMap::iterator current(i); - ++i; - if (current->second->app_id() == app_id && IsPinned(current->first)) - Unpin(current->first); - } -} - -void ChromeLauncherController::UpdateAppLaunchersFromPref() { - // Construct a vector representation of to-be-pinned apps from the pref. - std::vector<std::string> pinned_apps; - int chrome_icon_index = GetChromeIconIndexFromPref(); - int index = 0; - int max_index = model_->item_count(); - // Using the alternate shelf layout the App Icon should be the first item in - // the list thus start adding items at slot 1 (instead of slot 0). - if (ash::switches::UseAlternateShelfLayout()) { - ++index; - ++max_index; - // The alternate shelf layout's icon position will always include the - // AppLauncher which needs to be subtracted here. - if (chrome_icon_index > 0) - --chrome_icon_index; - } - const base::ListValue* pinned_apps_pref = - profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); - for (base::ListValue::const_iterator it(pinned_apps_pref->begin()); - it != pinned_apps_pref->end(); ++it) { - // To preserve the Chrome icon position, we insert a dummy slot for it - if - // the model has a Chrome item. While initializing we can come here with no - // item in which case the count would be 1 or below. - if (it - pinned_apps_pref->begin() == chrome_icon_index && - model_->item_count() > 1) { - pinned_apps.push_back(extension_misc::kChromeAppId); - } - - DictionaryValue* app = NULL; - std::string app_id; - if ((*it)->GetAsDictionary(&app) && - app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) && - std::find(pinned_apps.begin(), pinned_apps.end(), app_id) == - pinned_apps.end() && - app_tab_helper_->IsValidID(app_id)) { - pinned_apps.push_back(app_id); - } - } - - // Walk the model and |pinned_apps| from the pref lockstep, adding and - // removing items as necessary. NB: This code uses plain old indexing instead - // of iterators because of model mutations as part of the loop. - std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin()); - for (; index < max_index && pref_app_id != pinned_apps.end(); ++index) { - // If the next app launcher according to the pref is present in the model, - // delete all app launcher entries in between. - if (*pref_app_id == extension_misc::kChromeAppId || - IsAppPinned(*pref_app_id)) { - for (; index < max_index; ++index) { - const ash::LauncherItem& item(model_->items()[index]); - if (item.type != ash::TYPE_APP_SHORTCUT && - item.type != ash::TYPE_BROWSER_SHORTCUT) - continue; - - IDToItemControllerMap::const_iterator entry = - id_to_item_controller_map_.find(item.id); - if ((extension_misc::kChromeAppId == *pref_app_id && - item.type == ash::TYPE_BROWSER_SHORTCUT) || - (entry != id_to_item_controller_map_.end() && - entry->second->app_id() == *pref_app_id)) { - ++pref_app_id; - break; - } else { - if (item.type == ash::TYPE_BROWSER_SHORTCUT) { - // We cannot delete the browser shortcut. As such we move it up by - // one. To avoid any side effects from our pinned state observer, we - // do not call the model directly. - MoveItemWithoutPinnedStateChangeNotification(index, index + 1); - } else { - LauncherItemClosed(item.id); - --max_index; - } - --index; - } - } - // If the item wasn't found, that means id_to_item_controller_map_ - // is out of sync. - DCHECK(index < max_index); - } else { - // This app wasn't pinned before, insert a new entry. - ash::LauncherID id = CreateAppShortcutLauncherItem(*pref_app_id, index); - index = model_->ItemIndexByID(id); - ++pref_app_id; - } - } - - // Remove any trailing existing items. - while (index < model_->item_count()) { - const ash::LauncherItem& item(model_->items()[index]); - if (item.type == ash::TYPE_APP_SHORTCUT) - LauncherItemClosed(item.id); - else - ++index; - } - - // Append unprocessed items from the pref to the end of the model. - for (; pref_app_id != pinned_apps.end(); ++pref_app_id) { - // Ignore the chrome icon. - if (*pref_app_id != extension_misc::kChromeAppId) - DoPinAppWithID(*pref_app_id); - } - -} - -void ChromeLauncherController::SetShelfAutoHideBehaviorPrefs( - ash::ShelfAutoHideBehavior behavior, - aura::RootWindow* root_window) { - const char* value = NULL; - switch (behavior) { - case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: - value = ash::kShelfAutoHideBehaviorAlways; - break; - case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER: - value = ash::kShelfAutoHideBehaviorNever; - break; - case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN: - // This one should not be a valid preference option for now. We only want - // to completely hide it when we run app mode. - NOTREACHED(); - return; - } - - UpdatePerDisplayPref( - profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value); - - if (root_window == ash::Shell::GetPrimaryRootWindow()) { - // See comment in |kShelfAlignment| about why we have two prefs here. - profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value); - profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value); - } -} - -void ChromeLauncherController::SetShelfAutoHideBehaviorFromPrefs() { - ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); - - for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin(); - iter != root_windows.end(); ++iter) { - ash::Shell::GetInstance()->SetShelfAutoHideBehavior( - GetShelfAutoHideBehavior(*iter), *iter); - } -} - -void ChromeLauncherController::SetShelfAlignmentFromPrefs() { - if (!ash::ShelfWidget::ShelfAlignmentAllowed()) - return; - - ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); - - for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin(); - iter != root_windows.end(); ++iter) { - // See comment in |kShelfAlignment| as to why we consider two prefs. - const std::string alignment_value( - GetPrefForRootWindow(profile_->GetPrefs(), - *iter, - prefs::kShelfAlignmentLocal, - prefs::kShelfAlignment)); - ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM; - if (alignment_value == ash::kShelfAlignmentLeft) - alignment = ash::SHELF_ALIGNMENT_LEFT; - else if (alignment_value == ash::kShelfAlignmentRight) - alignment = ash::SHELF_ALIGNMENT_RIGHT; - else if (alignment_value == ash::kShelfAlignmentTop) - alignment = ash::SHELF_ALIGNMENT_TOP; - ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter); - } -} - -void ChromeLauncherController::SetShelfBehaviorsFromPrefs() { - SetShelfAutoHideBehaviorFromPrefs(); - SetShelfAlignmentFromPrefs(); -} - -WebContents* ChromeLauncherController::GetLastActiveWebContents( - const std::string& app_id) { - AppIDToWebContentsListMap::const_iterator i = - app_id_to_web_contents_list_.find(app_id); - if (i == app_id_to_web_contents_list_.end()) - return NULL; - DCHECK_GT(i->second.size(), 0u); - return *i->second.begin(); -} - -ash::LauncherID ChromeLauncherController::InsertAppLauncherItem( - LauncherItemController* controller, - const std::string& app_id, - ash::LauncherItemStatus status, - int index, - ash::LauncherItemType launcher_item_type) { - ash::LauncherID id = model_->next_id(); - CHECK(!HasItemController(id)); - CHECK(controller); - id_to_item_controller_map_[id] = controller; - controller->set_launcher_id(id); - - ash::LauncherItem item; - item.type = launcher_item_type; - item.is_incognito = false; - item.image = extensions::IconsInfo::GetDefaultAppIcon(); - - WebContents* active_tab = GetLastActiveWebContents(app_id); - if (active_tab) { - Browser* browser = chrome::FindBrowserWithWebContents(active_tab); - DCHECK(browser); - if (browser->window()->IsActive()) - status = ash::STATUS_ACTIVE; - else - status = ash::STATUS_RUNNING; - } - item.status = status; - - model_->AddAt(index, item); - - app_icon_loader_->FetchImage(app_id); - - return id; -} - -bool ChromeLauncherController::HasItemController(ash::LauncherID id) const { - return id_to_item_controller_map_.find(id) != - id_to_item_controller_map_.end(); -} - -std::vector<content::WebContents*> -ChromeLauncherController::GetV1ApplicationsFromController( - LauncherItemController* controller) { - DCHECK(controller->type() == LauncherItemController::TYPE_SHORTCUT); - AppShortcutLauncherItemController* app_controller = - static_cast<AppShortcutLauncherItemController*>(controller); - return app_controller->GetRunningApplications(); -} - -bool ChromeLauncherController::IsBrowserRepresentedInBrowserList( - Browser* browser) { - return (browser && - (browser->is_type_tabbed() || - !browser->is_app() || - !browser->is_type_popup() || - GetLauncherIDForAppID(web_app::GetExtensionIdFromApplicationName( - browser->app_name())) <= 0)); -} - -LauncherItemController* -ChromeLauncherController::GetBrowserShortcutLauncherItemController() { - for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); - i != id_to_item_controller_map_.end(); ++i) { - int index = model_->ItemIndexByID(i->first); - const ash::LauncherItem& item = model_->items()[index]; - if (item.type == ash::TYPE_BROWSER_SHORTCUT) - return i->second; - } - // LauncerItemController For Browser Shortcut must be existed. If it does not - // existe create it. - ash::LauncherID id = CreateBrowserShortcutLauncherItem(); - DCHECK(id_to_item_controller_map_[id]); - return id_to_item_controller_map_[id]; -} - -ash::LauncherID ChromeLauncherController::CreateBrowserShortcutLauncherItem() { - ash::LauncherItem browser_shortcut; - browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; - browser_shortcut.is_incognito = false; - ResourceBundle& rb = ResourceBundle::GetSharedInstance(); - browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32); - ash::LauncherID id = model_->next_id(); - size_t index = GetChromeIconIndexFromPref(); - model_->AddAt(index, browser_shortcut); - browser_item_controller_.reset( - new BrowserShortcutLauncherItemController(this, profile_)); - id_to_item_controller_map_[id] = browser_item_controller_.get(); - id_to_item_controller_map_[id]->set_launcher_id(id); - return id; -} - -void ChromeLauncherController::PersistChromeItemIndex(int index) { - profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index); -} - -int ChromeLauncherController::GetChromeIconIndexFromPref() const { - size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex); - const base::ListValue* pinned_apps_pref = - profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); - if (ash::switches::UseAlternateShelfLayout()) - return std::max(static_cast<size_t>(1), - std::min(pinned_apps_pref->GetSize() + 1, index)); - return std::max(static_cast<size_t>(0), - std::min(pinned_apps_pref->GetSize(), index)); -} - -bool ChromeLauncherController::IsIncognito( - content::WebContents* web_contents) const { - const Profile* profile = - Profile::FromBrowserContext(web_contents->GetBrowserContext()); - return profile->IsOffTheRecord() && !profile->IsGuestSession(); -} - -void ChromeLauncherController::CloseWindowedAppsFromRemovedExtension( - const std::string& app_id) { - // This function cannot rely on the controller's enumeration functionality - // since the extension has already be unloaded. - const BrowserList* ash_browser_list = - BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); - std::vector<Browser*> browser_to_close; - for (BrowserList::const_reverse_iterator - it = ash_browser_list->begin_last_active(); - it != ash_browser_list->end_last_active(); ++it) { - Browser* browser = *it; - if (!browser->is_type_tabbed() && - browser->is_type_popup() && - browser->is_app() && - app_id == web_app::GetExtensionIdFromApplicationName( - browser->app_name())) { - browser_to_close.push_back(browser); - } - } - while (!browser_to_close.empty()) { - TabStripModel* tab_strip = browser_to_close.back()->tab_strip_model(); - tab_strip->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE); - browser_to_close.pop_back(); - } -} - -void -ChromeLauncherController::MoveItemWithoutPinnedStateChangeNotification( - int source_index, int target_index) { - base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true); - model_->Move(source_index, target_index); + return instance_->GetPerAppInterface() != NULL; } diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h index 7f4cc73..dafc18d 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.h @@ -5,43 +5,20 @@ #ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_H_ #define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_H_ -#include <list> -#include <map> -#include <set> #include <string> -#include <vector> -#include "ash/display/display_controller.h" #include "ash/launcher/launcher_delegate.h" -#include "ash/launcher/launcher_model_observer.h" #include "ash/launcher/launcher_types.h" -#include "ash/shelf/shelf_layout_manager_observer.h" #include "ash/shelf/shelf_types.h" -#include "ash/shell_observer.h" -#include "base/basictypes.h" -#include "base/compiler_specific.h" -#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" -#include "base/prefs/pref_change_registrar.h" #include "chrome/browser/extensions/app_icon_loader.h" #include "chrome/browser/extensions/extension_prefs.h" -#include "chrome/browser/prefs/pref_service_syncable_observer.h" -#include "chrome/browser/ui/ash/app_sync_ui_state_observer.h" -#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" -#include "chrome/browser/ui/browser_list_observer.h" -#include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h" -#include "content/public/browser/notification_observer.h" -#include "ui/aura/window_observer.h" - -class AppSyncUIState; -class Browser; -class BrowserShortcutLauncherItemController; -class ExtensionEnableFlow; -class GURL; + +class LauncherItemControllerPerAppTest; class LauncherItemController; class Profile; -class ShellWindowLauncherController; -class TabContents; +class ChromeLauncherAppMenuItem; +class ChromeLauncherControllerPerApp; namespace ash { class LauncherModel; @@ -49,10 +26,10 @@ class LauncherModel; namespace aura { class Window; +class RootWindow; } namespace content { -class NotificationRegistrar; class WebContents; } @@ -65,23 +42,17 @@ typedef ScopedVector<ChromeLauncherAppMenuItem> ChromeLauncherAppMenuItems; // ChromeLauncherController manages the launcher items needed for content // windows. Launcher items have a type, an optional app id, and a controller. -// This incarnation groups running tabs/windows in application specific lists. -// * Browser app windows have BrowserLauncherItemController, owned by the -// BrowserView instance. +// ChromeLauncherController will furthermore create the particular +// implementation of interest - either sorting by application (new) or sorting +// by browser (old). +// * Tabbed browsers and browser app windows have BrowserLauncherItemController, +// owned by the BrowserView instance. // * App shell windows have ShellWindowLauncherItemController, owned by // ShellWindowLauncherController. // * Shortcuts have no LauncherItemController. -class ChromeLauncherController : public ash::LauncherDelegate, - public ash::LauncherModelObserver, - public ash::ShellObserver, - public ash::DisplayController::Observer, - public content::NotificationObserver, - public extensions::AppIconLoader::Delegate, - public PrefServiceSyncableObserver, - public AppSyncUIStateObserver, - public ExtensionEnableFlowDelegate, - public chrome::BrowserListObserver, - public ash::ShelfLayoutManagerObserver { +class ChromeLauncherController + : public ash::LauncherDelegate, + public extensions::AppIconLoader::Delegate { public: // Indicates if a launcher item is incognito or not. enum IncognitoState { @@ -111,11 +82,16 @@ class ChromeLauncherController : public ash::LauncherDelegate, virtual bool IsValidID(const std::string& id) = 0; }; - ChromeLauncherController(Profile* profile, ash::LauncherModel* model); + ChromeLauncherController() {} virtual ~ChromeLauncherController(); // Initializes this ChromeLauncherController. - void Init(); + virtual void Init() = 0; + + // Returns the new per application interface of the given launcher. If it is + // a per browser (old) controller, it will return NULL; + // TODO(skuhne): Remove when we rip out the old launcher. + virtual ChromeLauncherControllerPerApp* GetPerAppInterface() = 0; // Creates an instance. static ChromeLauncherController* CreateInstance(Profile* profile, @@ -124,391 +100,211 @@ class ChromeLauncherController : public ash::LauncherDelegate, // Returns the single ChromeLauncherController instance. static ChromeLauncherController* instance() { return instance_; } + // Creates a new tabbed item on the launcher for |controller|. + virtual ash::LauncherID CreateTabbedLauncherItem( + LauncherItemController* controller, + IncognitoState is_incognito, + ash::LauncherItemStatus status) = 0; + // Creates a new app item on the launcher for |controller|. - ash::LauncherID CreateAppLauncherItem(LauncherItemController* controller, - const std::string& app_id, - ash::LauncherItemStatus status); + virtual ash::LauncherID CreateAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status) = 0; - // Updates the running status of an item. It will also update the status of - // browsers launcher item if needed. - void SetItemStatus(ash::LauncherID id, ash::LauncherItemStatus status); + // Updates the running status of an item. + virtual void SetItemStatus(ash::LauncherID id, + ash::LauncherItemStatus status) = 0; // Updates the controller associated with id (which should be a shortcut). // |controller| remains owned by caller. - void SetItemController(ash::LauncherID id, - LauncherItemController* controller); + virtual void SetItemController(ash::LauncherID id, + LauncherItemController* controller) = 0; // Closes or unpins the launcher item. - void CloseLauncherItem(ash::LauncherID id); + virtual void CloseLauncherItem(ash::LauncherID id) = 0; // Pins the specified id. Currently only supports platform apps. - void Pin(ash::LauncherID id); + virtual void Pin(ash::LauncherID id) = 0; // Unpins the specified id, closing if not running. - void Unpin(ash::LauncherID id); + virtual void Unpin(ash::LauncherID id) = 0; // Returns true if the item identified by |id| is pinned. - bool IsPinned(ash::LauncherID id); + virtual bool IsPinned(ash::LauncherID id) = 0; // Pins/unpins the specified id. - void TogglePinned(ash::LauncherID id); + virtual void TogglePinned(ash::LauncherID id) = 0; // Returns true if the specified item can be pinned or unpinned. Only apps can // be pinned. - bool IsPinnable(ash::LauncherID id) const; + virtual bool IsPinnable(ash::LauncherID id) const = 0; // If there is no launcher item in the launcher for application |app_id|, one // gets created. The (existing or created) launcher items get then locked // against a users un-pinning removal. - void LockV1AppWithID(const std::string& app_id); + virtual void LockV1AppWithID(const std::string& app_id) = 0; // A previously locked launcher item of type |app_id| gets unlocked. If the // lock count reaches 0 and the item is not pinned it will go away. - void UnlockV1AppWithID(const std::string& app_id); + virtual void UnlockV1AppWithID(const std::string& app_id) = 0; // Requests that the launcher item controller specified by |id| open a new // instance of the app. |event_flags| holds the flags of the event which // triggered this command. - void Launch(ash::LauncherID id, int event_flags); + virtual void Launch(ash::LauncherID id, int event_flags) = 0; // Closes the specified item. - void Close(ash::LauncherID id); + virtual void Close(ash::LauncherID id) = 0; // Returns true if the specified item is open. - bool IsOpen(ash::LauncherID id); + virtual bool IsOpen(ash::LauncherID id) = 0; // Returns true if the specified item is for a platform app. - bool IsPlatformApp(ash::LauncherID id); + virtual bool IsPlatformApp(ash::LauncherID id) = 0; // Opens a new instance of the application identified by |app_id|. // Used by the app-list, and by pinned-app launcher items. - void LaunchApp(const std::string& app_id, int event_flags); + virtual void LaunchApp(const std::string& app_id, int event_flags) = 0; // If |app_id| is running, reactivates the app's most recently active window, // otherwise launches and activates the app. // Used by the app-list, and by pinned-app launcher items. - void ActivateApp(const std::string& app_id, int event_flags); + virtual void ActivateApp(const std::string& app_id, int event_flags) = 0; // Returns the launch type of app for the specified id. - extensions::ExtensionPrefs::LaunchType GetLaunchType(ash::LauncherID id); + virtual extensions::ExtensionPrefs::LaunchType GetLaunchType( + ash::LauncherID id) = 0; // Returns the id of the app for the specified tab. - std::string GetAppID(content::WebContents* tab); + virtual std::string GetAppID(content::WebContents* tab) = 0; + + // Returns the launcherID of the first non-panel item whose app_id + // matches |app_id| or 0 if none match. + virtual ash::LauncherID GetLauncherIDForAppID(const std::string& app_id) = 0; - std::string GetAppIDForLauncherID(ash::LauncherID id); + // Returns the id of the app for the specified id (which must exist). + virtual std::string GetAppIDForLauncherID(ash::LauncherID id) = 0; // Set the image for a specific launcher item (e.g. when set by the app). - void SetLauncherItemImage(ash::LauncherID launcher_id, - const gfx::ImageSkia& image); + virtual void SetLauncherItemImage(ash::LauncherID launcher_id, + const gfx::ImageSkia& image) = 0; - // Find out if the given application |id| is a windowed app item and not a - // pinned item in the launcher. - bool IsWindowedAppInLauncher(const std::string& app_id); + // Returns true if a pinned launcher item with given |app_id| could be found. + virtual bool IsAppPinned(const std::string& app_id) = 0; + + // Pins an app with |app_id| to launcher. If there is a running instance in + // launcher, the running instance is pinned. If there is no running instance, + // a new launcher item is created and pinned. + virtual void PinAppWithID(const std::string& app_id) = 0; // Updates the launche type of the app for the specified id to |launch_type|. - void SetLaunchType(ash::LauncherID id, - extensions::ExtensionPrefs::LaunchType launch_type); + virtual void SetLaunchType( + ash::LauncherID id, + extensions::ExtensionPrefs::LaunchType launch_type) = 0; + + // Unpins any app items whose id is |app_id|. + virtual void UnpinAppsWithID(const std::string& app_id) = 0; // Returns true if the user is currently logged in as a guest. - // Makes virtual for unittest in LauncherContextMenuTest. - virtual bool IsLoggedInAsGuest(); + virtual bool IsLoggedInAsGuest() = 0; // Invoked when user clicks on button in the launcher and there is no last // used window (or CTRL is held with the click). - void CreateNewWindow(); + virtual void CreateNewWindow() = 0; // Invoked when the user clicks on button in the launcher to create a new // incognito window. - void CreateNewIncognitoWindow(); + virtual void CreateNewIncognitoWindow() = 0; // Checks whether the user is allowed to pin apps. Pinning may be disallowed // by policy in case there is a pre-defined set of pinned apps. - bool CanPin() const; + virtual bool CanPin() const = 0; // Updates the pinned pref state. The pinned state consists of a list pref. // Each item of the list is a dictionary. The key |kAppIDPath| gives the // id of the app. - void PersistPinnedState(); + virtual void PersistPinnedState() = 0; - ash::LauncherModel* model(); + virtual ash::LauncherModel* model() = 0; - Profile* profile(); + virtual Profile* profile() = 0; // Gets the shelf auto-hide behavior on |root_window|. - ash::ShelfAutoHideBehavior GetShelfAutoHideBehavior( - aura::RootWindow* root_window) const; + virtual ash::ShelfAutoHideBehavior GetShelfAutoHideBehavior( + aura::RootWindow* root_window) const = 0; // Returns |true| if the user is allowed to modify the shelf auto-hide // behavior on |root_window|. - bool CanUserModifyShelfAutoHideBehavior(aura::RootWindow* root_window) const; + virtual bool CanUserModifyShelfAutoHideBehavior( + aura::RootWindow* root_window) const = 0; // Toggles the shelf auto-hide behavior on |root_window|. Does nothing if the // user is not allowed to modify the auto-hide behavior. - void ToggleShelfAutoHideBehavior(aura::RootWindow* root_window); + virtual void ToggleShelfAutoHideBehavior(aura::RootWindow* root_window) = 0; // The tab no longer represents its previously identified application. - void RemoveTabFromRunningApp(content::WebContents* tab, - const std::string& app_id); + virtual void RemoveTabFromRunningApp(content::WebContents* tab, + const std::string& app_id) = 0; // Notify the controller that the state of an non platform app's tabs // have changed, - void UpdateAppState(content::WebContents* contents, AppState app_state); + virtual void UpdateAppState(content::WebContents* contents, + AppState app_state) = 0; // Limits application refocusing to urls that match |url| for |id|. - void SetRefocusURLPatternForTest(ash::LauncherID id, const GURL& url); + virtual void SetRefocusURLPatternForTest(ash::LauncherID id, + const GURL& url) = 0; // Returns the extension identified by |app_id|. - const extensions::Extension* GetExtensionForAppID( - const std::string& app_id) const; + virtual const extensions::Extension* GetExtensionForAppID( + const std::string& app_id) const = 0; // Activates a |window|. If |allow_minimize| is true and the system allows // it, the the window will get minimized instead. - void ActivateWindowOrMinimizeIfActive(ui::BaseWindow* window, - bool allow_minimize); - + virtual void ActivateWindowOrMinimizeIfActive(ui::BaseWindow* window, + bool allow_minimize) = 0; // ash::LauncherDelegate overrides: virtual void ItemSelected(const ash::LauncherItem& item, - const ui::Event& event) OVERRIDE; - virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE; + const ui::Event& event) OVERRIDE = 0; + virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE = 0; virtual ui::MenuModel* CreateContextMenu( - const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE; + const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE = 0; virtual ash::LauncherMenuModel* CreateApplicationMenu( const ash::LauncherItem& item, - int event_flags) OVERRIDE; - virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE; - virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE; - virtual bool ShouldShowTooltip(const ash::LauncherItem& item) OVERRIDE; - virtual void OnLauncherCreated(ash::Launcher* launcher) OVERRIDE; - virtual void OnLauncherDestroyed(ash::Launcher* launcher) OVERRIDE; - virtual ash::LauncherID GetLauncherIDForAppID( - const std::string& app_id) OVERRIDE; - virtual void PinAppWithID(const std::string& app_id) OVERRIDE; - virtual bool IsAppPinned(const std::string& app_id) OVERRIDE; - virtual void UnpinAppsWithID(const std::string& app_id) OVERRIDE; - - // ash::LauncherModelObserver overrides: - virtual void LauncherItemAdded(int index) OVERRIDE; - virtual void LauncherItemRemoved(int index, ash::LauncherID id) OVERRIDE; - virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE; - virtual void LauncherItemChanged(int index, - const ash::LauncherItem& old_item) OVERRIDE; - virtual void LauncherStatusChanged() OVERRIDE; - - // content::NotificationObserver overrides: - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; - - // ash::ShellObserver overrides: - virtual void OnShelfAlignmentChanged(aura::RootWindow* root_window) OVERRIDE; - - // ash::DisplayController::Observer overrides: - virtual void OnDisplayConfigurationChanging() OVERRIDE; - virtual void OnDisplayConfigurationChanged() OVERRIDE; - - // PrefServiceSyncableObserver overrides: - virtual void OnIsSyncingChanged() OVERRIDE; - - // AppSyncUIStateObserver overrides: - virtual void OnAppSyncUIStatusChanged() OVERRIDE; - - // ExtensionEnableFlowDelegate overrides: - virtual void ExtensionEnableFlowFinished() OVERRIDE; - virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE; + int event_flags) OVERRIDE = 0; + virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE = 0; + virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE = 0; + virtual bool ShouldShowTooltip(const ash::LauncherItem& item) OVERRIDE = 0; + virtual bool IsPerAppLauncher() OVERRIDE; // extensions::AppIconLoader overrides: virtual void SetAppImage(const std::string& app_id, - const gfx::ImageSkia& image) OVERRIDE; - - // ash::ShelfLayoutManagerObserver overrides: - virtual void OnAutoHideBehaviorChanged( - aura::RootWindow* root_window, - ash::ShelfAutoHideBehavior new_behavior) OVERRIDE; - - // Get the list of all running incarnations of this item. - // |event_flags| specifies the flags which were set by the event which - // triggered this menu generation. It can be used to generate different lists. - ChromeLauncherAppMenuItems GetApplicationList(const ash::LauncherItem& item, - int event_flags); - - // Get the list of all tabs which belong to a certain application type. - std::vector<content::WebContents*> GetV1ApplicationsFromAppId( - std::string app_id); - - // Activates a specified shell application. - void ActivateShellApp(const std::string& app_id, int index); - - // Checks if a given |web_contents| is known to be associated with an - // application of type |app_id|. - bool IsWebContentHandledByApplication(content::WebContents* web_contents, - const std::string& app_id); - - // Check if the gMail app is loaded and it can handle the given web content. - // This special treatment is required to address crbug.com/234268. - bool ContentCanBeHandledByGmailApp(content::WebContents* web_contents); - - // Get the favicon for the application list entry for |web_contents|. - // Note that for incognito windows the incognito icon will be returned. - // If |web_contents| has not loaded, returns the default favicon. - gfx::Image GetAppListIcon(content::WebContents* web_contents) const; - - // Get the title for the applicatoin list entry for |web_contents|. - // If |web_contents| has not loaded, returns "Net Tab". - string16 GetAppListTitle(content::WebContents* web_contents) const; - - // Overridden from chrome::BrowserListObserver. - virtual void OnBrowserRemoved(Browser* browser) OVERRIDE; - - // Returns true when the given |browser| is listed in the browser application - // list. - bool IsBrowserRepresentedInBrowserList(Browser* browser); - - // Returns the LauncherItemController of BrowserShortcut. - LauncherItemController* GetBrowserShortcutLauncherItemController(); + const gfx::ImageSkia& image) OVERRIDE = 0; protected: - // Creates a new app shortcut item and controller on the launcher at |index|. - // Use kInsertItemAtEnd to add a shortcut as the last item. - ash::LauncherID CreateAppShortcutLauncherItem(const std::string& app_id, - int index); - - // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class. - // These are intended for testing. - void SetAppTabHelperForTest(AppTabHelper* helper); - void SetAppIconLoaderForTest(extensions::AppIconLoader* loader); - const std::string& GetAppIdFromLauncherIdForTest(ash::LauncherID id); - - private: - friend class ChromeLauncherControllerTest; - friend class LauncherAppBrowserTest; + friend class LauncherItemControllerPerAppTest; friend class LauncherPlatformAppBrowserTest; - - typedef std::map<ash::LauncherID, LauncherItemController*> - IDToItemControllerMap; - typedef std::list<content::WebContents*> WebContentsList; - typedef std::map<std::string, WebContentsList> AppIDToWebContentsListMap; - typedef std::map<content::WebContents*, std::string> WebContentsToAppIDMap; + friend class LauncherAppBrowserTest; + // TODO(skuhne): Remove these when the old launcher get removed. + friend class LauncherPlatformPerAppAppBrowserTest; + friend class LauncherPerAppAppBrowserTest; // Creates a new app shortcut item and controller on the launcher at |index|. // Use kInsertItemAtEnd to add a shortcut as the last item. - ash::LauncherID CreateAppShortcutLauncherItemWithType( - const std::string& app_id, - int index, - ash::LauncherItemType launcher_item_type); - - // Updates the activation state of the Broswer item. - void UpdateBrowserItemStatus(); - - // Returns the profile used for new windows. - Profile* GetProfileForNewWindows(); - - // Invoked when the associated browser or app is closed. - void LauncherItemClosed(ash::LauncherID id); - - // Internal helpers for pinning and unpinning that handle both - // client-triggered and internal pinning operations. - void DoPinAppWithID(const std::string& app_id); - void DoUnpinAppsWithID(const std::string& app_id); - - // Re-syncs launcher model with prefs::kPinnedLauncherApps. - void UpdateAppLaunchersFromPref(); - - // Persists the shelf auto-hide behavior to prefs. - void SetShelfAutoHideBehaviorPrefs(ash::ShelfAutoHideBehavior behavior, - aura::RootWindow* root_window); - - // Sets the shelf auto-hide behavior from prefs. - void SetShelfAutoHideBehaviorFromPrefs(); - - // Sets the shelf alignment from prefs. - void SetShelfAlignmentFromPrefs(); - - // Sets both of auto-hide behavior and alignment from prefs. - void SetShelfBehaviorsFromPrefs(); - - // Returns the most recently active web contents for an app. - content::WebContents* GetLastActiveWebContents(const std::string& app_id); - - // Creates an app launcher to insert at |index|. Note that |index| may be - // adjusted by the model to meet ordering constraints. - // The |launcher_item_type| will be set into the LauncherModel. - ash::LauncherID InsertAppLauncherItem( - LauncherItemController* controller, + virtual ash::LauncherID CreateAppShortcutLauncherItem( const std::string& app_id, - ash::LauncherItemStatus status, - int index, - ash::LauncherItemType launcher_item_type); + int index) = 0; - bool HasItemController(ash::LauncherID id) const; - - // Enumerate all Web contents which match a given shortcut |controller|. - std::vector<content::WebContents*> GetV1ApplicationsFromController( - LauncherItemController* controller); - - // Create LauncherItem for Browser Shortcut. - ash::LauncherID CreateBrowserShortcutLauncherItem(); - - // Check if the given |web_contents| is in incognito mode. - bool IsIncognito(content::WebContents* web_contents) const; - - // Update browser shortcut's index. - void PersistChromeItemIndex(int index); - - // Get browser shortcut's index from pref. - int GetChromeIconIndexFromPref() const; - - // Close all windowed V1 applications of a certain extension which was already - // deleted. - void CloseWindowedAppsFromRemovedExtension(const std::string& app_id); - - // Move a launcher item ignoring the pinned state changes from |index| to - // |target_index|. - void MoveItemWithoutPinnedStateChangeNotification(int source_index, - int target_index); + // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class. + // These are intended for testing. + virtual void SetAppTabHelperForTest(AppTabHelper* helper) = 0; + virtual void SetAppIconLoaderForTest(extensions::AppIconLoader* loader) = 0; + virtual const std::string& GetAppIdFromLauncherIdForTest( + ash::LauncherID id) = 0; + private: static ChromeLauncherController* instance_; - - ash::LauncherModel* model_; - - // Profile used for prefs and loading extensions. This is NOT necessarily the - // profile new windows are created with. - Profile* profile_; - - IDToItemControllerMap id_to_item_controller_map_; - - // Maintains activation order of web contents for each app. - AppIDToWebContentsListMap app_id_to_web_contents_list_; - - // Direct access to app_id for a web contents. - WebContentsToAppIDMap web_contents_to_app_id_; - - // Used to track shell windows. - scoped_ptr<ShellWindowLauncherController> shell_window_controller_; - - // Used to get app info for tabs. - scoped_ptr<AppTabHelper> app_tab_helper_; - - // Used to load the image for an app item. - scoped_ptr<extensions::AppIconLoader> app_icon_loader_; - - content::NotificationRegistrar notification_registrar_; - - PrefChangeRegistrar pref_change_registrar_; - - AppSyncUIState* app_sync_ui_state_; - - scoped_ptr<ExtensionEnableFlow> extension_enable_flow_; - - // Launchers that are currently being observed. - std::set<ash::Launcher*> launchers_; - - // The owned browser shortcut item. - scoped_ptr<BrowserShortcutLauncherItemController> browser_item_controller_; - - // If true, incoming pinned state changes should be ignored. - bool ignore_persist_pinned_state_change_; - - DISALLOW_COPY_AND_ASSIGN(ChromeLauncherController); }; #endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_H_ diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc index abf0408..c68476c 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc @@ -8,15 +8,13 @@ #include "apps/shell_window.h" #include "apps/shell_window_registry.h" #include "ash/ash_switches.h" -#include "ash/display/display_controller.h" #include "ash/launcher/launcher.h" #include "ash/launcher/launcher_model.h" -#include "ash/launcher/launcher_util.h" -#include "ash/launcher/launcher_view.h" #include "ash/shell.h" #include "ash/test/launcher_view_test_api.h" #include "ash/test/shell_test_api.h" #include "ash/wm/window_util.h" +#include "base/command_line.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/apps/app_browsertest_util.h" @@ -29,13 +27,8 @@ #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/extension_test_message_listener.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ui/app_list/app_list_service.h" -#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h" -#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" -#include "chrome/browser/ui/browser_finder.h" -#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/browser/ui/host_desktop.h" @@ -46,81 +39,37 @@ #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" -#include "extensions/common/switches.h" #include "testing/gtest/include/gtest/gtest.h" -#include "ui/app_list/views/apps_grid_view.h" #include "ui/aura/client/aura_constants.h" -#include "ui/aura/test/event_generator.h" #include "ui/aura/window.h" -#include "ui/base/events/event.h" using apps::ShellWindow; using extensions::Extension; using content::WebContents; -namespace { - -class TestEvent : public ui::Event { - public: - explicit TestEvent(ui::EventType type) - : ui::Event(type, base::TimeDelta(), 0) { - } - virtual ~TestEvent() { - } - - private: - DISALLOW_COPY_AND_ASSIGN(TestEvent); -}; - -class TestShellWindowRegistryObserver - : public apps::ShellWindowRegistry::Observer { - public: - explicit TestShellWindowRegistryObserver(Profile* profile) - : profile_(profile), - icon_updates_(0) { - apps::ShellWindowRegistry::Get(profile_)->AddObserver(this); - } - - virtual ~TestShellWindowRegistryObserver() { - apps::ShellWindowRegistry::Get(profile_)->RemoveObserver(this); - } - - // Overridden from ShellWindowRegistry::Observer: - virtual void OnShellWindowAdded(ShellWindow* shell_window) OVERRIDE {} - - virtual void OnShellWindowIconChanged(ShellWindow* shell_window) OVERRIDE { - ++icon_updates_; - } - - virtual void OnShellWindowRemoved(ShellWindow* shell_window) OVERRIDE {} - - int icon_updates() { return icon_updates_; } - - private: - Profile* profile_; - int icon_updates_; - - DISALLOW_COPY_AND_ASSIGN(TestShellWindowRegistryObserver); -}; - -} // namespace - class LauncherPlatformAppBrowserTest : public extensions::PlatformAppBrowserTest { protected: - LauncherPlatformAppBrowserTest() : launcher_(NULL), controller_(NULL) { + LauncherPlatformAppBrowserTest() + : launcher_(NULL), + controller_(NULL) { } virtual ~LauncherPlatformAppBrowserTest() {} + ash::LauncherModel* launcher_model() { + return ash::test::ShellTestApi(ash::Shell::GetInstance()).launcher_model(); + } + virtual void RunTestOnMainThreadLoop() OVERRIDE { launcher_ = ash::Launcher::ForPrimaryDisplay(); - controller_ = ChromeLauncherController::instance(); + controller_ = static_cast<ChromeLauncherController*>(launcher_->delegate()); return extensions::PlatformAppBrowserTest::RunTestOnMainThreadLoop(); } - ash::LauncherModel* launcher_model() { - return ash::test::ShellTestApi(ash::Shell::GetInstance()).launcher_model(); + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + PlatformAppBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(ash::switches::kAshDisablePerAppLauncher); } ash::LauncherID CreateAppShortcutLauncherItem(const std::string& name) { @@ -130,7 +79,7 @@ class LauncherPlatformAppBrowserTest const ash::LauncherItem& GetLastLauncherItem() { // Unless there are any panels, the item at index [count - 1] will be - // the app list, and the item at [count - 2] will be the desired item. + // the app list, and the item at [count - 2] will be the desited item. return launcher_model()->items()[launcher_model()->item_count() - 2]; } @@ -140,39 +89,15 @@ class LauncherPlatformAppBrowserTest return launcher_model()->items()[launcher_model()->item_count() - 1]; } - LauncherItemController* GetItemController(ash::LauncherID id) { - return controller_->id_to_item_controller_map_[id]; - } - - // Returns the number of menu items, ignoring separators. - int GetNumApplicationMenuItems(const ash::LauncherItem& item) { - const int event_flags = 0; - scoped_ptr<ash::LauncherMenuModel> menu( - controller_->CreateApplicationMenu(item, event_flags)); - int num_items = 0; - for (int i = 0; i < menu->GetItemCount(); ++i) { - if (menu->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) - ++num_items; - } - return num_items; - } - - // Activate the launcher item with the given |id|. - void ActivateLauncherItem(int id) { - launcher_->ActivateLauncherItem(id); - } - ash::Launcher* launcher_; ChromeLauncherController* controller_; - - private: - - DISALLOW_COPY_AND_ASSIGN(LauncherPlatformAppBrowserTest); }; class LauncherAppBrowserTest : public ExtensionBrowserTest { protected: - LauncherAppBrowserTest() : launcher_(NULL), model_(NULL), controller_(NULL) { + LauncherAppBrowserTest() + : launcher_(NULL), + model_(NULL) { } virtual ~LauncherAppBrowserTest() {} @@ -181,17 +106,12 @@ class LauncherAppBrowserTest : public ExtensionBrowserTest { launcher_ = ash::Launcher::ForPrimaryDisplay(); model_ = ash::test::ShellTestApi(ash::Shell::GetInstance()).launcher_model(); - controller_ = ChromeLauncherController::instance(); return ExtensionBrowserTest::RunTestOnMainThreadLoop(); } - size_t NumberOfDetectedLauncherBrowsers(bool show_all_tabs) { - LauncherItemController* item_controller = - controller_->GetBrowserShortcutLauncherItemController(); - int items = item_controller->GetApplicationList( - show_all_tabs ? ui::EF_SHIFT_DOWN : 0).size(); - // If we have at least one item, we have also a title which we remove here. - return items ? (items - 1) : 0; + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + ExtensionBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(ash::switches::kAshDisablePerAppLauncher); } const Extension* LoadAndLaunchExtension( @@ -201,21 +121,19 @@ class LauncherAppBrowserTest : public ExtensionBrowserTest { EXPECT_TRUE(LoadExtension(test_data_dir_.AppendASCII(name))); ExtensionService* service = extensions::ExtensionSystem::Get( - profile())->extension_service(); + browser()->profile())->extension_service(); const Extension* extension = service->GetExtensionById(last_loaded_extension_id_, false); EXPECT_TRUE(extension); - chrome::OpenApplication(chrome::AppLaunchParams(profile(), - extension, - container, - disposition)); + chrome::OpenApplication(chrome::AppLaunchParams( + browser()->profile(), extension, container, disposition)); return extension; } ash::LauncherID CreateShortcut(const char* name) { ExtensionService* service = extensions::ExtensionSystem::Get( - profile())->extension_service(); + browser()->profile())->extension_service(); LoadExtension(test_data_dir_.AppendASCII(name)); // First get app_id. @@ -224,72 +142,29 @@ class LauncherAppBrowserTest : public ExtensionBrowserTest { const std::string app_id = extension->id(); // Then create a shortcut. + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); int item_count = model_->item_count(); - ash::LauncherID shortcut_id = controller_->CreateAppShortcutLauncherItem( - app_id, - item_count); - controller_->PersistPinnedState(); + ash::LauncherID shortcut_id = controller->CreateAppShortcutLauncherItem( + app_id, item_count); + controller->PersistPinnedState(); EXPECT_EQ(++item_count, model_->item_count()); - const ash::LauncherItem& item = *model_->ItemByID(shortcut_id); + ash::LauncherItem item = *model_->ItemByID(shortcut_id); EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item.type); return item.id; } - // Activate the launcher item with the given |id|. - void ActivateLauncherItem(int id) { - launcher_->ActivateLauncherItem(id); - } - ash::LauncherID PinFakeApp(const std::string& name) { - return controller_->CreateAppShortcutLauncherItem( + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + return controller->CreateAppShortcutLauncherItem( name, model_->item_count()); } ash::Launcher* launcher_; ash::LauncherModel* model_; - ChromeLauncherController* controller_; - - private: - - DISALLOW_COPY_AND_ASSIGN(LauncherAppBrowserTest); }; -class LauncherAppBrowserTestNoDefaultBrowser : public LauncherAppBrowserTest { - protected: - LauncherAppBrowserTestNoDefaultBrowser() {} - virtual ~LauncherAppBrowserTestNoDefaultBrowser() {} - - virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { - LauncherAppBrowserTest::SetUpCommandLine(command_line); - command_line->AppendSwitch(switches::kNoStartupWindow); - } - - private: - - DISALLOW_COPY_AND_ASSIGN(LauncherAppBrowserTestNoDefaultBrowser); -}; - -// Since the default for minimizing on click might change, I added both classes -// to either get the minimize on click or not. -class LauncherAppBrowserNoMinimizeOnClick - : public LauncherPlatformAppBrowserTest { - protected: - LauncherAppBrowserNoMinimizeOnClick() {} - virtual ~LauncherAppBrowserNoMinimizeOnClick() {} - - virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { - LauncherPlatformAppBrowserTest::SetUpCommandLine(command_line); - command_line->AppendSwitch( - switches::kDisableMinimizeOnSecondLauncherItemClick); - } - - private: - - DISALLOW_COPY_AND_ASSIGN(LauncherAppBrowserNoMinimizeOnClick); -}; - -typedef LauncherPlatformAppBrowserTest LauncherAppBrowserMinimizeOnClick; - // Test that we can launch a platform app and get a running item. IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, LaunchUnpinned) { int item_count = launcher_model()->item_count(); @@ -435,7 +310,6 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, MultipleWindows) { ash::LauncherID item_id = item1.id; EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); - EXPECT_EQ(2, GetNumApplicationMenuItems(item1)); // Title + 1 window // Add second window. ShellWindow* window2 = CreateShellWindow(extension); @@ -443,7 +317,6 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, MultipleWindows) { ASSERT_EQ(item_count, launcher_model()->item_count()); const ash::LauncherItem& item2 = *launcher_model()->ItemByID(item_id); EXPECT_EQ(ash::STATUS_ACTIVE, item2.status); - EXPECT_EQ(3, GetNumApplicationMenuItems(item2)); // Title + 2 windows // Close second window. CloseShellWindow(window2); @@ -451,7 +324,6 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, MultipleWindows) { ASSERT_EQ(item_count, launcher_model()->item_count()); const ash::LauncherItem& item3 = *launcher_model()->ItemByID(item_id); EXPECT_EQ(ash::STATUS_ACTIVE, item3.status); - EXPECT_EQ(2, GetNumApplicationMenuItems(item3)); // Title + 1 window // Close first window. CloseShellWindow(window1); @@ -501,9 +373,33 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, MultipleApps) { ASSERT_EQ(item_count, launcher_model()->item_count()); } +// Test that we can launch a platform app panel and get a running item. +IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, LaunchPanelWindow) { + int item_count = launcher_model()->item_count(); + const Extension* extension = LoadAndLaunchPlatformApp("launch"); + ShellWindow::CreateParams params; + params.window_type = ShellWindow::WINDOW_TYPE_PANEL; + params.focused = false; + ShellWindow* window = CreateShellWindowFromParams(extension, params); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item = GetLastLauncherPanelItem(); + EXPECT_EQ(ash::TYPE_APP_PANEL, item.type); + // Opening a panel does not activate it. + EXPECT_EQ(ash::STATUS_RUNNING, item.status); + CloseShellWindow(window); + --item_count; + EXPECT_EQ(item_count, launcher_model()->item_count()); +} + +#if defined(OS_CHROMEOS) +#define MAYBE_WindowActivation DISABLED_WindowActivation +#else +#define MAYBE_WindowActivation WindowActivation +#endif // Confirm that app windows can be reactivated by clicking their icons and that // the correct activation order is maintained. -IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, WindowActivation) { +IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, MAYBE_WindowActivation) { int item_count = launcher_model()->item_count(); // First run app. @@ -531,7 +427,7 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, WindowActivation) { launcher_model()->ItemByID(item_id1)->status); // Activate first one. - ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + launcher_->ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); EXPECT_EQ(ash::STATUS_ACTIVE, launcher_model()->ItemByID(item_id1)->status); EXPECT_EQ(ash::STATUS_RUNNING, launcher_model()->ItemByID(item_id2)->status); @@ -539,7 +435,7 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, WindowActivation) { EXPECT_FALSE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); // Activate second one. - ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id2)); + launcher_->ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id2)); EXPECT_EQ(ash::STATUS_RUNNING, launcher_model()->ItemByID(item_id1)->status); EXPECT_EQ(ash::STATUS_ACTIVE, launcher_model()->ItemByID(item_id2)->status); @@ -554,23 +450,23 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, WindowActivation) { EXPECT_TRUE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); // Activate launcher item for app1, this will activate the first app window. - ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + launcher_->ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); EXPECT_TRUE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); EXPECT_FALSE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); - ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); - EXPECT_TRUE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); + launcher_->ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + EXPECT_TRUE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); // Activate the second app again - ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id2)); + launcher_->ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id2)); EXPECT_FALSE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); EXPECT_TRUE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); EXPECT_FALSE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); // Activate the first app again - ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); - EXPECT_TRUE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); + launcher_->ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + EXPECT_TRUE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); EXPECT_FALSE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); - EXPECT_FALSE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); + EXPECT_FALSE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); // Close second app. CloseShellWindow(window2); @@ -586,138 +482,6 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, WindowActivation) { EXPECT_EQ(item_count, launcher_model()->item_count()); } -// Confirm that Click behavior for app windows is correnct. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserNoMinimizeOnClick, - AppClickBehavior) { - // Launch a platform app and create a window for it. - const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); - ShellWindow* window1 = CreateShellWindow(extension1); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - // Confirm that a controller item was created and is the correct state. - const ash::LauncherItem& item1 = GetLastLauncherItem(); - LauncherItemController* item1_controller = GetItemController(item1.id); - EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); - EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); - EXPECT_EQ(LauncherItemController::TYPE_APP, item1_controller->type()); - // Clicking the item should have no effect. - TestEvent click_event(ui::ET_MOUSE_PRESSED); - item1_controller->Clicked(click_event); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - // Minimize the window and confirm that the controller item is updated. - window1->GetBaseWindow()->Minimize(); - EXPECT_FALSE(window1->GetNativeWindow()->IsVisible()); - EXPECT_FALSE(window1->GetBaseWindow()->IsActive()); - EXPECT_EQ(ash::STATUS_RUNNING, item1.status); - // Clicking the item should activate the window. - item1_controller->Clicked(click_event); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); - // Maximizing a window should preserve state after minimize + click. - window1->GetBaseWindow()->Maximize(); - window1->GetBaseWindow()->Minimize(); - item1_controller->Clicked(click_event); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized()); -} - -// Confirm the minimizing click behavior for apps. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserMinimizeOnClick, - PackagedAppClickBehaviorInMinimizeMode) { - // Launch one platform app and create a window for it. - const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); - ShellWindow* window1 = CreateShellWindow(extension1); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - - // Confirm that a controller item was created and is the correct state. - const ash::LauncherItem& item1 = GetLastLauncherItem(); - LauncherItemController* item1_controller = GetItemController(item1.id); - EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); - EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); - EXPECT_EQ(LauncherItemController::TYPE_APP, item1_controller->type()); - // Since it is already active, clicking it should minimize. - TestEvent click_event(ui::ET_MOUSE_PRESSED); - item1_controller->Clicked(click_event); - EXPECT_FALSE(window1->GetNativeWindow()->IsVisible()); - EXPECT_FALSE(window1->GetBaseWindow()->IsActive()); - EXPECT_TRUE(window1->GetBaseWindow()->IsMinimized()); - EXPECT_EQ(ash::STATUS_RUNNING, item1.status); - // Clicking the item again should activate the window again. - item1_controller->Clicked(click_event); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); - // Maximizing a window should preserve state after minimize + click. - window1->GetBaseWindow()->Maximize(); - window1->GetBaseWindow()->Minimize(); - item1_controller->Clicked(click_event); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized()); - window1->GetBaseWindow()->Restore(); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - EXPECT_FALSE(window1->GetBaseWindow()->IsMaximized()); - - // Creating a second window of the same type should change the behavior so - // that a click does not change the activation state. - ShellWindow* window1a = CreateShellWindow(extension1); - EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1a->GetBaseWindow()->IsActive()); - // The first click does nothing. - item1_controller->Clicked(click_event); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - EXPECT_FALSE(window1a->GetBaseWindow()->IsActive()); - // The second neither. - item1_controller->Clicked(click_event); - EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); - EXPECT_FALSE(window1a->GetBaseWindow()->IsActive()); -} - -// Confirm that click behavior for app panels is correct. -IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, AppPanelClickBehavior) { - // Enable experimental APIs to allow panel creation. - CommandLine::ForCurrentProcess()->AppendSwitch( - extensions::switches::kEnableExperimentalExtensionApis); - // Launch a platform app and create a panel window for it. - const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); - ShellWindow::CreateParams params; - params.window_type = ShellWindow::WINDOW_TYPE_PANEL; - params.focused = false; - ShellWindow* panel = CreateShellWindowFromParams(extension1, params); - EXPECT_TRUE(panel->GetNativeWindow()->IsVisible()); - // Panels should not be active by default. - EXPECT_FALSE(panel->GetBaseWindow()->IsActive()); - // Confirm that a controller item was created and is the correct state. - const ash::LauncherItem& item1 = GetLastLauncherPanelItem(); - LauncherItemController* item1_controller = GetItemController(item1.id); - EXPECT_EQ(ash::TYPE_APP_PANEL, item1.type); - EXPECT_EQ(ash::STATUS_RUNNING, item1.status); - EXPECT_EQ(LauncherItemController::TYPE_APP_PANEL, item1_controller->type()); - // Click the item and confirm that the panel is activated. - TestEvent click_event(ui::ET_MOUSE_PRESSED); - item1_controller->Clicked(click_event); - EXPECT_TRUE(panel->GetBaseWindow()->IsActive()); - EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); - // Click the item again and confirm that the panel is minimized. - item1_controller->Clicked(click_event); - EXPECT_TRUE(panel->GetBaseWindow()->IsMinimized()); - EXPECT_EQ(ash::STATUS_RUNNING, item1.status); - // Click the item again and confirm that the panel is activated. - item1_controller->Clicked(click_event); - EXPECT_TRUE(panel->GetNativeWindow()->IsVisible()); - EXPECT_TRUE(panel->GetBaseWindow()->IsActive()); - EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); -} - IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, BrowserActivation) { int item_count = launcher_model()->item_count(); @@ -736,49 +500,24 @@ IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, BrowserActivation) { launcher_model()->ItemByID(item_id1)->status); } -// Test that opening an app sets the correct icon -IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, SetIcon) { - TestShellWindowRegistryObserver test_observer(browser()->profile()); - - // Enable experimental APIs to allow panel creation. - CommandLine::ForCurrentProcess()->AppendSwitch( - extensions::switches::kEnableExperimentalExtensionApis); - - int base_launcher_item_count = launcher_model()->item_count(); - ExtensionTestMessageListener launched_listener("Launched", false); - ExtensionTestMessageListener completed_listener("Completed", false); - LoadAndLaunchPlatformApp("app_icon"); - ASSERT_TRUE(launched_listener.WaitUntilSatisfied()); - ASSERT_TRUE(completed_listener.WaitUntilSatisfied()); - - // Now wait until the WebContent has decoded the icons and chrome has - // processed it. This needs to be in a loop since the renderer runs in a - // different process. - while (test_observer.icon_updates() < 3) { - base::RunLoop run_loop; - run_loop.RunUntilIdle(); - } - - // This test creates one shell window and one panel window. - int launcher_item_count = launcher_model()->item_count(); - ASSERT_EQ(base_launcher_item_count + 2, launcher_item_count); - // The Panel will be the last item, the app list second-to-last, the app - // third from last. - const ash::LauncherItem& app_item = - launcher_model()->items()[launcher_item_count - 3]; - const ash::LauncherItem& panel_item = - launcher_model()->items()[launcher_item_count - 1]; - const LauncherItemController* app_item_controller = - GetItemController(app_item.id); - const LauncherItemController* panel_item_controller = - GetItemController(panel_item.id); - // Icons for Apps are set by the ShellWindowLauncherController, so - // image_set_by_controller() should be set. - EXPECT_TRUE(app_item_controller->image_set_by_controller()); - EXPECT_TRUE(panel_item_controller->image_set_by_controller()); - // Ensure icon heights are correct (see test.js in app_icon/ test directory) - EXPECT_EQ(48, app_item.image.height()); - EXPECT_EQ(64, panel_item.image.height()); +// Test that draw attention sets the launcher item status. +IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, DrawAttention) { + const Extension* extension = LoadAndLaunchPlatformApp("launch"); + ShellWindow* shell_window = CreateShellWindow(extension); + const ash::LauncherItem& item = GetLastLauncherItem(); + EXPECT_EQ(ash::STATUS_ACTIVE, item.status); + // Set Minimize window to deactivate the launcher item. + shell_window->GetBaseWindow()->Minimize(); + EXPECT_EQ(ash::STATUS_RUNNING, item.status); + // Set DrawAttention property. + shell_window->GetNativeWindow()->SetProperty( + aura::client::kDrawAttentionKey, true); + EXPECT_EQ(ash::STATUS_ATTENTION, item.status); + // Activate window, should clear DrawAttention. + shell_window->GetBaseWindow()->Activate(); + EXPECT_EQ(ash::STATUS_ACTIVE, item.status); + EXPECT_FALSE(shell_window->GetNativeWindow()->GetProperty( + aura::client::kDrawAttentionKey)); } // Test that we can launch an app with a shortcut. @@ -787,7 +526,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, LaunchPinned) { int tab_count = tab_strip->count(); ash::LauncherID shortcut_id = CreateShortcut("app1"); EXPECT_EQ(ash::STATUS_CLOSED, (*model_->ItemByID(shortcut_id)).status); - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(++tab_count, tab_strip->count()); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); WebContents* tab = tab_strip->GetActiveWebContents(); @@ -847,7 +586,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, LaunchMaximized) { ash::wm::MaximizeWindow(window2); ash::LauncherID shortcut_id = CreateShortcut("app1"); - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(++tab_count, tab_strip->count()); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); @@ -855,7 +594,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, LaunchMaximized) { ash::wm::ActivateWindow(window1); EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut_id)).status); - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); } @@ -896,13 +635,13 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MultipleApps) { EXPECT_EQ(++item_count, model_->item_count()); // Launch first app. - ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); EXPECT_EQ(++tab_count, tab_strip->count()); WebContents* tab1 = tab_strip->GetActiveWebContents(); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut1)).status); // Launch second app. - ActivateLauncherItem(model_->ItemIndexByID(shortcut2)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut2)); EXPECT_EQ(++tab_count, tab_strip->count()); WebContents* tab2 = tab_strip->GetActiveWebContents(); ASSERT_NE(tab1, tab2); @@ -910,7 +649,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MultipleApps) { EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut2)).status); // Reactivate first app. - ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); EXPECT_EQ(tab_count, tab_strip->count()); EXPECT_EQ(tab_strip->GetActiveWebContents(), tab1); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut1)).status); @@ -923,20 +662,21 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MultipleApps) { NEW_FOREGROUND_TAB, 0); EXPECT_EQ(++tab_count, tab_strip->count()); + WebContents* tab3 = tab_strip->GetActiveWebContents(); EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut1)).status); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut2)).status); // Reactivate first app. - ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); EXPECT_EQ(tab_count, tab_strip->count()); EXPECT_EQ(tab_strip->GetActiveWebContents(), tab1); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut1)).status); EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut2)).status); // And second again. This time the second tab should become active. - ActivateLauncherItem(model_->ItemIndexByID(shortcut2)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut2)); EXPECT_EQ(tab_count, tab_strip->count()); - EXPECT_EQ(tab_strip->GetActiveWebContents(), tab2); + EXPECT_EQ(tab_strip->GetActiveWebContents(), tab3); EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut1)).status); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut2)).status); } @@ -946,7 +686,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MultipleApps) { IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, Navigation) { ash::LauncherID shortcut_id = CreateShortcut("app1"); EXPECT_EQ(ash::STATUS_CLOSED, (*model_->ItemByID(shortcut_id)).status); - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); // Navigate away. @@ -964,10 +704,9 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MultipleOwnedTabs) { TabStripModel* tab_strip = browser()->tab_strip_model(); int tab_count = tab_strip->count(); ash::LauncherID shortcut_id = CreateShortcut("app1"); - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(++tab_count, tab_strip->count()); EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); - WebContents* first_tab = tab_strip->GetActiveWebContents(); // Create new tab owned by app. ui_test_utils::NavigateToURLWithDisposition( @@ -978,6 +717,7 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MultipleOwnedTabs) { EXPECT_EQ(++tab_count, tab_strip->count()); // Confirm app is still active. EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + WebContents* second_tab = tab_strip->GetActiveWebContents(); // Create new tab not owned by app. ui_test_utils::NavigateToURLWithDisposition( @@ -989,22 +729,24 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MultipleOwnedTabs) { // No longer active. EXPECT_EQ(ash::STATUS_RUNNING, model_->ItemByID(shortcut_id)->status); - // Activating app makes first tab active again. - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + // Activating app makes second tab active again. + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); - EXPECT_EQ(tab_strip->GetActiveWebContents(), first_tab); + EXPECT_EQ(tab_strip->GetActiveWebContents(), second_tab); } IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, RefocusFilter) { + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); TabStripModel* tab_strip = browser()->tab_strip_model(); int tab_count = tab_strip->count(); ash::LauncherID shortcut_id = CreateShortcut("app1"); - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(++tab_count, tab_strip->count()); EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); WebContents* first_tab = tab_strip->GetActiveWebContents(); - controller_->SetRefocusURLPatternForTest( + controller->SetRefocusURLPatternForTest( shortcut_id, GURL("http://www.example.com/path1/*")); // Create new tab owned by app. ui_test_utils::NavigateToURLWithDisposition( @@ -1028,32 +770,34 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, RefocusFilter) { // Activating app makes first tab active again, because second tab isn't // in its refocus url path. - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); EXPECT_EQ(tab_strip->GetActiveWebContents(), first_tab); } IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, RefocusFilterLaunch) { + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); TabStripModel* tab_strip = browser()->tab_strip_model(); int tab_count = tab_strip->count(); ash::LauncherID shortcut_id = CreateShortcut("app1"); - controller_->SetRefocusURLPatternForTest( + controller->SetRefocusURLPatternForTest( shortcut_id, GURL("http://www.example.com/path1/*")); - // Create new tab. + // Create new tab owned by app. ui_test_utils::NavigateToURLWithDisposition( browser(), - GURL("http://www.example2.com/path2/bar.html"), + GURL("http://www.example.com/path2/bar.html"), NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); EXPECT_EQ(++tab_count, tab_strip->count()); WebContents* first_tab = tab_strip->GetActiveWebContents(); - // Confirm app is not active. - EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); + // Confirm app is active. + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); // Activating app should launch new tab, because second tab isn't // in its refocus url path. - ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + launcher_->ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); EXPECT_EQ(++tab_count, tab_strip->count()); WebContents* second_tab = tab_strip->GetActiveWebContents(); EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); @@ -1061,580 +805,6 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, RefocusFilterLaunch) { EXPECT_EQ(tab_strip->GetActiveWebContents(), second_tab); } -// Check the launcher activation state for applications and browser. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, ActivationStateCheck) { - TabStripModel* tab_strip = browser()->tab_strip_model(); - // Get the browser item index - int browser_index = ash::launcher::GetBrowserItemIndex(*controller_->model()); - EXPECT_TRUE(browser_index >= 0); - - // Even though we are just comming up, the browser should be active. - EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); - - ash::LauncherID shortcut_id = CreateShortcut("app1"); - controller_->SetRefocusURLPatternForTest( - shortcut_id, GURL("http://www.example.com/path1/*")); - - EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); - EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); - - // Create new tab which would be the running app. - ui_test_utils::NavigateToURLWithDisposition( - browser(), - GURL("http://www.example.com/path1/bar.html"), - NEW_FOREGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - - // There should never be two items active at the same time. - EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); - EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[browser_index].status); - - tab_strip->ActivateTabAt(0, false); - EXPECT_EQ(ash::STATUS_RUNNING, model_->ItemByID(shortcut_id)->status); - EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); - - tab_strip->CloseWebContentsAt(1, TabStripModel::CLOSE_NONE); - EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); - EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); - - ash::wm::DeactivateWindow(browser()->window()->GetNativeWindow()); - EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); - EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[browser_index].status); -} - -// Check that the launcher activation state for a V1 application stays closed -// even after an asynchronous browser event comes in after the tab got -// destroyed. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, AsyncActivationStateCheck) { - TabStripModel* tab_strip = browser()->tab_strip_model(); - - ash::LauncherID shortcut_id = CreateShortcut("app1"); - controller_->SetRefocusURLPatternForTest( - shortcut_id, GURL("http://www.example.com/path1/*")); - - EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); - - // Create new tab which would be the running app. - ui_test_utils::NavigateToURLWithDisposition( - browser(), - GURL("http://www.example.com/path1/bar.html"), - NEW_FOREGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - - EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); - // To address the issue of crbug.com/174050, the tab we are about to close - // has to be active. - tab_strip->ActivateTabAt(1, false); - EXPECT_EQ(1, tab_strip->active_index()); - - // Close the web contents. - tab_strip->CloseWebContentsAt(1, TabStripModel::CLOSE_NONE); - // The status should now be set to closed. - EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); -} - -// Checks that a windowed application does not add an item to the browser list. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTestNoDefaultBrowser, - WindowedAppDoesNotAddToBrowser) { - // Get the number of items in the browser menu. - size_t items = NumberOfDetectedLauncherBrowsers(false); - size_t running_browser = chrome::GetTotalBrowserCount(); - EXPECT_EQ(0u, items); - EXPECT_EQ(0u, running_browser); - - LoadAndLaunchExtension("app1", extension_misc::LAUNCH_WINDOW, NEW_WINDOW); - - // No new browser should get detected, even though one more is running. - EXPECT_EQ(0u, NumberOfDetectedLauncherBrowsers(false)); - EXPECT_EQ(++running_browser, chrome::GetTotalBrowserCount()); - - LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); - - // A new browser should get detected and one more should be running. - EXPECT_EQ(NumberOfDetectedLauncherBrowsers(false), 1u); - EXPECT_EQ(++running_browser, chrome::GetTotalBrowserCount()); -} - -// Checks the functionality to enumerate all browsers vs. all tabs. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTestNoDefaultBrowser, - EnumerateALlBrowsersAndTabs) { - // Create at least one browser. - LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); - size_t browsers = NumberOfDetectedLauncherBrowsers(false); - size_t tabs = NumberOfDetectedLauncherBrowsers(true); - - // Create a second browser. - LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); - - EXPECT_EQ(++browsers, NumberOfDetectedLauncherBrowsers(false)); - EXPECT_EQ(++tabs, NumberOfDetectedLauncherBrowsers(true)); - - // Create only a tab. - LoadAndLaunchExtension("app1", - extension_misc::LAUNCH_TAB, - NEW_FOREGROUND_TAB); - - EXPECT_EQ(browsers, NumberOfDetectedLauncherBrowsers(false)); - EXPECT_EQ(++tabs, NumberOfDetectedLauncherBrowsers(true)); -} - -// Check that the keyboard activation of a launcher item tabs properly through -// the items at hand. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, AltNumberTabsTabbing) { - TabStripModel* tab_strip = browser()->tab_strip_model(); - - ash::LauncherID shortcut_id = CreateShortcut("app"); - controller_->SetRefocusURLPatternForTest( - shortcut_id, GURL("http://www.example.com/path/*")); - std::string url = "http://www.example.com/path/bla"; - - int shortcut_index = model_->ItemIndexByID(shortcut_id); - - // Create an application handled browser tab. - ui_test_utils::NavigateToURLWithDisposition( - browser(), - GURL(url), - NEW_FOREGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - - content::WebContents* content1 = tab_strip->GetActiveWebContents(); - - // Create some other browser tab. - ui_test_utils::NavigateToURLWithDisposition( - browser(), - GURL("http://www.test.com"), - NEW_FOREGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - content::WebContents* content1a = tab_strip->GetActiveWebContents(); - - // Make sure that the active tab is now our handled tab. - EXPECT_NE(content1a, content1); - - // The active tab should still be the unnamed tab. Then we switch and reach - // the first app and stay there. - EXPECT_EQ(content1a, tab_strip->GetActiveWebContents()); - ActivateLauncherItem(shortcut_index); - EXPECT_EQ(content1, tab_strip->GetActiveWebContents()); - ActivateLauncherItem(shortcut_index); - EXPECT_EQ(content1, tab_strip->GetActiveWebContents()); - - ui_test_utils::NavigateToURLWithDisposition( - browser(), - GURL(url), - NEW_FOREGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - content::WebContents* content2 = tab_strip->GetActiveWebContents(); - - EXPECT_EQ(content2, browser()->tab_strip_model()->GetActiveWebContents()); - ActivateLauncherItem(shortcut_index); - EXPECT_EQ(content1, browser()->tab_strip_model()->GetActiveWebContents()); - ActivateLauncherItem(shortcut_index); - EXPECT_EQ(content2, browser()->tab_strip_model()->GetActiveWebContents()); -} - -// Check that the keyboard activation of a launcher item tabs properly through -// the items at hand. -IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, - AltNumberAppsTabbing) { - // First run app. - const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); - ui::BaseWindow* window1 = CreateShellWindow(extension1)->GetBaseWindow(); - const ash::LauncherItem& item1 = GetLastLauncherItem(); - ash::LauncherID app_id = item1.id; - int app_index = launcher_model()->ItemIndexByID(app_id); - - EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); - EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); - - const Extension* extension2 = LoadAndLaunchPlatformApp("launch_2"); - ui::BaseWindow* window2 = CreateShellWindow(extension2)->GetBaseWindow(); - - // By now the browser should be active. Issue Alt keystrokes several times to - // see that we stay on that application. - EXPECT_TRUE(window2->IsActive()); - ActivateLauncherItem(app_index); - EXPECT_TRUE(window1->IsActive()); - ActivateLauncherItem(app_index); - EXPECT_TRUE(window1->IsActive()); - - ui::BaseWindow* window1a = CreateShellWindow(extension1)->GetBaseWindow(); - - EXPECT_TRUE(window1a->IsActive()); - EXPECT_FALSE(window1->IsActive()); - ActivateLauncherItem(app_index); - EXPECT_TRUE(window1->IsActive()); - ActivateLauncherItem(app_index); - EXPECT_TRUE(window1a->IsActive()); -} - -// Test that we can launch a platform app panel and get a running item. -IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, - LaunchPanelWindow) { - int item_count = launcher_model()->item_count(); - const Extension* extension = LoadAndLaunchPlatformApp("launch"); - ShellWindow::CreateParams params; - params.window_type = ShellWindow::WINDOW_TYPE_PANEL; - params.focused = false; - ShellWindow* window = CreateShellWindowFromParams(extension, params); - ++item_count; - ASSERT_EQ(item_count, launcher_model()->item_count()); - const ash::LauncherItem& item = GetLastLauncherPanelItem(); - EXPECT_EQ(ash::TYPE_APP_PANEL, item.type); - // Opening a panel does not activate it. - EXPECT_EQ(ash::STATUS_RUNNING, item.status); - CloseShellWindow(window); - --item_count; - EXPECT_EQ(item_count, launcher_model()->item_count()); -} - -// Test attention states of windows. -IN_PROC_BROWSER_TEST_F(LauncherPlatformAppBrowserTest, WindowAttentionStatus) { - const Extension* extension = LoadAndLaunchPlatformApp("launch"); - ShellWindow::CreateParams params; - params.window_type = ShellWindow::WINDOW_TYPE_PANEL; - params.focused = false; - ShellWindow* panel = CreateShellWindowFromParams(extension, params); - EXPECT_TRUE(panel->GetNativeWindow()->IsVisible()); - // Panels should not be active by default. - EXPECT_FALSE(panel->GetBaseWindow()->IsActive()); - // Confirm that a controller item was created and is the correct state. - const ash::LauncherItem& item = GetLastLauncherPanelItem(); - LauncherItemController* item_controller = GetItemController(item.id); - EXPECT_EQ(ash::TYPE_APP_PANEL, item.type); - EXPECT_EQ(ash::STATUS_RUNNING, item.status); - EXPECT_EQ(LauncherItemController::TYPE_APP_PANEL, item_controller->type()); - - // App windows should go to attention state. - panel->GetNativeWindow()->SetProperty(aura::client::kDrawAttentionKey, true); - EXPECT_EQ(ash::STATUS_ATTENTION, item.status); - - // Click the item and confirm that the panel is activated. - TestEvent click_event(ui::ET_MOUSE_PRESSED); - item_controller->Clicked(click_event); - EXPECT_TRUE(panel->GetBaseWindow()->IsActive()); - EXPECT_EQ(ash::STATUS_ACTIVE, item.status); - - // Active windows don't show attention. - panel->GetNativeWindow()->SetProperty(aura::client::kDrawAttentionKey, true); - EXPECT_EQ(ash::STATUS_ACTIVE, item.status); -} - -// Checks that the browser Alt "tabbing" is properly done. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTestNoDefaultBrowser, - AltNumberBrowserTabbing) { - // Get the number of items in the browser menu. - EXPECT_EQ(0u, chrome::GetTotalBrowserCount()); - // The first activation should create a browser. - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(1u, chrome::GetTotalBrowserCount()); - // A second activation should not create a new instance. - launcher_->ActivateLauncherItem(0); - Browser* browser1 = chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow()); - EXPECT_TRUE(browser1); - aura::Window* window1 = browser1->window()->GetNativeWindow(); - Browser* browser2 = CreateBrowser(profile()); - aura::Window* window2 = browser2->window()->GetNativeWindow(); - - EXPECT_EQ(2u, chrome::GetTotalBrowserCount()); - EXPECT_NE(window1, window2); - EXPECT_EQ(window2, ash::wm::GetActiveWindow()); - - // Activate multiple times the switcher to see that the windows get activated. - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(window1, ash::wm::GetActiveWindow()); - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(window2, ash::wm::GetActiveWindow()); - - // Create a third browser - make sure that we do not toggle simply between - // two windows. - Browser* browser3 = CreateBrowser(profile()); - aura::Window* window3 = browser3->window()->GetNativeWindow(); - - EXPECT_EQ(3u, chrome::GetTotalBrowserCount()); - EXPECT_NE(window1, window3); - EXPECT_NE(window2, window3); - EXPECT_EQ(window3, ash::wm::GetActiveWindow()); - - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(window1, ash::wm::GetActiveWindow()); - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(window2, ash::wm::GetActiveWindow()); - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(window3, ash::wm::GetActiveWindow()); - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(window1, ash::wm::GetActiveWindow()); - - // Create anther app and make sure that none of our browsers is active. - LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); - EXPECT_NE(window1, ash::wm::GetActiveWindow()); - EXPECT_NE(window2, ash::wm::GetActiveWindow()); - - // After activation our browser should be active again. - launcher_->ActivateLauncherItem(0); - EXPECT_EQ(window1, ash::wm::GetActiveWindow()); -} - -// Checks that after a session restore, we do not start applications on an -// activation. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, ActivateAfterSessionRestore) { - EXPECT_EQ(1u, chrome::GetTotalBrowserCount()); - - // Create a known application. - ash::LauncherID shortcut_id = CreateShortcut("app1"); - - // Create a new browser - without activating it - and load an "app" into it. - Browser::CreateParams params = - Browser::CreateParams(profile(), chrome::GetActiveDesktop()); - params.initial_show_state = ui::SHOW_STATE_INACTIVE; - Browser* browser2 = new Browser(params); - controller_->SetRefocusURLPatternForTest( - shortcut_id, GURL("http://www.example.com/path/*")); - std::string url = "http://www.example.com/path/bla"; - ui_test_utils::NavigateToURLWithDisposition( - browser2, - GURL(url), - NEW_FOREGROUND_TAB, - ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); - - // Remember the number of tabs for each browser. - TabStripModel* tab_strip = browser()->tab_strip_model(); - int tab_count1 = tab_strip->count(); - TabStripModel* tab_strip2 = browser2->tab_strip_model(); - int tab_count2 = tab_strip2->count(); - - // Check that we have two browsers and the inactive browser remained inactive. - EXPECT_EQ(2u, chrome::GetTotalBrowserCount()); - EXPECT_EQ(chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow()), - browser()); - // Check that the LRU browser list does only contain the original browser. - BrowserList* ash_browser_list = - BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); - BrowserList::const_reverse_iterator it = - ash_browser_list->begin_last_active(); - EXPECT_EQ(*it, browser()); - ++it; - EXPECT_EQ(it, ash_browser_list->end_last_active()); - - // Now request to either activate an existing app or create a new one. - controller_->ItemSelected(*model_->ItemByID(shortcut_id), - ui::KeyEvent(ui::ET_KEY_RELEASED, - ui::VKEY_RETURN, - 0, - false)); - - // Check that we have set focus on the existing application and nothing new - // was created. - EXPECT_EQ(2u, chrome::GetTotalBrowserCount()); - EXPECT_EQ(tab_count1, tab_strip->count()); - EXPECT_EQ(tab_count2, tab_strip2->count()); - EXPECT_EQ(chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow()), - browser2); -} - -// Do various drag and drop interaction tests between the application list and -// the launcher. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, DragAndDrop) { - // Get a number of interfaces we need. - aura::test::EventGenerator generator( - ash::Shell::GetPrimaryRootWindow(), gfx::Point()); - ash::test::LauncherViewTestAPI test(launcher_->GetLauncherViewForTest()); - AppListService* service = AppListService::Get(); - - // There should be two items in our launcher by this time. - EXPECT_EQ(2, model_->item_count()); - EXPECT_FALSE(service->IsAppListVisible()); - - // Open the app list menu and check that the drag and drop host was set. - gfx::Rect app_list_bounds = - test.launcher_view()->GetAppListButtonView()->GetBoundsInScreen(); - generator.MoveMouseTo(app_list_bounds.CenterPoint().x(), - app_list_bounds.CenterPoint().y()); - base::MessageLoop::current()->RunUntilIdle(); - generator.ClickLeftButton(); - - EXPECT_TRUE(service->IsAppListVisible()); - app_list::AppsGridView* grid_view = - app_list::AppsGridView::GetLastGridViewForTest(); - ASSERT_TRUE(grid_view); - ASSERT_TRUE(grid_view->has_drag_and_drop_host_for_test()); - - // There should be 2 items in our application list. - const views::ViewModel* vm_grid = grid_view->view_model_for_test(); - EXPECT_EQ(2, vm_grid->view_size()); - - // Test #1: Drag an app list which does not exist yet item into the - // launcher. Keeping it dragged, see that a new item gets created. Continuing - // to drag it out should remove it again. - - // Get over item #1 of the application list and press the mouse button. - views::View* item1 = vm_grid->view_at(1); - gfx::Rect bounds_grid_1 = item1->GetBoundsInScreen(); - generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), - bounds_grid_1.CenterPoint().y()); - base::MessageLoop::current()->RunUntilIdle(); - generator.PressLeftButton(); - - EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); - - // Drag the item into the launcher and check that a new item gets created. - const views::ViewModel* vm_launcher = - test.launcher_view()->view_model_for_test(); - views::View* launcher1 = vm_launcher->view_at(1); - gfx::Rect bounds_launcher_1 = launcher1->GetBoundsInScreen(); - generator.MoveMouseTo(bounds_launcher_1.CenterPoint().x(), - bounds_launcher_1.CenterPoint().y()); - base::MessageLoop::current()->RunUntilIdle(); - - // Check that a new item got created. - EXPECT_EQ(3, model_->item_count()); - EXPECT_TRUE(grid_view->forward_events_to_drag_and_drop_host_for_test()); - - // Move it where the item originally was and check that it disappears again. - generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), - bounds_grid_1.CenterPoint().y()); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(2, model_->item_count()); - EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); - - // Dropping it should keep the launcher as it originally was. - generator.ReleaseLeftButton(); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(2, model_->item_count()); - // There are a few animations which need finishing before we can continue. - test.RunMessageLoopUntilAnimationsDone(); - // Move the mouse outside of the launcher. - generator.MoveMouseTo(0, 0); - - // Test #2: Check that the unknown item dropped into the launcher will - // create a new item. - generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), - bounds_grid_1.CenterPoint().y()); - generator.PressLeftButton(); - generator.MoveMouseTo(bounds_launcher_1.CenterPoint().x(), - bounds_launcher_1.CenterPoint().y()); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(3, model_->item_count()); - EXPECT_TRUE(grid_view->forward_events_to_drag_and_drop_host_for_test()); - generator.ReleaseLeftButton(); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); - EXPECT_EQ(3, model_->item_count()); // It should be still there. - test.RunMessageLoopUntilAnimationsDone(); - - // Test #3: Check that the now known item dropped into the launcher will - // not create a new item. - generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), - bounds_grid_1.CenterPoint().y()); - generator.PressLeftButton(); - generator.MoveMouseTo(bounds_launcher_1.CenterPoint().x(), - bounds_launcher_1.CenterPoint().y()); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(3, model_->item_count()); // No new item got added. - EXPECT_TRUE(grid_view->forward_events_to_drag_and_drop_host_for_test()); - generator.ReleaseLeftButton(); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); - EXPECT_EQ(3, model_->item_count()); // And it remains that way. -} - -// Check that clicking on an app launcher item launches a new browser. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, ClickItem) { - // Get a number of interfaces we need. - aura::test::EventGenerator generator( - ash::Shell::GetPrimaryRootWindow(), gfx::Point()); - ash::test::LauncherViewTestAPI test(launcher_->GetLauncherViewForTest()); - AppListService* service = AppListService::Get(); - // There should be two items in our launcher by this time. - EXPECT_EQ(2, model_->item_count()); - EXPECT_FALSE(service->IsAppListVisible()); - - // Open the app list menu and check that the drag and drop host was set. - gfx::Rect app_list_bounds = - test.launcher_view()->GetAppListButtonView()->GetBoundsInScreen(); - generator.MoveMouseTo(app_list_bounds.CenterPoint().x(), - app_list_bounds.CenterPoint().y()); - generator.ClickLeftButton(); - base::MessageLoop::current()->RunUntilIdle(); - - EXPECT_TRUE(service->IsAppListVisible()); - app_list::AppsGridView* grid_view = - app_list::AppsGridView::GetLastGridViewForTest(); - ASSERT_TRUE(grid_view); - const views::ViewModel* vm_grid = grid_view->view_model_for_test(); - EXPECT_EQ(2, vm_grid->view_size()); - gfx::Rect bounds_grid_1 = vm_grid->view_at(1)->GetBoundsInScreen(); - // Test now that a click does create a new application tab. - TabStripModel* tab_strip = browser()->tab_strip_model(); - int tab_count = tab_strip->count(); - generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), - bounds_grid_1.CenterPoint().y()); - generator.ClickLeftButton(); - base::MessageLoop::current()->RunUntilIdle(); - EXPECT_EQ(tab_count + 1, tab_strip->count()); -} - -// Check LauncherItemController of Browser Shortcut functionality. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTestNoDefaultBrowser, - BrowserShortcutLauncherItemController) { - LauncherItemController* item_controller = - controller_->GetBrowserShortcutLauncherItemController(); - - // Get the number of browsers. - size_t running_browser = chrome::GetTotalBrowserCount(); - EXPECT_EQ(0u, running_browser); - EXPECT_FALSE(item_controller->IsOpen()); - - // Activate. This creates new browser - item_controller->Activate(); - // New Window is created. - running_browser = chrome::GetTotalBrowserCount(); - EXPECT_EQ(1u, running_browser); - EXPECT_TRUE(item_controller->IsOpen()); - - // Minimize Window. - aura::Window* window = ash::wm::GetActiveWindow(); - ash::wm::MinimizeWindow(window); - EXPECT_TRUE(ash::wm::IsWindowMinimized(window)); - - // Activate again. This doesn't create new browser. - // It activates window. - item_controller->Activate(); - running_browser = chrome::GetTotalBrowserCount(); - EXPECT_EQ(1u, running_browser); - EXPECT_TRUE(item_controller->IsOpen()); - EXPECT_FALSE(ash::wm::IsWindowMinimized(window)); -} - -// Check that GetIDByWindow() returns |LauncherID| of the active tab. -IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, MatchingLauncherIDandActiveTab) { - EXPECT_EQ(1u, chrome::GetTotalBrowserCount()); - EXPECT_EQ(1, browser()->tab_strip_model()->count()); - EXPECT_EQ(0, browser()->tab_strip_model()->active_index()); - EXPECT_EQ(2, model_->item_count()); - - aura::Window* window = browser()->window()->GetNativeWindow(); - - int browser_index = ash::launcher::GetBrowserItemIndex(*model_); - ash::LauncherID browser_id = model_->items()[browser_index].id; - EXPECT_EQ(browser_id, controller_->GetIDByWindow(window)); - - ash::LauncherID app_id = CreateShortcut("app1"); - EXPECT_EQ(3, model_->item_count()); - - // Creates a new tab for "app1" and checks that GetIDByWindow() returns - // |LauncherID| of "app1". - ActivateLauncherItem(model_->ItemIndexByID(app_id)); - EXPECT_EQ(2, browser()->tab_strip_model()->count()); - EXPECT_EQ(1, browser()->tab_strip_model()->active_index()); - EXPECT_EQ(app_id, controller_->GetIDByWindow(window)); - - // Makes tab at index 0(NTP) as an active tab and checks that GetIDByWindow() - // returns |LauncherID| of browser shortcut. - browser()->tab_strip_model()->ActivateTabAt(0, false); - EXPECT_EQ(0, browser()->tab_strip_model()->active_index()); - EXPECT_EQ(browser_id, controller_->GetIDByWindow(window)); -} - IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, OverflowBubble) { // Make sure to have a browser window chrome::NewTab(browser()); @@ -1658,7 +828,9 @@ IN_PROC_BROWSER_TEST_F(LauncherAppBrowserTest, OverflowBubble) { EXPECT_TRUE(launcher_->IsShowingOverflowBubble()); // Unpin first pinned app and there should be no crash. - controller_->UnpinAppsWithID(std::string("fake_app_0")); + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + controller->UnpinAppsWithID(std::string("fake_app_0")); test.RunMessageLoopUntilAnimationsDone(); EXPECT_FALSE(launcher_->IsShowingOverflowBubble()); diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc new file mode 100644 index 0000000..3137581 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc @@ -0,0 +1,1678 @@ +// 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/ash/launcher/chrome_launcher_controller_per_app.h" + +#include <vector> + +#include "ash/ash_switches.h" +#include "ash/launcher/launcher.h" +#include "ash/launcher/launcher_model.h" +#include "ash/launcher/launcher_util.h" +#include "ash/root_window_controller.h" +#include "ash/shelf/shelf_layout_manager.h" +#include "ash/shelf/shelf_widget.h" +#include "ash/shell.h" +#include "ash/wm/window_util.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/app_mode/app_mode_utils.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/extensions/app_icon_loader_impl.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/favicon/favicon_tab_helper.h" +#include "chrome/browser/prefs/incognito_mode_prefs.h" +#include "chrome/browser/prefs/pref_service_syncable.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/ash/app_sync_ui_state.h" +#include "chrome/browser/ui/ash/chrome_launcher_prefs.h" +#include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h" +#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_browser.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h" +#include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h" +#include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h" +#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/extensions/application_launch.h" +#include "chrome/browser/ui/extensions/extension_enable_flow.h" +#include "chrome/browser/ui/host_desktop.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/web_applications/web_app.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/manifest_handlers/icons_handler.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/web_contents.h" +#include "extensions/common/extension_resource.h" +#include "extensions/common/url_pattern.h" +#include "grit/ash_resources.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "grit/ui_resources.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/views/corewm/window_animations.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/login/default_pinned_apps_field_trial.h" +#endif + +using extensions::Extension; +using extension_misc::kGmailAppId; +using content::WebContents; + +namespace { + +std::string GetPrefKeyForRootWindow(aura::RootWindow* root_window) { + gfx::Display display = gfx::Screen::GetScreenFor( + root_window)->GetDisplayNearestWindow(root_window); + DCHECK(display.is_valid()); + + return base::Int64ToString(display.id()); +} + +void UpdatePerDisplayPref(PrefService* pref_service, + aura::RootWindow* root_window, + const char* pref_key, + const std::string& value) { + std::string key = GetPrefKeyForRootWindow(root_window); + if (key.empty()) + return; + + DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences); + base::DictionaryValue* shelf_prefs = update.Get(); + base::DictionaryValue* prefs = NULL; + if (!shelf_prefs->GetDictionary(key, &prefs)) { + prefs = new base::DictionaryValue(); + shelf_prefs->Set(key, prefs); + } + prefs->SetStringWithoutPathExpansion(pref_key, value); +} + +// Returns a pref value in |pref_service| for the display of |root_window|. The +// pref value is stored in |local_path| and |path|, but |pref_service| may have +// per-display preferences and the value can be specified by policy. Here is +// the priority: +// * A value managed by policy. This is a single value that applies to all +// displays. +// * A user-set value for the specified display. +// * A user-set value in |local_path| or |path|, if no per-display settings are +// ever specified (see http://crbug.com/173719 for why). |local_path| is +// preferred. See comment in |kShelfAlignment| as to why we consider two +// prefs and why |local_path| is preferred. +// * A value recommended by policy. This is a single value that applies to all +// root windows. +// * The default value for |local_path| if the value is not recommended by +// policy. +std::string GetPrefForRootWindow(PrefService* pref_service, + aura::RootWindow* root_window, + const char* local_path, + const char* path) { + const PrefService::Preference* local_pref = + pref_service->FindPreference(local_path); + const std::string value(pref_service->GetString(local_path)); + if (local_pref->IsManaged()) + return value; + + std::string pref_key = GetPrefKeyForRootWindow(root_window); + bool has_per_display_prefs = false; + if (!pref_key.empty()) { + const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary( + prefs::kShelfPreferences); + const base::DictionaryValue* display_pref = NULL; + std::string per_display_value; + if (shelf_prefs->GetDictionary(pref_key, &display_pref) && + display_pref->GetString(path, &per_display_value)) + return per_display_value; + + // If the pref for the specified display is not found, scan the whole prefs + // and check if the prefs for other display is already specified. + std::string unused_value; + for (base::DictionaryValue::Iterator iter(*shelf_prefs); + !iter.IsAtEnd(); iter.Advance()) { + const base::DictionaryValue* display_pref = NULL; + if (iter.value().GetAsDictionary(&display_pref) && + display_pref->GetString(path, &unused_value)) { + has_per_display_prefs = true; + break; + } + } + } + + if (local_pref->IsRecommended() || !has_per_display_prefs) + return value; + + const base::Value* default_value = + pref_service->GetDefaultPrefValue(local_path); + std::string default_string; + default_value->GetAsString(&default_string); + return default_string; +} + +// If prefs have synced and no user-set value exists at |local_path|, the value +// from |synced_path| is copied to |local_path|. +void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service, + const char* local_path, + const char* synced_path) { + if (!pref_service->FindPreference(local_path)->HasUserSetting() && + pref_service->IsSyncing()) { + // First time the user is using this machine, propagate from remote to + // local. + pref_service->SetString(local_path, pref_service->GetString(synced_path)); + } +} + +} // namespace + +ChromeLauncherControllerPerApp::ChromeLauncherControllerPerApp( + Profile* profile, + ash::LauncherModel* model) + : model_(model), + profile_(profile), + app_sync_ui_state_(NULL), + ignore_persist_pinned_state_change_(false) { + if (!profile_) { + // Use the original profile as on chromeos we may get a temporary off the + // record profile. + profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile(); + + app_sync_ui_state_ = AppSyncUIState::Get(profile_); + if (app_sync_ui_state_) + app_sync_ui_state_->AddObserver(this); + } + + model_->AddObserver(this); + BrowserList::AddObserver(this); + // Right now ash::Shell isn't created for tests. + // TODO(mukai): Allows it to observe display change and write tests. + if (ash::Shell::HasInstance()) + ash::Shell::GetInstance()->display_controller()->AddObserver(this); + // TODO(stevenjb): Find a better owner for shell_window_controller_? + shell_window_controller_.reset(new ShellWindowLauncherController(this)); + app_tab_helper_.reset(new LauncherAppTabHelper(profile_)); + app_icon_loader_.reset(new extensions::AppIconLoaderImpl( + profile_, extension_misc::EXTENSION_ICON_SMALL, this)); + + notification_registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_LOADED, + content::Source<Profile>(profile_)); + notification_registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_UNLOADED, + content::Source<Profile>(profile_)); + pref_change_registrar_.Init(profile_->GetPrefs()); + pref_change_registrar_.Add( + prefs::kPinnedLauncherApps, + base::Bind(&ChromeLauncherControllerPerApp::UpdateAppLaunchersFromPref, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kShelfAlignmentLocal, + base::Bind(&ChromeLauncherControllerPerApp::SetShelfAlignmentFromPrefs, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kShelfAutoHideBehaviorLocal, + base::Bind(&ChromeLauncherControllerPerApp:: + SetShelfAutoHideBehaviorFromPrefs, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kShelfPreferences, + base::Bind(&ChromeLauncherControllerPerApp::SetShelfBehaviorsFromPrefs, + base::Unretained(this))); +} + +ChromeLauncherControllerPerApp::~ChromeLauncherControllerPerApp() { + // Reset the shell window controller here since it has a weak pointer to this. + shell_window_controller_.reset(); + + for (std::set<ash::Launcher*>::iterator iter = launchers_.begin(); + iter != launchers_.end(); + ++iter) + (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this); + + model_->RemoveObserver(this); + BrowserList::RemoveObserver(this); + if (ash::Shell::HasInstance()) + ash::Shell::GetInstance()->display_controller()->RemoveObserver(this); + for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + i->second->OnRemoved(); + // TODO(skuhne): After getting rid of the old launcher, get also rid of the + // BrowserLauncherItemController (since it is only used for activation + // tracking at that point. + int index = model_->ItemIndexByID(i->first); + // A "browser proxy" is not known to the model and this removal does + // therefore not need to be propagated to the model. + if (index != -1 && + model_->items()[index].type != ash::TYPE_BROWSER_SHORTCUT) + model_->RemoveItemAt(index); + } + + if (ash::Shell::HasInstance()) + ash::Shell::GetInstance()->RemoveShellObserver(this); + + if (app_sync_ui_state_) + app_sync_ui_state_->RemoveObserver(this); + + PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this); +} + +void ChromeLauncherControllerPerApp::Init() { + UpdateAppLaunchersFromPref(); + CreateBrowserShortcutLauncherItem(); + + // TODO(sky): update unit test so that this test isn't necessary. + if (ash::Shell::HasInstance()) { + SetShelfAutoHideBehaviorFromPrefs(); + SetShelfAlignmentFromPrefs(); + PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); + if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() || + !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)-> + HasUserSetting()) { + // This causes OnIsSyncingChanged to be called when the value of + // PrefService::IsSyncing() changes. + prefs->AddObserver(this); + } + ash::Shell::GetInstance()->AddShellObserver(this); + } +} + +ChromeLauncherControllerPerApp* +ChromeLauncherControllerPerApp::GetPerAppInterface() { + return this; +} + +ash::LauncherID ChromeLauncherControllerPerApp::CreateTabbedLauncherItem( + LauncherItemController* controller, + IncognitoState is_incognito, + ash::LauncherItemStatus status) { + // The PerAppLauncher doesn't need an empty slot for TabbedLauncherItem and + // its LauncherItemController. + // TODO(skuhne): Remove function when PerBrowser launcher gets removed. + return 0; +} + +ash::LauncherID ChromeLauncherControllerPerApp::CreateAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status) { + CHECK(controller); + int index = 0; + // Panels are inserted on the left so as not to push all existing panels over. + if (controller->GetLauncherItemType() != ash::TYPE_APP_PANEL) { + index = model_->item_count(); + // For the alternate shelf layout increment the index (after the app icon) + if (ash::switches::UseAlternateShelfLayout()) + ++index; + } + return InsertAppLauncherItem(controller, + app_id, + status, + index, + controller->GetLauncherItemType()); +} + +void ChromeLauncherControllerPerApp::SetItemStatus( + ash::LauncherID id, + ash::LauncherItemStatus status) { + int index = model_->ItemIndexByID(id); + // Since ordinary browser windows are not registered, we might get a negative + // index here. + if (index >= 0) { + ash::LauncherItem item = model_->items()[index]; + item.status = status; + model_->Set(index, item); + + if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT) + return; + } + UpdateBrowserItemStatus(); +} + +void ChromeLauncherControllerPerApp::SetItemController( + ash::LauncherID id, + LauncherItemController* controller) { + CHECK(controller); + IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); + CHECK(iter != id_to_item_controller_map_.end()); + iter->second->OnRemoved(); + iter->second = controller; + controller->set_launcher_id(id); +} + +void ChromeLauncherControllerPerApp::CloseLauncherItem(ash::LauncherID id) { + CHECK(id); + if (IsPinned(id)) { + // Create a new shortcut controller. + IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); + CHECK(iter != id_to_item_controller_map_.end()); + SetItemStatus(id, ash::STATUS_CLOSED); + std::string app_id = iter->second->app_id(); + iter->second->OnRemoved(); + iter->second = new AppShortcutLauncherItemController(app_id, this); + iter->second->set_launcher_id(id); + } else { + LauncherItemClosed(id); + } +} + +void ChromeLauncherControllerPerApp::Pin(ash::LauncherID id) { + DCHECK(HasItemController(id)); + + int index = model_->ItemIndexByID(id); + DCHECK_GE(index, 0); + + ash::LauncherItem item = model_->items()[index]; + + if (item.type == ash::TYPE_PLATFORM_APP || + item.type == ash::TYPE_WINDOWED_APP) { + item.type = ash::TYPE_APP_SHORTCUT; + model_->Set(index, item); + } else if (item.type != ash::TYPE_APP_SHORTCUT) { + return; + } + + if (CanPin()) + PersistPinnedState(); +} + +void ChromeLauncherControllerPerApp::Unpin(ash::LauncherID id) { + DCHECK(HasItemController(id)); + + LauncherItemController* controller = id_to_item_controller_map_[id]; + if (controller->type() == LauncherItemController::TYPE_APP) { + int index = model_->ItemIndexByID(id); + DCHECK_GE(index, 0); + ash::LauncherItem item = model_->items()[index]; + item.type = ash::TYPE_PLATFORM_APP; + model_->Set(index, item); + } else { + // Prevent the removal of items upon unpin if it is locked by a running + // windowed V1 app. + if (!controller->locked()) { + LauncherItemClosed(id); + } else { + int index = model_->ItemIndexByID(id); + DCHECK_GE(index, 0); + ash::LauncherItem item = model_->items()[index]; + item.type = ash::TYPE_WINDOWED_APP; + model_->Set(index, item); + } + } + if (CanPin()) + PersistPinnedState(); +} + +bool ChromeLauncherControllerPerApp::IsPinned(ash::LauncherID id) { + int index = model_->ItemIndexByID(id); + if (index < 0) + return false; + ash::LauncherItemType type = model_->items()[index].type; + return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_SHORTCUT); +} + +void ChromeLauncherControllerPerApp::TogglePinned(ash::LauncherID id) { + if (!HasItemController(id)) + return; // May happen if item closed with menu open. + + if (IsPinned(id)) + Unpin(id); + else + Pin(id); +} + +bool ChromeLauncherControllerPerApp::IsPinnable(ash::LauncherID id) const { + int index = model_->ItemIndexByID(id); + if (index == -1) + return false; + + ash::LauncherItemType type = model_->items()[index].type; + return ((type == ash::TYPE_APP_SHORTCUT || + type == ash::TYPE_PLATFORM_APP || + type == ash::TYPE_WINDOWED_APP) && + CanPin()); +} + +void ChromeLauncherControllerPerApp::LockV1AppWithID( + const std::string& app_id) { + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (!IsPinned(id) && !IsWindowedAppInLauncher(app_id)) { + CreateAppShortcutLauncherItemWithType(app_id, + model_->item_count(), + ash::TYPE_WINDOWED_APP); + id = GetLauncherIDForAppID(app_id); + } + CHECK(id); + id_to_item_controller_map_[id]->lock(); +} + +void ChromeLauncherControllerPerApp::UnlockV1AppWithID( + const std::string& app_id) { + ash::LauncherID id = GetLauncherIDForAppID(app_id); + CHECK(IsPinned(id) || IsWindowedAppInLauncher(app_id)); + CHECK(id); + LauncherItemController* controller = id_to_item_controller_map_[id]; + controller->unlock(); + if (!controller->locked() && !IsPinned(id)) + CloseLauncherItem(id); +} + +void ChromeLauncherControllerPerApp::Launch(ash::LauncherID id, + int event_flags) { + if (!HasItemController(id)) + return; // In case invoked from menu and item closed while menu up. + id_to_item_controller_map_[id]->Launch(event_flags); +} + +void ChromeLauncherControllerPerApp::Close(ash::LauncherID id) { + if (!HasItemController(id)) + return; // May happen if menu closed. + id_to_item_controller_map_[id]->Close(); +} + +bool ChromeLauncherControllerPerApp::IsOpen(ash::LauncherID id) { + if (!HasItemController(id)) + return false; + return id_to_item_controller_map_[id]->IsOpen(); +} + +bool ChromeLauncherControllerPerApp::IsPlatformApp(ash::LauncherID id) { + if (!HasItemController(id)) + return false; + + std::string app_id = GetAppIDForLauncherID(id); + const Extension* extension = GetExtensionForAppID(app_id); + // An extension can be synced / updated at any time and therefore not be + // available. + return extension ? extension->is_platform_app() : false; +} + +void ChromeLauncherControllerPerApp::LaunchApp(const std::string& app_id, + int event_flags) { + // |extension| could be NULL when it is being unloaded for updating. + const Extension* extension = GetExtensionForAppID(app_id); + if (!extension) + return; + + const ExtensionService* service = + extensions::ExtensionSystem::Get(profile_)->extension_service(); + if (!service->IsExtensionEnabledForLauncher(app_id)) { + // Do nothing if there is already a running enable flow. + if (extension_enable_flow_) + return; + + extension_enable_flow_.reset( + new ExtensionEnableFlow(profile_, app_id, this)); + extension_enable_flow_->StartForNativeWindow(NULL); + return; + } + + chrome::OpenApplication(chrome::AppLaunchParams(GetProfileForNewWindows(), + extension, + event_flags)); +} + +void ChromeLauncherControllerPerApp::ActivateApp(const std::string& app_id, + int event_flags) { + // If there is an existing non-shortcut controller for this app, open it. + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (id) { + LauncherItemController* controller = id_to_item_controller_map_[id]; + controller->Activate(); + return; + } + + // Create a temporary application launcher item and use it to see if there are + // running instances. + scoped_ptr<AppShortcutLauncherItemController> app_controller( + new AppShortcutLauncherItemController(app_id, this)); + if (!app_controller->GetRunningApplications().empty()) + app_controller->Activate(); + else + LaunchApp(app_id, event_flags); +} + +extensions::ExtensionPrefs::LaunchType + ChromeLauncherControllerPerApp::GetLaunchType(ash::LauncherID id) { + DCHECK(HasItemController(id)); + + const Extension* extension = GetExtensionForAppID( + id_to_item_controller_map_[id]->app_id()); + + // An extension can be unloaded/updated/unavailable at any time. + if (!extension) + return extensions::ExtensionPrefs::LAUNCH_DEFAULT; + + return profile_->GetExtensionService()->extension_prefs()->GetLaunchType( + extension, + extensions::ExtensionPrefs::LAUNCH_DEFAULT); +} + +std::string ChromeLauncherControllerPerApp::GetAppID( + content::WebContents* tab) { + return app_tab_helper_->GetAppID(tab); +} + +ash::LauncherID ChromeLauncherControllerPerApp::GetLauncherIDForAppID( + const std::string& app_id) { + for (IDToItemControllerMap::const_iterator i = + id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + if (i->second->type() == LauncherItemController::TYPE_APP_PANEL) + continue; // Don't include panels + if (i->second->app_id() == app_id) + return i->first; + } + return 0; +} + +std::string ChromeLauncherControllerPerApp::GetAppIDForLauncherID( + ash::LauncherID id) { + CHECK(HasItemController(id)); + return id_to_item_controller_map_[id]->app_id(); +} + +void ChromeLauncherControllerPerApp::SetAppImage( + const std::string& id, + const gfx::ImageSkia& image) { + // TODO: need to get this working for shortcuts. + + for (IDToItemControllerMap::const_iterator i = + id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + LauncherItemController* controller = i->second; + if (controller->app_id() != id) + continue; + if (controller->image_set_by_controller()) + continue; + int index = model_->ItemIndexByID(i->first); + if (index == -1) + continue; + ash::LauncherItem item = model_->items()[index]; + item.image = image; + model_->Set(index, item); + // It's possible we're waiting on more than one item, so don't break. + } +} + +void ChromeLauncherControllerPerApp::OnAutoHideBehaviorChanged( + aura::RootWindow* root_window, + ash::ShelfAutoHideBehavior new_behavior) { + SetShelfAutoHideBehaviorPrefs(new_behavior, root_window); +} + +void ChromeLauncherControllerPerApp::SetLauncherItemImage( + ash::LauncherID launcher_id, + const gfx::ImageSkia& image) { + int index = model_->ItemIndexByID(launcher_id); + if (index == -1) + return; + ash::LauncherItem item = model_->items()[index]; + item.image = image; + model_->Set(index, item); +} + +bool ChromeLauncherControllerPerApp::IsAppPinned(const std::string& app_id) { + for (IDToItemControllerMap::const_iterator i = + id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + if (IsPinned(i->first) && i->second->app_id() == app_id) + return true; + } + return false; +} + +bool ChromeLauncherControllerPerApp::IsWindowedAppInLauncher( + const std::string& app_id) { + int index = model_->ItemIndexByID(GetLauncherIDForAppID(app_id)); + if (index < 0) + return false; + + ash::LauncherItemType type = model_->items()[index].type; + return type == ash::TYPE_WINDOWED_APP; +} + +void ChromeLauncherControllerPerApp::PinAppWithID(const std::string& app_id) { + if (CanPin()) + DoPinAppWithID(app_id); + else + NOTREACHED(); +} + +void ChromeLauncherControllerPerApp::SetLaunchType( + ash::LauncherID id, + extensions::ExtensionPrefs::LaunchType launch_type) { + if (!HasItemController(id)) + return; + + profile_->GetExtensionService()->extension_prefs()->SetLaunchType( + id_to_item_controller_map_[id]->app_id(), launch_type); +} + +void ChromeLauncherControllerPerApp::UnpinAppsWithID( + const std::string& app_id) { + if (CanPin()) + DoUnpinAppsWithID(app_id); + else + NOTREACHED(); +} + +bool ChromeLauncherControllerPerApp::IsLoggedInAsGuest() { + return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord(); +} + +void ChromeLauncherControllerPerApp::CreateNewWindow() { + chrome::NewEmptyWindow( + GetProfileForNewWindows(), chrome::HOST_DESKTOP_TYPE_ASH); +} + +void ChromeLauncherControllerPerApp::CreateNewIncognitoWindow() { + chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile(), + chrome::HOST_DESKTOP_TYPE_ASH); +} + +bool ChromeLauncherControllerPerApp::CanPin() const { + const PrefService::Preference* pref = + profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps); + return pref && pref->IsUserModifiable(); +} + +void ChromeLauncherControllerPerApp::PersistPinnedState() { + if (ignore_persist_pinned_state_change_) + return; + // It is a coding error to call PersistPinnedState() if the pinned apps are + // not user-editable. The code should check earlier and not perform any + // modification actions that trigger persisting the state. + if (!CanPin()) { + NOTREACHED() << "Can't pin but pinned state being updated"; + return; + } + + // Mutating kPinnedLauncherApps is going to notify us and trigger us to + // process the change. We don't want that to happen so remove ourselves as a + // listener. + pref_change_registrar_.Remove(prefs::kPinnedLauncherApps); + { + ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); + updater->Clear(); + for (size_t i = 0; i < model_->items().size(); ++i) { + if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { + ash::LauncherID id = model_->items()[i].id; + if (HasItemController(id) && IsPinned(id)) { + base::DictionaryValue* app_value = ash::CreateAppDict( + id_to_item_controller_map_[id]->app_id()); + if (app_value) + updater->Append(app_value); + } + } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) { + PersistChromeItemIndex(i); + } + } + } + pref_change_registrar_.Add( + prefs::kPinnedLauncherApps, + base::Bind(&ChromeLauncherControllerPerApp::UpdateAppLaunchersFromPref, + base::Unretained(this))); +} + +ash::LauncherModel* ChromeLauncherControllerPerApp::model() { + return model_; +} + +Profile* ChromeLauncherControllerPerApp::profile() { + return profile_; +} + +ash::ShelfAutoHideBehavior + ChromeLauncherControllerPerApp::GetShelfAutoHideBehavior( + aura::RootWindow* root_window) const { + // Don't show the shelf in app mode. + if (chrome::IsRunningInAppMode()) + return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN; + + // See comment in |kShelfAlignment| as to why we consider two prefs. + const std::string behavior_value( + GetPrefForRootWindow(profile_->GetPrefs(), + root_window, + prefs::kShelfAutoHideBehaviorLocal, + prefs::kShelfAutoHideBehavior)); + + // Note: To maintain sync compatibility with old images of chrome/chromeos + // the set of values that may be encountered includes the now-extinct + // "Default" as well as "Never" and "Always", "Default" should now + // be treated as "Never" (http://crbug.com/146773). + if (behavior_value == ash::kShelfAutoHideBehaviorAlways) + return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; + return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER; +} + +bool ChromeLauncherControllerPerApp::CanUserModifyShelfAutoHideBehavior( + aura::RootWindow* root_window) const { + return profile_->GetPrefs()-> + FindPreference(prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable(); +} + +void ChromeLauncherControllerPerApp::ToggleShelfAutoHideBehavior( + aura::RootWindow* root_window) { + ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) == + ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ? + ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER : + ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; + SetShelfAutoHideBehaviorPrefs(behavior, root_window); + return; +} + +void ChromeLauncherControllerPerApp::RemoveTabFromRunningApp( + WebContents* tab, + const std::string& app_id) { + web_contents_to_app_id_.erase(tab); + AppIDToWebContentsListMap::iterator i_app_id = + app_id_to_web_contents_list_.find(app_id); + if (i_app_id != app_id_to_web_contents_list_.end()) { + WebContentsList* tab_list = &i_app_id->second; + tab_list->remove(tab); + if (tab_list->empty()) { + app_id_to_web_contents_list_.erase(i_app_id); + i_app_id = app_id_to_web_contents_list_.end(); + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (id) + SetItemStatus(id, ash::STATUS_CLOSED); + } + } +} + +void ChromeLauncherControllerPerApp::UpdateAppState( + content::WebContents* contents, + AppState app_state) { + std::string app_id = GetAppID(contents); + + // Check if the gMail app is loaded and it matches the given content. + // This special treatment is needed to address crbug.com/234268. + if (app_id.empty() && ContentCanBeHandledByGmailApp(contents)) + app_id = kGmailAppId; + + // Check the old |app_id| for a tab. If the contents has changed we need to + // remove it from the previous app. + if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) { + std::string last_app_id = web_contents_to_app_id_[contents]; + if (last_app_id != app_id) + RemoveTabFromRunningApp(contents, last_app_id); + } + + if (app_id.empty()) { + // Even if there is no application running, we should update the activation + // state of the associated browser. + UpdateBrowserItemStatus(); + return; + } + + web_contents_to_app_id_[contents] = app_id; + + if (app_state == APP_STATE_REMOVED) { + // The tab has gone away. + RemoveTabFromRunningApp(contents, app_id); + } else { + WebContentsList& tab_list(app_id_to_web_contents_list_[app_id]); + + if (app_state == APP_STATE_INACTIVE) { + WebContentsList::const_iterator i_tab = + std::find(tab_list.begin(), tab_list.end(), contents); + if (i_tab == tab_list.end()) + tab_list.push_back(contents); + if (i_tab != tab_list.begin()) { + // Going inactive, but wasn't the front tab, indicating that a new + // tab has already become active. + return; + } + } else { + tab_list.remove(contents); + tab_list.push_front(contents); + } + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (id) { + // If the window is active, mark the app as active. + SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ? + ash::STATUS_ACTIVE : ash::STATUS_RUNNING); + } + } + UpdateBrowserItemStatus(); +} + +void ChromeLauncherControllerPerApp::SetRefocusURLPatternForTest( + ash::LauncherID id, + const GURL& url) { + DCHECK(HasItemController(id)); + LauncherItemController* controller = id_to_item_controller_map_[id]; + + int index = model_->ItemIndexByID(id); + if (index == -1) { + NOTREACHED() << "Invalid launcher id"; + return; + } + + ash::LauncherItemType type = model_->items()[index].type; + if (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_WINDOWED_APP) { + AppShortcutLauncherItemController* app_controller = + static_cast<AppShortcutLauncherItemController*>(controller); + app_controller->set_refocus_url(url); + } else { + NOTREACHED() << "Invalid launcher type"; + } +} + +const Extension* ChromeLauncherControllerPerApp::GetExtensionForAppID( + const std::string& app_id) const { + // Some unit tests do not have a real extension. + return (profile_->GetExtensionService()) ? + profile_->GetExtensionService()->GetInstalledExtension(app_id) : NULL; +} + +void ChromeLauncherControllerPerApp::ActivateWindowOrMinimizeIfActive( + ui::BaseWindow* window, + bool allow_minimize) { + if (window->IsActive() && allow_minimize) { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableMinimizeOnSecondLauncherItemClick)) { + AnimateWindow(window->GetNativeWindow(), + views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE); + } else { + window->Minimize(); + } + } else { + window->Show(); + window->Activate(); + } +} + +void ChromeLauncherControllerPerApp::ItemSelected(const ash::LauncherItem& item, + const ui::Event& event) { + DCHECK(HasItemController(item.id)); + LauncherItemController* item_controller = id_to_item_controller_map_[item.id]; +#if defined(OS_CHROMEOS) + if (!item_controller->app_id().empty()) { + chromeos::default_pinned_apps_field_trial::RecordShelfAppClick( + item_controller->app_id()); + } +#endif + item_controller->Clicked(event); +} + +string16 ChromeLauncherControllerPerApp::GetTitle( + const ash::LauncherItem& item) { + DCHECK(HasItemController(item.id)); + return id_to_item_controller_map_[item.id]->GetTitle(); +} + +ui::MenuModel* ChromeLauncherControllerPerApp::CreateContextMenu( + const ash::LauncherItem& item, + aura::RootWindow* root_window) { + return new LauncherContextMenu(this, &item, root_window); +} + +ash::LauncherMenuModel* ChromeLauncherControllerPerApp::CreateApplicationMenu( + const ash::LauncherItem& item, + int event_flags) { + return new LauncherApplicationMenuItemModel(GetApplicationList(item, + event_flags)); +} + +ash::LauncherID ChromeLauncherControllerPerApp::GetIDByWindow( + aura::Window* window) { + int browser_index = ash::launcher::GetBrowserItemIndex(*model_); + DCHECK_GE(browser_index, 0); + ash::LauncherID browser_id = model_->items()[browser_index].id; + + IDToItemControllerMap::const_iterator i = id_to_item_controller_map_.begin(); + for (; i != id_to_item_controller_map_.end(); ++i) { + // Since a |window| can be used by multiple applications, an explicit + // application always gets chosen over the generic browser. + if (i->first != browser_id && i->second->IsCurrentlyShownInWindow(window)) + return i->first; + } + + if (i == id_to_item_controller_map_.end() && + GetBrowserShortcutLauncherItemController()-> + IsCurrentlyShownInWindow(window)) + return browser_id; + + return 0; +} + +bool ChromeLauncherControllerPerApp::IsDraggable( + const ash::LauncherItem& item) { + return (item.type == ash::TYPE_APP_SHORTCUT || + item.type == ash::TYPE_WINDOWED_APP) ? CanPin() : true; +} + +bool ChromeLauncherControllerPerApp::ShouldShowTooltip( + const ash::LauncherItem& item) { + if (item.type == ash::TYPE_APP_PANEL && + id_to_item_controller_map_[item.id]->IsVisible()) + return false; + return true; +} + +void ChromeLauncherControllerPerApp::OnLauncherCreated( + ash::Launcher* launcher) { + launchers_.insert(launcher); + launcher->shelf_widget()->shelf_layout_manager()->AddObserver(this); +} + +void ChromeLauncherControllerPerApp::OnLauncherDestroyed( + ash::Launcher* launcher) { + launchers_.erase(launcher); + // RemoveObserver is not called here, since by the time this method is called + // Launcher is already in its destructor. +} + +void ChromeLauncherControllerPerApp::LauncherItemAdded(int index) { +} + +void ChromeLauncherControllerPerApp::LauncherItemRemoved( + int index, + ash::LauncherID id) { +} + +void ChromeLauncherControllerPerApp::LauncherItemMoved( + int start_index, + int target_index) { + ash::LauncherID id = model_->items()[target_index].id; + if (HasItemController(id) && IsPinned(id)) + PersistPinnedState(); +} + +void ChromeLauncherControllerPerApp::LauncherItemChanged( + int index, + const ash::LauncherItem& old_item) { + ash::LauncherID id = model_->items()[index].id; + DCHECK(HasItemController(id)); + id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item); +} + +void ChromeLauncherControllerPerApp::LauncherStatusChanged() { +} + +void ChromeLauncherControllerPerApp::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_EXTENSION_LOADED: { + const Extension* extension = + content::Details<const Extension>(details).ptr(); + if (IsAppPinned(extension->id())) { + // Clear and re-fetch to ensure icon is up-to-date. + app_icon_loader_->ClearImage(extension->id()); + app_icon_loader_->FetchImage(extension->id()); + } + + UpdateAppLaunchersFromPref(); + break; + } + case chrome::NOTIFICATION_EXTENSION_UNLOADED: { + const content::Details<extensions::UnloadedExtensionInfo>& unload_info( + details); + const Extension* extension = unload_info->extension; + const std::string& id = extension->id(); + // Since we might have windowed apps of this type which might have + // outstanding locks which needs to be removed. + if (GetLauncherIDForAppID(id) && + unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) { + CloseWindowedAppsFromRemovedExtension(id); + } + + if (IsAppPinned(id)) { + if (unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) { + DoUnpinAppsWithID(id); + app_icon_loader_->ClearImage(id); + } else { + app_icon_loader_->UpdateImage(id); + } + } + break; + } + default: + NOTREACHED() << "Unexpected notification type=" << type; + } +} + +void ChromeLauncherControllerPerApp::OnShelfAlignmentChanged( + aura::RootWindow* root_window) { + const char* pref_value = NULL; + switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) { + case ash::SHELF_ALIGNMENT_BOTTOM: + pref_value = ash::kShelfAlignmentBottom; + break; + case ash::SHELF_ALIGNMENT_LEFT: + pref_value = ash::kShelfAlignmentLeft; + break; + case ash::SHELF_ALIGNMENT_RIGHT: + pref_value = ash::kShelfAlignmentRight; + break; + case ash::SHELF_ALIGNMENT_TOP: + pref_value = ash::kShelfAlignmentTop; + } + + UpdatePerDisplayPref( + profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value); + + if (root_window == ash::Shell::GetPrimaryRootWindow()) { + // See comment in |kShelfAlignment| about why we have two prefs here. + profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value); + profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value); + } +} + +void ChromeLauncherControllerPerApp::OnDisplayConfigurationChanging() { +} + +void ChromeLauncherControllerPerApp::OnDisplayConfigurationChanged() { + SetShelfBehaviorsFromPrefs(); +} + +void ChromeLauncherControllerPerApp::OnIsSyncingChanged() { + PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); + MaybePropagatePrefToLocal(prefs, + prefs::kShelfAlignmentLocal, + prefs::kShelfAlignment); + MaybePropagatePrefToLocal(prefs, + prefs::kShelfAutoHideBehaviorLocal, + prefs::kShelfAutoHideBehavior); +} + +void ChromeLauncherControllerPerApp::OnAppSyncUIStatusChanged() { + if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING) + model_->SetStatus(ash::LauncherModel::STATUS_LOADING); + else + model_->SetStatus(ash::LauncherModel::STATUS_NORMAL); +} + +void ChromeLauncherControllerPerApp::ExtensionEnableFlowFinished() { + LaunchApp(extension_enable_flow_->extension_id(), ui::EF_NONE); + extension_enable_flow_.reset(); +} + +void ChromeLauncherControllerPerApp::ExtensionEnableFlowAborted( + bool user_initiated) { + extension_enable_flow_.reset(); +} + +ChromeLauncherAppMenuItems ChromeLauncherControllerPerApp::GetApplicationList( + const ash::LauncherItem& item, + int event_flags) { + // Make sure that there is a controller associated with the id and that the + // extension itself is a valid application and not a panel. + if (!HasItemController(item.id) || + !GetLauncherIDForAppID(id_to_item_controller_map_[item.id]->app_id())) + return ChromeLauncherAppMenuItems().Pass(); + + return id_to_item_controller_map_[item.id]->GetApplicationList(event_flags); +} + +std::vector<content::WebContents*> +ChromeLauncherControllerPerApp::GetV1ApplicationsFromAppId( + std::string app_id) { + ash::LauncherID id = GetLauncherIDForAppID(app_id); + + // If there is no such an item pinned to the launcher, no menu gets created. + if (id) { + LauncherItemController* controller = id_to_item_controller_map_[id]; + DCHECK(controller); + if (controller->type() == LauncherItemController::TYPE_SHORTCUT) + return GetV1ApplicationsFromController(controller); + } + return std::vector<content::WebContents*>(); +} + +void ChromeLauncherControllerPerApp::ActivateShellApp( + const std::string& app_id, + int index) { + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (id) { + LauncherItemController* controller = id_to_item_controller_map_[id]; + if (controller->type() == LauncherItemController::TYPE_APP) { + ShellWindowLauncherItemController* shell_window_controller = + static_cast<ShellWindowLauncherItemController*>(controller); + shell_window_controller->ActivateIndexedApp(index); + } + } +} + +bool ChromeLauncherControllerPerApp::IsWebContentHandledByApplication( + content::WebContents* web_contents, + const std::string& app_id) { + if ((web_contents_to_app_id_.find(web_contents) != + web_contents_to_app_id_.end()) && + (web_contents_to_app_id_[web_contents] == app_id)) + return true; + return (app_id == kGmailAppId && ContentCanBeHandledByGmailApp(web_contents)); +} + +bool ChromeLauncherControllerPerApp::ContentCanBeHandledByGmailApp( + content::WebContents* web_contents) { + ash::LauncherID id = GetLauncherIDForAppID(kGmailAppId); + if (id) { + const GURL url = web_contents->GetURL(); + // We need to extend the application matching for the gMail app beyond the + // manifest file's specification. This is required because of the namespace + // overlap with the offline app ("/mail/mu/"). + if (!MatchPattern(url.path(), "/mail/mu/*") && + MatchPattern(url.path(), "/mail/*") && + GetExtensionForAppID(kGmailAppId) && + GetExtensionForAppID(kGmailAppId)->OverlapsWithOrigin(url)) + return true; + } + return false; +} + +gfx::Image ChromeLauncherControllerPerApp::GetAppListIcon( + content::WebContents* web_contents) const { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + if (IsIncognito(web_contents)) + return rb.GetImageNamed(IDR_AURA_LAUNCHER_LIST_INCOGNITO_BROWSER); + FaviconTabHelper* favicon_tab_helper = + FaviconTabHelper::FromWebContents(web_contents); + gfx::Image result = favicon_tab_helper->GetFavicon(); + if (result.IsEmpty()) + return rb.GetImageNamed(IDR_DEFAULT_FAVICON); + return result; +} + +string16 ChromeLauncherControllerPerApp::GetAppListTitle( + content::WebContents* web_contents) const { + string16 title = web_contents->GetTitle(); + if (!title.empty()) + return title; + WebContentsToAppIDMap::const_iterator iter = + web_contents_to_app_id_.find(web_contents); + if (iter != web_contents_to_app_id_.end()) { + std::string app_id = iter->second; + const extensions::Extension* extension = GetExtensionForAppID(app_id); + if (extension) + return UTF8ToUTF16(extension->name()); + } + return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE); +} + +void ChromeLauncherControllerPerApp::OnBrowserRemoved(Browser* browser) { + // When called by a unit test it is possible that there is no shell. + // In that case, the following function should not get called. + if (ash::Shell::HasInstance()) + UpdateBrowserItemStatus(); +} + +ash::LauncherID ChromeLauncherControllerPerApp::CreateAppShortcutLauncherItem( + const std::string& app_id, + int index) { + return CreateAppShortcutLauncherItemWithType(app_id, + index, + ash::TYPE_APP_SHORTCUT); +} + +void ChromeLauncherControllerPerApp::SetAppTabHelperForTest( + AppTabHelper* helper) { + app_tab_helper_.reset(helper); +} + +void ChromeLauncherControllerPerApp::SetAppIconLoaderForTest( + extensions::AppIconLoader* loader) { + app_icon_loader_.reset(loader); +} + +const std::string& +ChromeLauncherControllerPerApp::GetAppIdFromLauncherIdForTest( + ash::LauncherID id) { + return id_to_item_controller_map_[id]->app_id(); +} + +ash::LauncherID +ChromeLauncherControllerPerApp::CreateAppShortcutLauncherItemWithType( + const std::string& app_id, + int index, + ash::LauncherItemType launcher_item_type) { + AppShortcutLauncherItemController* controller = + new AppShortcutLauncherItemController(app_id, this); + ash::LauncherID launcher_id = InsertAppLauncherItem( + controller, app_id, ash::STATUS_CLOSED, index, launcher_item_type); + return launcher_id; +} + +void ChromeLauncherControllerPerApp::UpdateBrowserItemStatus() { + // Determine the new browser's active state and change if necessary. + size_t browser_index = ash::launcher::GetBrowserItemIndex(*model_); + DCHECK_GE(browser_index, 0u); + ash::LauncherItem browser_item = model_->items()[browser_index]; + ash::LauncherItemStatus browser_status = ash::STATUS_CLOSED; + + aura::Window* window = ash::wm::GetActiveWindow(); + if (window) { + // Check if the active browser / tab is a browser which is not an app, + // a windowed app, a popup or any other item which is not a browser of + // interest. + Browser* browser = chrome::FindBrowserWithWindow(window); + if (IsBrowserRepresentedInBrowserList(browser)) { + browser_status = ash::STATUS_ACTIVE; + const ash::LauncherItems& items = model_->items(); + // If another launcher item has claimed to be active, we don't. + for (size_t i = 0; + i < items.size() && browser_status == ash::STATUS_ACTIVE; ++i) { + if (i != browser_index && items[i].status == ash::STATUS_ACTIVE) + browser_status = ash::STATUS_RUNNING; + } + } + } + + if (browser_status == ash::STATUS_CLOSED) { + const BrowserList* ash_browser_list = + BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); + for (BrowserList::const_reverse_iterator it = + ash_browser_list->begin_last_active(); + it != ash_browser_list->end_last_active() && + browser_status == ash::STATUS_CLOSED; ++it) { + if (IsBrowserRepresentedInBrowserList(*it)) + browser_status = ash::STATUS_RUNNING; + } + } + + if (browser_status != browser_item.status) { + browser_item.status = browser_status; + model_->Set(browser_index, browser_item); + } +} + +Profile* ChromeLauncherControllerPerApp::GetProfileForNewWindows() { + return ProfileManager::GetDefaultProfileOrOffTheRecord(); +} + +void ChromeLauncherControllerPerApp::LauncherItemClosed(ash::LauncherID id) { + IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); + CHECK(iter != id_to_item_controller_map_.end()); + CHECK(iter->second); + app_icon_loader_->ClearImage(iter->second->app_id()); + iter->second->OnRemoved(); + id_to_item_controller_map_.erase(iter); + int index = model_->ItemIndexByID(id); + // A "browser proxy" is not known to the model and this removal does + // therefore not need to be propagated to the model. + if (index != -1) + model_->RemoveItemAt(index); +} + +void ChromeLauncherControllerPerApp::DoPinAppWithID( + const std::string& app_id) { + // If there is an item, do nothing and return. + if (IsAppPinned(app_id)) + return; + + ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id); + if (launcher_id) { + // App item exists, pin it + Pin(launcher_id); + } else { + // Otherwise, create a shortcut item for it. + CreateAppShortcutLauncherItem(app_id, model_->item_count()); + if (CanPin()) + PersistPinnedState(); + } +} + +void ChromeLauncherControllerPerApp::DoUnpinAppsWithID( + const std::string& app_id) { + for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ) { + IDToItemControllerMap::iterator current(i); + ++i; + if (current->second->app_id() == app_id && IsPinned(current->first)) + Unpin(current->first); + } +} + +void ChromeLauncherControllerPerApp::UpdateAppLaunchersFromPref() { + // Construct a vector representation of to-be-pinned apps from the pref. + std::vector<std::string> pinned_apps; + int chrome_icon_index = GetChromeIconIndexFromPref(); + int index = 0; + int max_index = model_->item_count(); + // Using the alternate shelf layout the App Icon should be the first item in + // the list thus start adding items at slot 1 (instead of slot 0). + if (ash::switches::UseAlternateShelfLayout()) { + ++index; + ++max_index; + // The alternate shelf layout's icon position will always include the + // AppLauncher which needs to be subtracted here. + if (chrome_icon_index > 0) + --chrome_icon_index; + } + const base::ListValue* pinned_apps_pref = + profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); + for (base::ListValue::const_iterator it(pinned_apps_pref->begin()); + it != pinned_apps_pref->end(); ++it) { + // To preserve the Chrome icon position, we insert a dummy slot for it - if + // the model has a Chrome item. While initializing we can come here with no + // item in which case the count would be 1 or below. + if (it - pinned_apps_pref->begin() == chrome_icon_index && + model_->item_count() > 1) { + pinned_apps.push_back(extension_misc::kChromeAppId); + } + + DictionaryValue* app = NULL; + std::string app_id; + if ((*it)->GetAsDictionary(&app) && + app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) && + std::find(pinned_apps.begin(), pinned_apps.end(), app_id) == + pinned_apps.end() && + app_tab_helper_->IsValidID(app_id)) { + pinned_apps.push_back(app_id); + } + } + + // Walk the model and |pinned_apps| from the pref lockstep, adding and + // removing items as necessary. NB: This code uses plain old indexing instead + // of iterators because of model mutations as part of the loop. + std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin()); + for (; index < max_index && pref_app_id != pinned_apps.end(); ++index) { + // If the next app launcher according to the pref is present in the model, + // delete all app launcher entries in between. + if (*pref_app_id == extension_misc::kChromeAppId || + IsAppPinned(*pref_app_id)) { + for (; index < max_index; ++index) { + const ash::LauncherItem& item(model_->items()[index]); + if (item.type != ash::TYPE_APP_SHORTCUT && + item.type != ash::TYPE_BROWSER_SHORTCUT) + continue; + + IDToItemControllerMap::const_iterator entry = + id_to_item_controller_map_.find(item.id); + if ((extension_misc::kChromeAppId == *pref_app_id && + item.type == ash::TYPE_BROWSER_SHORTCUT) || + (entry != id_to_item_controller_map_.end() && + entry->second->app_id() == *pref_app_id)) { + ++pref_app_id; + break; + } else { + if (item.type == ash::TYPE_BROWSER_SHORTCUT) { + // We cannot delete the browser shortcut. As such we move it up by + // one. To avoid any side effects from our pinned state observer, we + // do not call the model directly. + MoveItemWithoutPinnedStateChangeNotification(index, index + 1); + } else { + LauncherItemClosed(item.id); + --max_index; + } + --index; + } + } + // If the item wasn't found, that means id_to_item_controller_map_ + // is out of sync. + DCHECK(index < max_index); + } else { + // This app wasn't pinned before, insert a new entry. + ash::LauncherID id = CreateAppShortcutLauncherItem(*pref_app_id, index); + index = model_->ItemIndexByID(id); + ++pref_app_id; + } + } + + // Remove any trailing existing items. + while (index < model_->item_count()) { + const ash::LauncherItem& item(model_->items()[index]); + if (item.type == ash::TYPE_APP_SHORTCUT) + LauncherItemClosed(item.id); + else + ++index; + } + + // Append unprocessed items from the pref to the end of the model. + for (; pref_app_id != pinned_apps.end(); ++pref_app_id) { + // Ignore the chrome icon. + if (*pref_app_id != extension_misc::kChromeAppId) + DoPinAppWithID(*pref_app_id); + } + +} + +void ChromeLauncherControllerPerApp::SetShelfAutoHideBehaviorPrefs( + ash::ShelfAutoHideBehavior behavior, + aura::RootWindow* root_window) { + const char* value = NULL; + switch (behavior) { + case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: + value = ash::kShelfAutoHideBehaviorAlways; + break; + case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER: + value = ash::kShelfAutoHideBehaviorNever; + break; + case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN: + // This one should not be a valid preference option for now. We only want + // to completely hide it when we run app mode. + NOTREACHED(); + return; + } + + UpdatePerDisplayPref( + profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value); + + if (root_window == ash::Shell::GetPrimaryRootWindow()) { + // See comment in |kShelfAlignment| about why we have two prefs here. + profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value); + profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value); + } +} + +void ChromeLauncherControllerPerApp::SetShelfAutoHideBehaviorFromPrefs() { + ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); + + for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin(); + iter != root_windows.end(); ++iter) { + ash::Shell::GetInstance()->SetShelfAutoHideBehavior( + GetShelfAutoHideBehavior(*iter), *iter); + } +} + +void ChromeLauncherControllerPerApp::SetShelfAlignmentFromPrefs() { + if (!ash::ShelfWidget::ShelfAlignmentAllowed()) + return; + + ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); + + for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin(); + iter != root_windows.end(); ++iter) { + // See comment in |kShelfAlignment| as to why we consider two prefs. + const std::string alignment_value( + GetPrefForRootWindow(profile_->GetPrefs(), + *iter, + prefs::kShelfAlignmentLocal, + prefs::kShelfAlignment)); + ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM; + if (alignment_value == ash::kShelfAlignmentLeft) + alignment = ash::SHELF_ALIGNMENT_LEFT; + else if (alignment_value == ash::kShelfAlignmentRight) + alignment = ash::SHELF_ALIGNMENT_RIGHT; + else if (alignment_value == ash::kShelfAlignmentTop) + alignment = ash::SHELF_ALIGNMENT_TOP; + ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter); + } +} + +void ChromeLauncherControllerPerApp::SetShelfBehaviorsFromPrefs() { + SetShelfAutoHideBehaviorFromPrefs(); + SetShelfAlignmentFromPrefs(); +} + +WebContents* ChromeLauncherControllerPerApp::GetLastActiveWebContents( + const std::string& app_id) { + AppIDToWebContentsListMap::const_iterator i = + app_id_to_web_contents_list_.find(app_id); + if (i == app_id_to_web_contents_list_.end()) + return NULL; + DCHECK_GT(i->second.size(), 0u); + return *i->second.begin(); +} + +ash::LauncherID ChromeLauncherControllerPerApp::InsertAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status, + int index, + ash::LauncherItemType launcher_item_type) { + ash::LauncherID id = model_->next_id(); + CHECK(!HasItemController(id)); + CHECK(controller); + id_to_item_controller_map_[id] = controller; + controller->set_launcher_id(id); + + ash::LauncherItem item; + item.type = launcher_item_type; + item.is_incognito = false; + item.image = extensions::IconsInfo::GetDefaultAppIcon(); + + WebContents* active_tab = GetLastActiveWebContents(app_id); + if (active_tab) { + Browser* browser = chrome::FindBrowserWithWebContents(active_tab); + DCHECK(browser); + if (browser->window()->IsActive()) + status = ash::STATUS_ACTIVE; + else + status = ash::STATUS_RUNNING; + } + item.status = status; + + model_->AddAt(index, item); + + app_icon_loader_->FetchImage(app_id); + + return id; +} + +bool ChromeLauncherControllerPerApp::HasItemController( + ash::LauncherID id) const { + return id_to_item_controller_map_.find(id) != + id_to_item_controller_map_.end(); +} + +std::vector<content::WebContents*> +ChromeLauncherControllerPerApp::GetV1ApplicationsFromController( + LauncherItemController* controller) { + DCHECK(controller->type() == LauncherItemController::TYPE_SHORTCUT); + AppShortcutLauncherItemController* app_controller = + static_cast<AppShortcutLauncherItemController*>(controller); + return app_controller->GetRunningApplications(); +} + +bool ChromeLauncherControllerPerApp::IsBrowserRepresentedInBrowserList( + Browser* browser) { + return (browser && + (browser->is_type_tabbed() || + !browser->is_app() || + !browser->is_type_popup() || + GetLauncherIDForAppID(web_app::GetExtensionIdFromApplicationName( + browser->app_name())) <= 0)); +} + +LauncherItemController* +ChromeLauncherControllerPerApp::GetBrowserShortcutLauncherItemController() { + for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + int index = model_->ItemIndexByID(i->first); + const ash::LauncherItem& item = model_->items()[index]; + if (item.type == ash::TYPE_BROWSER_SHORTCUT) + return i->second; + } + // LauncerItemController For Browser Shortcut must be existed. If it does not + // existe create it. + ash::LauncherID id = CreateBrowserShortcutLauncherItem(); + DCHECK(id_to_item_controller_map_[id]); + return id_to_item_controller_map_[id]; +} + +ash::LauncherID +ChromeLauncherControllerPerApp::CreateBrowserShortcutLauncherItem() { + ash::LauncherItem browser_shortcut; + browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; + browser_shortcut.is_incognito = false; + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32); + ash::LauncherID id = model_->next_id(); + size_t index = GetChromeIconIndexFromPref(); + model_->AddAt(index, browser_shortcut); + browser_item_controller_.reset( + new BrowserShortcutLauncherItemController(this, profile_)); + id_to_item_controller_map_[id] = browser_item_controller_.get(); + id_to_item_controller_map_[id]->set_launcher_id(id); + return id; +} + +void ChromeLauncherControllerPerApp::PersistChromeItemIndex(int index) { + profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index); +} + +int ChromeLauncherControllerPerApp::GetChromeIconIndexFromPref() const { + size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex); + const base::ListValue* pinned_apps_pref = + profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); + if (ash::switches::UseAlternateShelfLayout()) + return std::max(static_cast<size_t>(1), + std::min(pinned_apps_pref->GetSize() + 1, index)); + return std::max(static_cast<size_t>(0), + std::min(pinned_apps_pref->GetSize(), index)); +} + +bool ChromeLauncherControllerPerApp::IsIncognito( + content::WebContents* web_contents) const { + const Profile* profile = + Profile::FromBrowserContext(web_contents->GetBrowserContext()); + return profile->IsOffTheRecord() && !profile->IsGuestSession(); +} + +void ChromeLauncherControllerPerApp::CloseWindowedAppsFromRemovedExtension( + const std::string& app_id) { + // This function cannot rely on the controller's enumeration functionality + // since the extension has already be unloaded. + const BrowserList* ash_browser_list = + BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); + std::vector<Browser*> browser_to_close; + for (BrowserList::const_reverse_iterator + it = ash_browser_list->begin_last_active(); + it != ash_browser_list->end_last_active(); ++it) { + Browser* browser = *it; + if (!browser->is_type_tabbed() && + browser->is_type_popup() && + browser->is_app() && + app_id == web_app::GetExtensionIdFromApplicationName( + browser->app_name())) { + browser_to_close.push_back(browser); + } + } + while (!browser_to_close.empty()) { + TabStripModel* tab_strip = browser_to_close.back()->tab_strip_model(); + tab_strip->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE); + browser_to_close.pop_back(); + } +} + +void +ChromeLauncherControllerPerApp::MoveItemWithoutPinnedStateChangeNotification( + int source_index, int target_index) { + base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true); + model_->Move(source_index, target_index); +} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h new file mode 100644 index 0000000..951589c --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h @@ -0,0 +1,508 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_APP_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_APP_H_ + +#include <list> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "ash/display/display_controller.h" +#include "ash/launcher/launcher_model_observer.h" +#include "ash/launcher/launcher_types.h" +#include "ash/shelf/shelf_layout_manager_observer.h" +#include "ash/shelf/shelf_types.h" +#include "ash/shell_observer.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/prefs/pref_change_registrar.h" +#include "chrome/browser/extensions/extension_prefs.h" +#include "chrome/browser/prefs/pref_service_syncable_observer.h" +#include "chrome/browser/ui/ash/app_sync_ui_state_observer.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" +#include "chrome/browser/ui/browser_list_observer.h" +#include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "ui/aura/window_observer.h" + +class AppSyncUIState; +class Browser; +class BrowserLauncherItemControllerTest; +class BrowserShortcutLauncherItemController; +class ExtensionEnableFlow; +class LauncherItemController; +class Profile; +class TabContents; + +namespace ash { +class LauncherModel; +} + +namespace aura { +class Window; +} + +namespace content { +class WebContents; +} + +namespace ui { +class BaseWindow; +} + +// ChromeLauncherControllerPerApp manages the launcher items needed for content +// content windows. Launcher items have a type, an optional app id, and a +// controller. This incarnation groups running tabs/windows in application +// specific lists. +// * Tabbed browsers and browser app windows have BrowserLauncherItemController, +// owned by the BrowserView instance. +// * App shell windows have ShellWindowLauncherItemController, owned by +// ShellWindowLauncherController. +// * Shortcuts have no LauncherItemController. +class ChromeLauncherControllerPerApp + : public ash::LauncherModelObserver, + public ash::ShellObserver, + public ash::DisplayController::Observer, + public ChromeLauncherController, + public content::NotificationObserver, + public PrefServiceSyncableObserver, + public AppSyncUIStateObserver, + public ExtensionEnableFlowDelegate, + public chrome::BrowserListObserver, + public ash::ShelfLayoutManagerObserver { + public: + ChromeLauncherControllerPerApp(Profile* profile, ash::LauncherModel* model); + virtual ~ChromeLauncherControllerPerApp(); + + // ChromeLauncherController overrides: + + // Initializes this ChromeLauncherControllerPerApp. + virtual void Init() OVERRIDE; + + // Returns the new per application interface of the given launcher. If it is + // a per browser (old) controller, it will return NULL; + // TODO(skuhne): Remove when we rip out the old launcher. + virtual ChromeLauncherControllerPerApp* GetPerAppInterface() OVERRIDE; + + // Creates a new tabbed item on the launcher for |controller|. + virtual ash::LauncherID CreateTabbedLauncherItem( + LauncherItemController* controller, + IncognitoState is_incognito, + ash::LauncherItemStatus status) OVERRIDE; + + // Creates a new app item on the launcher for |controller|. + virtual ash::LauncherID CreateAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status) OVERRIDE; + + // Updates the running status of an item. It will also update the status of + // browsers launcher item if needed. + virtual void SetItemStatus(ash::LauncherID id, + ash::LauncherItemStatus status) OVERRIDE; + + // Updates the controller associated with id (which should be a shortcut). + // |controller| remains owned by caller. + virtual void SetItemController(ash::LauncherID id, + LauncherItemController* controller) OVERRIDE; + + // Closes or unpins the launcher item. + virtual void CloseLauncherItem(ash::LauncherID id) OVERRIDE; + + // Pins the specified id. Currently only supports platform apps. + virtual void Pin(ash::LauncherID id) OVERRIDE; + + // Unpins the specified id, closing if not running. + virtual void Unpin(ash::LauncherID id) OVERRIDE; + + // Returns true if the item identified by |id| is pinned. + virtual bool IsPinned(ash::LauncherID id) OVERRIDE; + + // Pins/unpins the specified id. + virtual void TogglePinned(ash::LauncherID id) OVERRIDE; + + // Returns true if the specified item can be pinned or unpinned. Only apps can + // be pinned. + virtual bool IsPinnable(ash::LauncherID id) const OVERRIDE; + + // If there is no launcher item in the launcher for application |app_id|, one + // gets created. The (existing or created) launcher items get then locked + // against a users un-pinning removal. + virtual void LockV1AppWithID(const std::string& app_id) OVERRIDE; + + // A previously locked launcher item of type |app_id| gets unlocked. If the + // lock count reaches 0 and the item is not pinned it will go away. + virtual void UnlockV1AppWithID(const std::string& app_id) OVERRIDE; + + // Requests that the launcher item controller specified by |id| open a new + // instance of the app. |event_flags| holds the flags of the event which + // triggered this command. + virtual void Launch(ash::LauncherID id, int event_flags) OVERRIDE; + + // Closes the specified item. + virtual void Close(ash::LauncherID id) OVERRIDE; + + // Returns true if the specified item is open. + virtual bool IsOpen(ash::LauncherID id) OVERRIDE; + + // Returns true if the specified item is for a platform app. + virtual bool IsPlatformApp(ash::LauncherID id) OVERRIDE; + + // Opens a new instance of the application identified by |app_id|. + // Used by the app-list, and by pinned-app launcher items. + virtual void LaunchApp(const std::string& app_id, int event_flags) OVERRIDE; + + // If |app_id| is running, reactivates the app's most recently active window, + // otherwise launches and activates the app. + // Used by the app-list, and by pinned-app launcher items. + virtual void ActivateApp(const std::string& app_id, int event_flags) OVERRIDE; + + // Returns the launch type of app for the specified id. + virtual extensions::ExtensionPrefs::LaunchType GetLaunchType( + ash::LauncherID id) OVERRIDE; + + // Returns the id of the app for the specified tab. + virtual std::string GetAppID(content::WebContents* tab) OVERRIDE; + + // Returns the |LauncherModel|'s ID or 0 if the AppId was not found. + virtual ash::LauncherID GetLauncherIDForAppID( + const std::string& app_id) OVERRIDE; + virtual std::string GetAppIDForLauncherID(ash::LauncherID id) OVERRIDE; + + // Set the image for a specific launcher item (e.g. when set by the app). + virtual void SetLauncherItemImage(ash::LauncherID launcher_id, + const gfx::ImageSkia& image) OVERRIDE; + + // Returns true if a pinned launcher item with given |app_id| could be found. + virtual bool IsAppPinned(const std::string& app_id) OVERRIDE; + + // Find out if the given application |id| is a windowed app item and not a + // pinned item in the launcher. + bool IsWindowedAppInLauncher(const std::string& app_id); + + // Pins an app with |app_id| to launcher. If there is a running instance in + // launcher, the running instance is pinned. If there is no running instance, + // a new launcher item is created and pinned. + virtual void PinAppWithID(const std::string& app_id) OVERRIDE; + + // Updates the launche type of the app for the specified id to |launch_type|. + virtual void SetLaunchType( + ash::LauncherID id, + extensions::ExtensionPrefs::LaunchType launch_type) OVERRIDE; + + // Unpins any app items whose id is |app_id|. + virtual void UnpinAppsWithID(const std::string& app_id) OVERRIDE; + + // Returns true if the user is currently logged in as a guest. + virtual bool IsLoggedInAsGuest() OVERRIDE; + + // Invoked when user clicks on button in the launcher and there is no last + // used window (or CTRL is held with the click). + virtual void CreateNewWindow() OVERRIDE; + + // Invoked when the user clicks on button in the launcher to create a new + // incognito window. + virtual void CreateNewIncognitoWindow() OVERRIDE; + + // Checks whether the user is allowed to pin apps. Pinning may be disallowed + // by policy in case there is a pre-defined set of pinned apps. + virtual bool CanPin() const OVERRIDE; + + // Updates the pinned pref state. The pinned state consists of a list pref. + // Each item of the list is a dictionary. The key |kAppIDPath| gives the + // id of the app. + virtual void PersistPinnedState() OVERRIDE; + + virtual ash::LauncherModel* model() OVERRIDE; + + virtual Profile* profile() OVERRIDE; + + // Gets the shelf auto-hide behavior on |root_window|. + virtual ash::ShelfAutoHideBehavior GetShelfAutoHideBehavior( + aura::RootWindow* root_window) const OVERRIDE; + + // Returns |true| if the user is allowed to modify the shelf auto-hide + // behavior on |root_window|. + virtual bool CanUserModifyShelfAutoHideBehavior( + aura::RootWindow* root_window) const OVERRIDE; + + // Toggles the shelf auto-hide behavior on |root_window|. Does nothing if the + // user is not allowed to modify the auto-hide behavior. + virtual void ToggleShelfAutoHideBehavior( + aura::RootWindow* root_window) OVERRIDE; + + // The tab no longer represents its previously identified application. + virtual void RemoveTabFromRunningApp(content::WebContents* tab, + const std::string& app_id) OVERRIDE; + + // Notify the controller that the state of an non platform app's tabs + // have changed, + virtual void UpdateAppState(content::WebContents* contents, + AppState app_state) OVERRIDE; + + // Limits application refocusing to urls that match |url| for |id|. + virtual void SetRefocusURLPatternForTest(ash::LauncherID id, + const GURL& url) OVERRIDE; + + // Returns the extension identified by |app_id|. + virtual const extensions::Extension* GetExtensionForAppID( + const std::string& app_id) const OVERRIDE; + + // Activates a |window|. If |allow_minimize| is true and the system allows + // it, the the window will get minimized instead. + virtual void ActivateWindowOrMinimizeIfActive(ui::BaseWindow* window, + bool allow_minimize) OVERRIDE; + + // ash::LauncherDelegate overrides: + virtual void ItemSelected(const ash::LauncherItem& item, + const ui::Event& event) OVERRIDE; + virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE; + virtual ui::MenuModel* CreateContextMenu( + const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE; + virtual ash::LauncherMenuModel* CreateApplicationMenu( + const ash::LauncherItem& item, + int event_flags) OVERRIDE; + virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE; + virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE; + virtual bool ShouldShowTooltip(const ash::LauncherItem& item) OVERRIDE; + virtual void OnLauncherCreated(ash::Launcher* launcher) OVERRIDE; + virtual void OnLauncherDestroyed(ash::Launcher* launcher) OVERRIDE; + + // ash::LauncherModelObserver overrides: + virtual void LauncherItemAdded(int index) OVERRIDE; + virtual void LauncherItemRemoved(int index, ash::LauncherID id) OVERRIDE; + virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE; + virtual void LauncherItemChanged(int index, + const ash::LauncherItem& old_item) OVERRIDE; + virtual void LauncherStatusChanged() OVERRIDE; + + // content::NotificationObserver overrides: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // ash::ShellObserver overrides: + virtual void OnShelfAlignmentChanged(aura::RootWindow* root_window) OVERRIDE; + + // ash::DisplayController::Observer overrides: + virtual void OnDisplayConfigurationChanging() OVERRIDE; + virtual void OnDisplayConfigurationChanged() OVERRIDE; + + // PrefServiceSyncableObserver overrides: + virtual void OnIsSyncingChanged() OVERRIDE; + + // AppSyncUIStateObserver overrides: + virtual void OnAppSyncUIStatusChanged() OVERRIDE; + + // ExtensionEnableFlowDelegate overrides: + virtual void ExtensionEnableFlowFinished() OVERRIDE; + virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE; + + // extensions::AppIconLoader overrides: + virtual void SetAppImage(const std::string& app_id, + const gfx::ImageSkia& image) OVERRIDE; + + // ash::ShelfLayoutManagerObserver overrides: + virtual void OnAutoHideBehaviorChanged( + aura::RootWindow* root_window, + ash::ShelfAutoHideBehavior new_behavior) OVERRIDE; + + // Get the list of all running incarnations of this item. + // |event_flags| specifies the flags which were set by the event which + // triggered this menu generation. It can be used to generate different lists. + ChromeLauncherAppMenuItems GetApplicationList(const ash::LauncherItem& item, + int event_flags); + + // Get the list of all tabs which belong to a certain application type. + std::vector<content::WebContents*> GetV1ApplicationsFromAppId( + std::string app_id); + + // Activates a specified shell application. + void ActivateShellApp(const std::string& app_id, int index); + + // Checks if a given |web_contents| is known to be associated with an + // application of type |app_id|. + bool IsWebContentHandledByApplication(content::WebContents* web_contents, + const std::string& app_id); + + // Check if the gMail app is loaded and it can handle the given web content. + // This special treatment is required to address crbug.com/234268. + bool ContentCanBeHandledByGmailApp(content::WebContents* web_contents); + + // Get the favicon for the application list entry for |web_contents|. + // Note that for incognito windows the incognito icon will be returned. + // If |web_contents| has not loaded, returns the default favicon. + gfx::Image GetAppListIcon(content::WebContents* web_contents) const; + + // Get the title for the applicatoin list entry for |web_contents|. + // If |web_contents| has not loaded, returns "Net Tab". + string16 GetAppListTitle(content::WebContents* web_contents) const; + + // Overridden from chrome::BrowserListObserver. + virtual void OnBrowserRemoved(Browser* browser) OVERRIDE; + + // Returns true when the given |browser| is listed in the browser application + // list. + bool IsBrowserRepresentedInBrowserList(Browser* browser); + + // Returns the LauncherItemController of BrowserShortcut. + LauncherItemController* GetBrowserShortcutLauncherItemController(); + + protected: + // ChromeLauncherController overrides: + + // Creates a new app shortcut item and controller on the launcher at |index|. + // Use kInsertItemAtEnd to add a shortcut as the last item. + virtual ash::LauncherID CreateAppShortcutLauncherItem( + const std::string& app_id, + int index) OVERRIDE; + + // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class. + // These are intended for testing. + virtual void SetAppTabHelperForTest(AppTabHelper* helper) OVERRIDE; + virtual void SetAppIconLoaderForTest( + extensions::AppIconLoader* loader) OVERRIDE; + virtual const std::string& GetAppIdFromLauncherIdForTest( + ash::LauncherID id) OVERRIDE; + + private: + friend class ChromeLauncherControllerPerAppTest; + friend class LauncherPerAppAppBrowserTest; + friend class LauncherPlatformPerAppAppBrowserTest; + + // Creates a new app shortcut item and controller on the launcher at |index|. + // Use kInsertItemAtEnd to add a shortcut as the last item. + virtual ash::LauncherID CreateAppShortcutLauncherItemWithType( + const std::string& app_id, + int index, + ash::LauncherItemType launcher_item_type); + + // Updates the activation state of the Broswer item. + void UpdateBrowserItemStatus(); + + typedef std::map<ash::LauncherID, LauncherItemController*> + IDToItemControllerMap; + typedef std::list<content::WebContents*> WebContentsList; + typedef std::map<std::string, WebContentsList> AppIDToWebContentsListMap; + typedef std::map<content::WebContents*, std::string> WebContentsToAppIDMap; + + // Returns the profile used for new windows. + Profile* GetProfileForNewWindows(); + + // Invoked when the associated browser or app is closed. + void LauncherItemClosed(ash::LauncherID id); + + // Internal helpers for pinning and unpinning that handle both + // client-triggered and internal pinning operations. + void DoPinAppWithID(const std::string& app_id); + void DoUnpinAppsWithID(const std::string& app_id); + + // Re-syncs launcher model with prefs::kPinnedLauncherApps. + void UpdateAppLaunchersFromPref(); + + // Persists the shelf auto-hide behavior to prefs. + void SetShelfAutoHideBehaviorPrefs(ash::ShelfAutoHideBehavior behavior, + aura::RootWindow* root_window); + + // Sets the shelf auto-hide behavior from prefs. + void SetShelfAutoHideBehaviorFromPrefs(); + + // Sets the shelf alignment from prefs. + void SetShelfAlignmentFromPrefs(); + + // Sets both of auto-hide behavior and alignment from prefs. + void SetShelfBehaviorsFromPrefs(); + + // Returns the most recently active web contents for an app. + content::WebContents* GetLastActiveWebContents(const std::string& app_id); + + // Creates an app launcher to insert at |index|. Note that |index| may be + // adjusted by the model to meet ordering constraints. + // The |launcher_item_type| will be set into the LauncherModel. + ash::LauncherID InsertAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status, + int index, + ash::LauncherItemType launcher_item_type); + + bool HasItemController(ash::LauncherID id) const; + + // Enumerate all Web contents which match a given shortcut |controller|. + std::vector<content::WebContents*> GetV1ApplicationsFromController( + LauncherItemController* controller); + + // Create LauncherItem for Browser Shortcut. + ash::LauncherID CreateBrowserShortcutLauncherItem(); + + // Check if the given |web_contents| is in incognito mode. + bool IsIncognito(content::WebContents* web_contents) const; + + // Update browser shortcut's index. + void PersistChromeItemIndex(int index); + + // Get browser shortcut's index from pref. + int GetChromeIconIndexFromPref() const; + + // Close all windowed V1 applications of a certain extension which was already + // deleted. + void CloseWindowedAppsFromRemovedExtension(const std::string& app_id); + + // Move a launcher item ignoring the pinned state changes from |index| to + // |target_index|. + void MoveItemWithoutPinnedStateChangeNotification(int source_index, + int target_index); + + ash::LauncherModel* model_; + + // Profile used for prefs and loading extensions. This is NOT necessarily the + // profile new windows are created with. + Profile* profile_; + + IDToItemControllerMap id_to_item_controller_map_; + + // Maintains activation order of web contents for each app. + AppIDToWebContentsListMap app_id_to_web_contents_list_; + + // Direct access to app_id for a web contents. + WebContentsToAppIDMap web_contents_to_app_id_; + + // Used to track shell windows. + scoped_ptr<ShellWindowLauncherController> shell_window_controller_; + + // Used to get app info for tabs. + scoped_ptr<AppTabHelper> app_tab_helper_; + + // Used to load the image for an app item. + scoped_ptr<extensions::AppIconLoader> app_icon_loader_; + + content::NotificationRegistrar notification_registrar_; + + PrefChangeRegistrar pref_change_registrar_; + + AppSyncUIState* app_sync_ui_state_; + + scoped_ptr<ExtensionEnableFlow> extension_enable_flow_; + + // Launchers that are currently being observed. + std::set<ash::Launcher*> launchers_; + + // The owned browser shortcut item. + scoped_ptr<BrowserShortcutLauncherItemController> browser_item_controller_; + + // If true, incoming pinned state changes should be ignored. + bool ignore_persist_pinned_state_change_; + + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerApp); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_APP_H_ diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_browsertest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_browsertest.cc new file mode 100644 index 0000000..23b41f7 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_browsertest.cc @@ -0,0 +1,1616 @@ +// 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/ash/launcher/chrome_launcher_controller.h" + +#include "apps/native_app_window.h" +#include "apps/shell_window.h" +#include "apps/shell_window_registry.h" +#include "ash/ash_switches.h" +#include "ash/display/display_controller.h" +#include "ash/launcher/launcher.h" +#include "ash/launcher/launcher_model.h" +#include "ash/launcher/launcher_util.h" +#include "ash/launcher/launcher_view.h" +#include "ash/shell.h" +#include "ash/test/launcher_view_test_api.h" +#include "ash/test/shell_test_api.h" +#include "ash/wm/window_util.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/apps/app_browsertest_util.h" +#include "chrome/browser/automation/automation_util.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/extension_test_message_listener.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/app_list/app_list_service.h" +#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/extensions/application_launch.h" +#include "chrome/browser/ui/host_desktop.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/web_contents.h" +#include "extensions/common/switches.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/app_list/views/apps_grid_view.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/window.h" +#include "ui/base/events/event.h" + +using apps::ShellWindow; +using extensions::Extension; +using content::WebContents; + +namespace { + +class TestEvent : public ui::Event { + public: + explicit TestEvent(ui::EventType type) + : ui::Event(type, base::TimeDelta(), 0) { + } + virtual ~TestEvent() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestEvent); +}; + +class TestShellWindowRegistryObserver + : public apps::ShellWindowRegistry::Observer { + public: + explicit TestShellWindowRegistryObserver(Profile* profile) + : profile_(profile), + icon_updates_(0) { + apps::ShellWindowRegistry::Get(profile_)->AddObserver(this); + } + + virtual ~TestShellWindowRegistryObserver() { + apps::ShellWindowRegistry::Get(profile_)->RemoveObserver(this); + } + + // Overridden from ShellWindowRegistry::Observer: + virtual void OnShellWindowAdded(ShellWindow* shell_window) OVERRIDE {} + + virtual void OnShellWindowIconChanged(ShellWindow* shell_window) OVERRIDE { + ++icon_updates_; + } + + virtual void OnShellWindowRemoved(ShellWindow* shell_window) OVERRIDE {} + + int icon_updates() { return icon_updates_; } + + private: + Profile* profile_; + int icon_updates_; + + DISALLOW_COPY_AND_ASSIGN(TestShellWindowRegistryObserver); +}; + +} // namespace + +// TODO(skuhne): Change name back to LauncherPlatformAppBrowserTest when the +// old launcher gets ripped out. +class LauncherPlatformPerAppAppBrowserTest + : public extensions::PlatformAppBrowserTest { + protected: + LauncherPlatformPerAppAppBrowserTest() + : launcher_(NULL), + controller_(NULL) { + } + + virtual ~LauncherPlatformPerAppAppBrowserTest() {} + + virtual void RunTestOnMainThreadLoop() OVERRIDE { + launcher_ = ash::Launcher::ForPrimaryDisplay(); + controller_ = + static_cast<ChromeLauncherControllerPerApp*>(launcher_->delegate()); + return extensions::PlatformAppBrowserTest::RunTestOnMainThreadLoop(); + } + + ash::LauncherModel* launcher_model() { + return ash::test::ShellTestApi(ash::Shell::GetInstance()).launcher_model(); + } + + ash::LauncherID CreateAppShortcutLauncherItem(const std::string& name) { + return controller_->CreateAppShortcutLauncherItem( + name, controller_->model()->item_count()); + } + + const ash::LauncherItem& GetLastLauncherItem() { + // Unless there are any panels, the item at index [count - 1] will be + // the app list, and the item at [count - 2] will be the desired item. + return launcher_model()->items()[launcher_model()->item_count() - 2]; + } + + const ash::LauncherItem& GetLastLauncherPanelItem() { + // Panels show up on the right side of the launcher, so the desired item + // will be the last one. + return launcher_model()->items()[launcher_model()->item_count() - 1]; + } + + LauncherItemController* GetItemController(ash::LauncherID id) { + return controller_->id_to_item_controller_map_[id]; + } + + // Returns the number of menu items, ignoring separators. + int GetNumApplicationMenuItems(const ash::LauncherItem& item) { + const int event_flags = 0; + scoped_ptr<ash::LauncherMenuModel> menu( + controller_->CreateApplicationMenu(item, event_flags)); + int num_items = 0; + for (int i = 0; i < menu->GetItemCount(); ++i) { + if (menu->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) + ++num_items; + } + return num_items; + } + + // Activate the launcher item with the given |id|. + void ActivateLauncherItem(int id) { + launcher_->ActivateLauncherItem(id); + } + + ash::Launcher* launcher_; + ChromeLauncherControllerPerApp* controller_; + + private: + + DISALLOW_COPY_AND_ASSIGN(LauncherPlatformPerAppAppBrowserTest); +}; + +// TODO(skuhne): Change name back to LauncherAppBrowserTest when the +// old launcher gets ripped out. +class LauncherPerAppAppBrowserTest : public ExtensionBrowserTest { + protected: + LauncherPerAppAppBrowserTest() + : launcher_(NULL), + model_(NULL) { + } + + virtual ~LauncherPerAppAppBrowserTest() {} + + virtual void RunTestOnMainThreadLoop() OVERRIDE { + launcher_ = ash::Launcher::ForPrimaryDisplay(); + model_ = + ash::test::ShellTestApi(ash::Shell::GetInstance()).launcher_model(); + return ExtensionBrowserTest::RunTestOnMainThreadLoop(); + } + + size_t NumberOfDetectedLauncherBrowsers(bool show_all_tabs) { + ChromeLauncherControllerPerApp* controller = + static_cast<ChromeLauncherControllerPerApp*>(launcher_->delegate()); + LauncherItemController* item_controller = + controller->GetBrowserShortcutLauncherItemController(); + int items = item_controller->GetApplicationList( + show_all_tabs ? ui::EF_SHIFT_DOWN : 0).size(); + // If we have at least one item, we have also a title which we remove here. + return items ? (items - 1) : 0; + } + + const Extension* LoadAndLaunchExtension( + const char* name, + extension_misc::LaunchContainer container, + WindowOpenDisposition disposition) { + EXPECT_TRUE(LoadExtension(test_data_dir_.AppendASCII(name))); + + ExtensionService* service = extensions::ExtensionSystem::Get( + profile())->extension_service(); + const Extension* extension = + service->GetExtensionById(last_loaded_extension_id_, false); + EXPECT_TRUE(extension); + + chrome::OpenApplication(chrome::AppLaunchParams(profile(), + extension, + container, + disposition)); + return extension; + } + + ash::LauncherID CreateShortcut(const char* name) { + ExtensionService* service = extensions::ExtensionSystem::Get( + profile())->extension_service(); + LoadExtension(test_data_dir_.AppendASCII(name)); + + // First get app_id. + const Extension* extension = + service->GetExtensionById(last_loaded_extension_id_, false); + const std::string app_id = extension->id(); + + // Then create a shortcut. + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + int item_count = model_->item_count(); + ash::LauncherID shortcut_id = controller->CreateAppShortcutLauncherItem( + app_id, + item_count); + controller->PersistPinnedState(); + EXPECT_EQ(++item_count, model_->item_count()); + const ash::LauncherItem& item = *model_->ItemByID(shortcut_id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item.type); + return item.id; + } + + // Activate the launcher item with the given |id|. + void ActivateLauncherItem(int id) { + launcher_->ActivateLauncherItem(id); + } + + ash::Launcher* launcher_; + ash::LauncherModel* model_; + + private: + + DISALLOW_COPY_AND_ASSIGN(LauncherPerAppAppBrowserTest); +}; + +// TODO(skuhne): Change name to LauncherAppBrowserTestNoBrowser when the +// old launcher gets ripped out. +class LauncherPerAppAppBrowserTestNoDefaultBrowser + : public LauncherPerAppAppBrowserTest { + protected: + LauncherPerAppAppBrowserTestNoDefaultBrowser() {} + virtual ~LauncherPerAppAppBrowserTestNoDefaultBrowser() {} + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + LauncherPerAppAppBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(switches::kNoStartupWindow); + } + + private: + + DISALLOW_COPY_AND_ASSIGN(LauncherPerAppAppBrowserTestNoDefaultBrowser); +}; + +// Since the default for minimizing on click might change, I added both classes +// to either get the minimize on click or not. +class LauncherPerAppAppBrowserNoMinimizeOnClick + : public LauncherPlatformPerAppAppBrowserTest { + protected: + LauncherPerAppAppBrowserNoMinimizeOnClick() {} + virtual ~LauncherPerAppAppBrowserNoMinimizeOnClick() {} + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + LauncherPlatformPerAppAppBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch( + switches::kDisableMinimizeOnSecondLauncherItemClick); + } + + private: + + DISALLOW_COPY_AND_ASSIGN(LauncherPerAppAppBrowserNoMinimizeOnClick); +}; + +typedef LauncherPlatformPerAppAppBrowserTest + LauncherPerAppAppBrowserMinimizeOnClick; + +// Test that we can launch a platform app and get a running item. +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, LaunchUnpinned) { + int item_count = launcher_model()->item_count(); + const Extension* extension = LoadAndLaunchPlatformApp("launch"); + ShellWindow* window = CreateShellWindow(extension); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item = GetLastLauncherItem(); + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item.status); + CloseShellWindow(window); + --item_count; + EXPECT_EQ(item_count, launcher_model()->item_count()); +} + +// Test that we can launch a platform app that already has a shortcut. +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, LaunchPinned) { + int item_count = launcher_model()->item_count(); + + // First get app_id. + const Extension* extension = LoadAndLaunchPlatformApp("launch"); + const std::string app_id = extension->id(); + + // Then create a shortcut. + ash::LauncherID shortcut_id = CreateAppShortcutLauncherItem(app_id); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + ash::LauncherItem item = *launcher_model()->ItemByID(shortcut_id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item.type); + EXPECT_EQ(ash::STATUS_CLOSED, item.status); + + // Open a window. Confirm the item is now running. + ShellWindow* window = CreateShellWindow(extension); + ash::wm::ActivateWindow(window->GetNativeWindow()); + ASSERT_EQ(item_count, launcher_model()->item_count()); + item = *launcher_model()->ItemByID(shortcut_id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item.status); + + // Then close it, make sure there's still an item. + CloseShellWindow(window); + ASSERT_EQ(item_count, launcher_model()->item_count()); + item = *launcher_model()->ItemByID(shortcut_id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item.type); + EXPECT_EQ(ash::STATUS_CLOSED, item.status); +} + +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, PinRunning) { + // Run. + int item_count = launcher_model()->item_count(); + const Extension* extension = LoadAndLaunchPlatformApp("launch"); + ShellWindow* window = CreateShellWindow(extension); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item1 = GetLastLauncherItem(); + ash::LauncherID id = item1.id; + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + + // Create a shortcut. The app item should be after it. + ash::LauncherID foo_id = CreateAppShortcutLauncherItem("foo"); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + EXPECT_LT(launcher_model()->ItemIndexByID(foo_id), + launcher_model()->ItemIndexByID(id)); + + // Pin the app. The item should remain. + controller_->Pin(id); + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item2 = *launcher_model()->ItemByID(id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item2.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item2.status); + + // New shortcuts should come after the item. + ash::LauncherID bar_id = CreateAppShortcutLauncherItem("bar"); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + EXPECT_LT(launcher_model()->ItemIndexByID(id), + launcher_model()->ItemIndexByID(bar_id)); + + // Then close it, make sure the item remains. + CloseShellWindow(window); + ASSERT_EQ(item_count, launcher_model()->item_count()); +} + +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, UnpinRunning) { + int item_count = launcher_model()->item_count(); + + // First get app_id. + const Extension* extension = LoadAndLaunchPlatformApp("launch"); + const std::string app_id = extension->id(); + + // Then create a shortcut. + ash::LauncherID shortcut_id = CreateAppShortcutLauncherItem(app_id); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + ash::LauncherItem item = *launcher_model()->ItemByID(shortcut_id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item.type); + EXPECT_EQ(ash::STATUS_CLOSED, item.status); + + // Create a second shortcut. This will be needed to force the first one to + // move once it gets unpinned. + ash::LauncherID foo_id = CreateAppShortcutLauncherItem("foo"); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + EXPECT_LT(launcher_model()->ItemIndexByID(shortcut_id), + launcher_model()->ItemIndexByID(foo_id)); + + // Open a window. Confirm the item is now running. + ShellWindow* window = CreateShellWindow(extension); + ash::wm::ActivateWindow(window->GetNativeWindow()); + ASSERT_EQ(item_count, launcher_model()->item_count()); + item = *launcher_model()->ItemByID(shortcut_id); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, item.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item.status); + + // Unpin the app. The item should remain. + controller_->Unpin(shortcut_id); + ASSERT_EQ(item_count, launcher_model()->item_count()); + item = *launcher_model()->ItemByID(shortcut_id); + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item.status); + // The item should have moved after the other shortcuts. + EXPECT_GT(launcher_model()->ItemIndexByID(shortcut_id), + launcher_model()->ItemIndexByID(foo_id)); + + // Then close it, make sure the item's gone. + CloseShellWindow(window); + --item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); +} + +// Test that we can launch a platform app with more than one window. +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, MultipleWindows) { + int item_count = launcher_model()->item_count(); + + // First run app. + const Extension* extension = LoadAndLaunchPlatformApp("launch"); + ShellWindow* window1 = CreateShellWindow(extension); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item = GetLastLauncherItem(); + ash::LauncherID item_id = item.id; + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item.status); + EXPECT_EQ(2, GetNumApplicationMenuItems(item)); // Title + 1 window + + // Add second window. + ShellWindow* window2 = CreateShellWindow(extension); + // Confirm item stays. + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item2 = *launcher_model()->ItemByID(item_id); + EXPECT_EQ(ash::STATUS_ACTIVE, item2.status); + EXPECT_EQ(3, GetNumApplicationMenuItems(item2)); // Title + 2 windows + + // Close second window. + CloseShellWindow(window2); + // Confirm item stays. + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item3 = *launcher_model()->ItemByID(item_id); + EXPECT_EQ(ash::STATUS_ACTIVE, item3.status); + EXPECT_EQ(2, GetNumApplicationMenuItems(item3)); // Title + 1 window + + // Close first window. + CloseShellWindow(window1); + // Confirm item is removed. + --item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); +} + +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, MultipleApps) { + int item_count = launcher_model()->item_count(); + + // First run app. + const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); + ShellWindow* window1 = CreateShellWindow(extension1); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item1 = GetLastLauncherItem(); + ash::LauncherID item_id1 = item1.id; + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + + // Then run second app. + const Extension* extension2 = LoadAndLaunchPlatformApp("launch_2"); + ShellWindow* window2 = CreateShellWindow(extension2); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item2 = GetLastLauncherItem(); + ash::LauncherID item_id2 = item2.id; + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item2.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item2.status); + + EXPECT_NE(item_id1, item_id2); + EXPECT_EQ(ash::STATUS_RUNNING, + launcher_model()->ItemByID(item_id1)->status); + + // Close second app. + CloseShellWindow(window2); + --item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + // First app should be active again. + EXPECT_EQ(ash::STATUS_ACTIVE, + launcher_model()->ItemByID(item_id1)->status); + + // Close first app. + CloseShellWindow(window1); + --item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + +} + +// Confirm that app windows can be reactivated by clicking their icons and that +// the correct activation order is maintained. +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, WindowActivation) { + int item_count = launcher_model()->item_count(); + + // First run app. + const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); + ShellWindow* window1 = CreateShellWindow(extension1); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item1 = GetLastLauncherItem(); + ash::LauncherID item_id1 = item1.id; + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + + // Then run second app. + const Extension* extension2 = LoadAndLaunchPlatformApp("launch_2"); + ShellWindow* window2 = CreateShellWindow(extension2); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item2 = GetLastLauncherItem(); + ash::LauncherID item_id2 = item2.id; + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item2.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item2.status); + + EXPECT_NE(item_id1, item_id2); + EXPECT_EQ(ash::STATUS_RUNNING, + launcher_model()->ItemByID(item_id1)->status); + + // Activate first one. + ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + EXPECT_EQ(ash::STATUS_ACTIVE, launcher_model()->ItemByID(item_id1)->status); + EXPECT_EQ(ash::STATUS_RUNNING, + launcher_model()->ItemByID(item_id2)->status); + EXPECT_TRUE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); + EXPECT_FALSE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); + + // Activate second one. + ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id2)); + EXPECT_EQ(ash::STATUS_RUNNING, + launcher_model()->ItemByID(item_id1)->status); + EXPECT_EQ(ash::STATUS_ACTIVE, launcher_model()->ItemByID(item_id2)->status); + EXPECT_FALSE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); + EXPECT_TRUE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); + + // Add window for app1. This will activate it. + ShellWindow* window1b = CreateShellWindow(extension1); + ash::wm::ActivateWindow(window1b->GetNativeWindow()); + EXPECT_FALSE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); + EXPECT_FALSE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); + EXPECT_TRUE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); + + // Activate launcher item for app1, this will activate the first app window. + ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + EXPECT_TRUE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); + EXPECT_FALSE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); + ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + EXPECT_TRUE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); + + // Activate the second app again + ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id2)); + EXPECT_FALSE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); + EXPECT_TRUE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); + EXPECT_FALSE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); + + // Activate the first app again + ActivateLauncherItem(launcher_model()->ItemIndexByID(item_id1)); + EXPECT_TRUE(ash::wm::IsActiveWindow(window1b->GetNativeWindow())); + EXPECT_FALSE(ash::wm::IsActiveWindow(window2->GetNativeWindow())); + EXPECT_FALSE(ash::wm::IsActiveWindow(window1->GetNativeWindow())); + + // Close second app. + CloseShellWindow(window2); + --item_count; + EXPECT_EQ(item_count, launcher_model()->item_count()); + // First app should be active again. + EXPECT_EQ(ash::STATUS_ACTIVE, launcher_model()->ItemByID(item_id1)->status); + + // Close first app. + CloseShellWindow(window1b); + CloseShellWindow(window1); + --item_count; + EXPECT_EQ(item_count, launcher_model()->item_count()); +} + +// Confirm that Click behavior for app windows is correnct. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserNoMinimizeOnClick, + AppClickBehavior) { + // Launch a platform app and create a window for it. + const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); + ShellWindow* window1 = CreateShellWindow(extension1); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + // Confirm that a controller item was created and is the correct state. + const ash::LauncherItem& item1 = GetLastLauncherItem(); + LauncherItemController* item1_controller = GetItemController(item1.id); + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + EXPECT_EQ(LauncherItemController::TYPE_APP, item1_controller->type()); + // Clicking the item should have no effect. + TestEvent click_event(ui::ET_MOUSE_PRESSED); + item1_controller->Clicked(click_event); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + // Minimize the window and confirm that the controller item is updated. + window1->GetBaseWindow()->Minimize(); + EXPECT_FALSE(window1->GetNativeWindow()->IsVisible()); + EXPECT_FALSE(window1->GetBaseWindow()->IsActive()); + EXPECT_EQ(ash::STATUS_RUNNING, item1.status); + // Clicking the item should activate the window. + item1_controller->Clicked(click_event); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + // Maximizing a window should preserve state after minimize + click. + window1->GetBaseWindow()->Maximize(); + window1->GetBaseWindow()->Minimize(); + item1_controller->Clicked(click_event); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized()); +} + +// Confirm the minimizing click behavior for apps. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserMinimizeOnClick, + PackagedAppClickBehaviorInMinimizeMode) { + // Launch one platform app and create a window for it. + const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); + ShellWindow* window1 = CreateShellWindow(extension1); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + + // Confirm that a controller item was created and is the correct state. + const ash::LauncherItem& item1 = GetLastLauncherItem(); + LauncherItemController* item1_controller = GetItemController(item1.id); + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + EXPECT_EQ(LauncherItemController::TYPE_APP, item1_controller->type()); + // Since it is already active, clicking it should minimize. + TestEvent click_event(ui::ET_MOUSE_PRESSED); + item1_controller->Clicked(click_event); + EXPECT_FALSE(window1->GetNativeWindow()->IsVisible()); + EXPECT_FALSE(window1->GetBaseWindow()->IsActive()); + EXPECT_TRUE(window1->GetBaseWindow()->IsMinimized()); + EXPECT_EQ(ash::STATUS_RUNNING, item1.status); + // Clicking the item again should activate the window again. + item1_controller->Clicked(click_event); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + // Maximizing a window should preserve state after minimize + click. + window1->GetBaseWindow()->Maximize(); + window1->GetBaseWindow()->Minimize(); + item1_controller->Clicked(click_event); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + EXPECT_TRUE(window1->GetBaseWindow()->IsMaximized()); + window1->GetBaseWindow()->Restore(); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + EXPECT_FALSE(window1->GetBaseWindow()->IsMaximized()); + + // Creating a second window of the same type should change the behavior so + // that a click does not change the activation state. + ShellWindow* window1a = CreateShellWindow(extension1); + EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1a->GetBaseWindow()->IsActive()); + // The first click does nothing. + item1_controller->Clicked(click_event); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + EXPECT_FALSE(window1a->GetBaseWindow()->IsActive()); + // The second neither. + item1_controller->Clicked(click_event); + EXPECT_TRUE(window1->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1a->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(window1->GetBaseWindow()->IsActive()); + EXPECT_FALSE(window1a->GetBaseWindow()->IsActive()); +} + +// Confirm that click behavior for app panels is correct. +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, + AppPanelClickBehavior) { + // Enable experimental APIs to allow panel creation. + CommandLine::ForCurrentProcess()->AppendSwitch( + extensions::switches::kEnableExperimentalExtensionApis); + // Launch a platform app and create a panel window for it. + const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); + ShellWindow::CreateParams params; + params.window_type = ShellWindow::WINDOW_TYPE_PANEL; + params.focused = false; + ShellWindow* panel = CreateShellWindowFromParams(extension1, params); + EXPECT_TRUE(panel->GetNativeWindow()->IsVisible()); + // Panels should not be active by default. + EXPECT_FALSE(panel->GetBaseWindow()->IsActive()); + // Confirm that a controller item was created and is the correct state. + const ash::LauncherItem& item1 = GetLastLauncherPanelItem(); + LauncherItemController* item1_controller = GetItemController(item1.id); + EXPECT_EQ(ash::TYPE_APP_PANEL, item1.type); + EXPECT_EQ(ash::STATUS_RUNNING, item1.status); + EXPECT_EQ(LauncherItemController::TYPE_APP_PANEL, item1_controller->type()); + // Click the item and confirm that the panel is activated. + TestEvent click_event(ui::ET_MOUSE_PRESSED); + item1_controller->Clicked(click_event); + EXPECT_TRUE(panel->GetBaseWindow()->IsActive()); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + // Click the item again and confirm that the panel is minimized. + item1_controller->Clicked(click_event); + EXPECT_TRUE(panel->GetBaseWindow()->IsMinimized()); + EXPECT_EQ(ash::STATUS_RUNNING, item1.status); + // Click the item again and confirm that the panel is activated. + item1_controller->Clicked(click_event); + EXPECT_TRUE(panel->GetNativeWindow()->IsVisible()); + EXPECT_TRUE(panel->GetBaseWindow()->IsActive()); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); +} + +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, + BrowserActivation) { + int item_count = launcher_model()->item_count(); + + // First run app. + const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); + CreateShellWindow(extension1); + ++item_count; + ASSERT_EQ(item_count, launcher_model()->item_count()); + const ash::LauncherItem& item1 = GetLastLauncherItem(); + ash::LauncherID item_id1 = item1.id; + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + + ash::wm::ActivateWindow(browser()->window()->GetNativeWindow()); + EXPECT_EQ(ash::STATUS_RUNNING, + launcher_model()->ItemByID(item_id1)->status); +} + +// Test that opening an app sets the correct icon +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, SetIcon) { + TestShellWindowRegistryObserver test_observer(browser()->profile()); + + // Enable experimental APIs to allow panel creation. + CommandLine::ForCurrentProcess()->AppendSwitch( + extensions::switches::kEnableExperimentalExtensionApis); + + int base_launcher_item_count = launcher_model()->item_count(); + ExtensionTestMessageListener launched_listener("Launched", false); + ExtensionTestMessageListener completed_listener("Completed", false); + LoadAndLaunchPlatformApp("app_icon"); + ASSERT_TRUE(launched_listener.WaitUntilSatisfied()); + ASSERT_TRUE(completed_listener.WaitUntilSatisfied()); + + // Now wait until the WebContent has decoded the icons and chrome has + // processed it. This needs to be in a loop since the renderer runs in a + // different process. + while (test_observer.icon_updates() < 3) { + base::RunLoop run_loop; + run_loop.RunUntilIdle(); + } + + // This test creates one shell window and one panel window. + int launcher_item_count = launcher_model()->item_count(); + ASSERT_EQ(base_launcher_item_count + 2, launcher_item_count); + // The Panel will be the last item, the app list second-to-last, the app + // third from last. + const ash::LauncherItem& app_item = + launcher_model()->items()[launcher_item_count - 3]; + const ash::LauncherItem& panel_item = + launcher_model()->items()[launcher_item_count - 1]; + const LauncherItemController* app_item_controller = + GetItemController(app_item.id); + const LauncherItemController* panel_item_controller = + GetItemController(panel_item.id); + // Icons for Apps are set by the ShellWindowLauncherController, so + // image_set_by_controller() should be set. + EXPECT_TRUE(app_item_controller->image_set_by_controller()); + EXPECT_TRUE(panel_item_controller->image_set_by_controller()); + // Ensure icon heights are correct (see test.js in app_icon/ test directory) + EXPECT_EQ(48, app_item.image.height()); + EXPECT_EQ(64, panel_item.image.height()); +} + +// Test that we can launch an app with a shortcut. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, LaunchPinned) { + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + ash::LauncherID shortcut_id = CreateShortcut("app1"); + EXPECT_EQ(ash::STATUS_CLOSED, (*model_->ItemByID(shortcut_id)).status); + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(++tab_count, tab_strip->count()); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); + WebContents* tab = tab_strip->GetActiveWebContents(); + content::WindowedNotificationObserver close_observer( + content::NOTIFICATION_WEB_CONTENTS_DESTROYED, + content::Source<WebContents>(tab)); + browser()->tab_strip_model()->CloseSelectedTabs(); + close_observer.Wait(); + EXPECT_EQ(--tab_count, tab_strip->count()); + EXPECT_EQ(ash::STATUS_CLOSED, (*model_->ItemByID(shortcut_id)).status); +} + +// Launch the app first and then create the shortcut. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, LaunchUnpinned) { + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, + NEW_FOREGROUND_TAB); + EXPECT_EQ(++tab_count, tab_strip->count()); + ash::LauncherID shortcut_id = CreateShortcut("app1"); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); + WebContents* tab = tab_strip->GetActiveWebContents(); + content::WindowedNotificationObserver close_observer( + content::NOTIFICATION_WEB_CONTENTS_DESTROYED, + content::Source<WebContents>(tab)); + browser()->tab_strip_model()->CloseSelectedTabs(); + close_observer.Wait(); + EXPECT_EQ(--tab_count, tab_strip->count()); + EXPECT_EQ(ash::STATUS_CLOSED, (*model_->ItemByID(shortcut_id)).status); +} + +// Launches an app in the background and then tries to open it. This is test for +// a crash we had. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, LaunchInBackground) { + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, + NEW_BACKGROUND_TAB); + EXPECT_EQ(++tab_count, tab_strip->count()); + ChromeLauncherController::instance()->LaunchApp(last_loaded_extension_id_, 0); +} + +// Confirm that clicking a icon for an app running in one of 2 maxmized windows +// activates the right window. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, LaunchMaximized) { + aura::Window* window1 = browser()->window()->GetNativeWindow(); + ash::wm::MaximizeWindow(window1); + content::WindowedNotificationObserver open_observer( + chrome::NOTIFICATION_BROWSER_WINDOW_READY, + content::NotificationService::AllSources()); + chrome::NewEmptyWindow(browser()->profile(), chrome::HOST_DESKTOP_TYPE_ASH); + open_observer.Wait(); + Browser* browser2 = content::Source<Browser>(open_observer.source()).ptr(); + aura::Window* window2 = browser2->window()->GetNativeWindow(); + TabStripModel* tab_strip = browser2->tab_strip_model(); + int tab_count = tab_strip->count(); + ash::wm::MaximizeWindow(window2); + + ash::LauncherID shortcut_id = CreateShortcut("app1"); + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(++tab_count, tab_strip->count()); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); + + window1->Show(); + ash::wm::ActivateWindow(window1); + EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut_id)).status); + + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); +} + +// Activating the same app multiple times should launch only a single copy. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, ActivateApp) { + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + const Extension* extension = + LoadExtension(test_data_dir_.AppendASCII("app1")); + + ChromeLauncherController::instance()->ActivateApp(extension->id(), 0); + EXPECT_EQ(++tab_count, tab_strip->count()); + ChromeLauncherController::instance()->ActivateApp(extension->id(), 0); + EXPECT_EQ(tab_count, tab_strip->count()); +} + +// Launching the same app multiple times should launch a copy for each call. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, LaunchApp) { + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + const Extension* extension = + LoadExtension(test_data_dir_.AppendASCII("app1")); + + ChromeLauncherController::instance()->LaunchApp(extension->id(), 0); + EXPECT_EQ(++tab_count, tab_strip->count()); + ChromeLauncherController::instance()->LaunchApp(extension->id(), 0); + EXPECT_EQ(++tab_count, tab_strip->count()); +} + +// Launch 2 apps and toggle which is active. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, MultipleApps) { + int item_count = model_->item_count(); + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + ash::LauncherID shortcut1 = CreateShortcut("app1"); + EXPECT_EQ(++item_count, model_->item_count()); + ash::LauncherID shortcut2 = CreateShortcut("app2"); + EXPECT_EQ(++item_count, model_->item_count()); + + // Launch first app. + ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); + EXPECT_EQ(++tab_count, tab_strip->count()); + WebContents* tab1 = tab_strip->GetActiveWebContents(); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut1)).status); + + // Launch second app. + ActivateLauncherItem(model_->ItemIndexByID(shortcut2)); + EXPECT_EQ(++tab_count, tab_strip->count()); + WebContents* tab2 = tab_strip->GetActiveWebContents(); + ASSERT_NE(tab1, tab2); + EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut1)).status); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut2)).status); + + // Reactivate first app. + ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); + EXPECT_EQ(tab_count, tab_strip->count()); + EXPECT_EQ(tab_strip->GetActiveWebContents(), tab1); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut1)).status); + EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut2)).status); + + // Open second tab for second app. This should activate it. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example.com/path3/foo.html"), + NEW_FOREGROUND_TAB, + 0); + EXPECT_EQ(++tab_count, tab_strip->count()); + EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut1)).status); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut2)).status); + + // Reactivate first app. + ActivateLauncherItem(model_->ItemIndexByID(shortcut1)); + EXPECT_EQ(tab_count, tab_strip->count()); + EXPECT_EQ(tab_strip->GetActiveWebContents(), tab1); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut1)).status); + EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut2)).status); + + // And second again. This time the second tab should become active. + ActivateLauncherItem(model_->ItemIndexByID(shortcut2)); + EXPECT_EQ(tab_count, tab_strip->count()); + EXPECT_EQ(tab_strip->GetActiveWebContents(), tab2); + EXPECT_EQ(ash::STATUS_RUNNING, (*model_->ItemByID(shortcut1)).status); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut2)).status); +} + +// Confirm that a page can be navigated from and to while maintaining the +// correct running state. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, Navigation) { + ash::LauncherID shortcut_id = CreateShortcut("app1"); + EXPECT_EQ(ash::STATUS_CLOSED, (*model_->ItemByID(shortcut_id)).status); + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); + + // Navigate away. + ui_test_utils::NavigateToURL( + browser(), GURL("http://www.example.com/path0/bar.html")); + EXPECT_EQ(ash::STATUS_CLOSED, (*model_->ItemByID(shortcut_id)).status); + + // Navigate back. + ui_test_utils::NavigateToURL( + browser(), GURL("http://www.example.com/path1/foo.html")); + EXPECT_EQ(ash::STATUS_ACTIVE, (*model_->ItemByID(shortcut_id)).status); +} + +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, MultipleOwnedTabs) { + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + ash::LauncherID shortcut_id = CreateShortcut("app1"); + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(++tab_count, tab_strip->count()); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + WebContents* first_tab = tab_strip->GetActiveWebContents(); + + // Create new tab owned by app. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example.com/path2/bar.html"), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + EXPECT_EQ(++tab_count, tab_strip->count()); + // Confirm app is still active. + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + + // Create new tab not owned by app. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example.com/path3/foo.html"), + NEW_FOREGROUND_TAB, + 0); + EXPECT_EQ(++tab_count, tab_strip->count()); + // No longer active. + EXPECT_EQ(ash::STATUS_RUNNING, model_->ItemByID(shortcut_id)->status); + + // Activating app makes first tab active again. + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + EXPECT_EQ(tab_strip->GetActiveWebContents(), first_tab); +} + +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, RefocusFilter) { + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + ash::LauncherID shortcut_id = CreateShortcut("app1"); + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(++tab_count, tab_strip->count()); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + WebContents* first_tab = tab_strip->GetActiveWebContents(); + + controller->SetRefocusURLPatternForTest( + shortcut_id, GURL("http://www.example.com/path1/*")); + // Create new tab owned by app. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example.com/path2/bar.html"), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + EXPECT_EQ(++tab_count, tab_strip->count()); + // Confirm app is still active. + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + + // Create new tab not owned by app. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example.com/path3/foo.html"), + NEW_FOREGROUND_TAB, + 0); + EXPECT_EQ(++tab_count, tab_strip->count()); + // No longer active. + EXPECT_EQ(ash::STATUS_RUNNING, model_->ItemByID(shortcut_id)->status); + + // Activating app makes first tab active again, because second tab isn't + // in its refocus url path. + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + EXPECT_EQ(tab_strip->GetActiveWebContents(), first_tab); +} + +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, RefocusFilterLaunch) { + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + ash::LauncherID shortcut_id = CreateShortcut("app1"); + controller->SetRefocusURLPatternForTest( + shortcut_id, GURL("http://www.example.com/path1/*")); + + // Create new tab. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example2.com/path2/bar.html"), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + EXPECT_EQ(++tab_count, tab_strip->count()); + WebContents* first_tab = tab_strip->GetActiveWebContents(); + // Confirm app is not active. + EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); + + // Activating app should launch new tab, because second tab isn't + // in its refocus url path. + ActivateLauncherItem(model_->ItemIndexByID(shortcut_id)); + EXPECT_EQ(++tab_count, tab_strip->count()); + WebContents* second_tab = tab_strip->GetActiveWebContents(); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + EXPECT_NE(first_tab, second_tab); + EXPECT_EQ(tab_strip->GetActiveWebContents(), second_tab); +} + +// Check the launcher activation state for applications and browser. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, ActivationStateCheck) { + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + TabStripModel* tab_strip = browser()->tab_strip_model(); + // Get the browser item index + int browser_index = ash::launcher::GetBrowserItemIndex(*controller->model()); + EXPECT_TRUE(browser_index >= 0); + + // Even though we are just comming up, the browser should be active. + EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); + + ash::LauncherID shortcut_id = CreateShortcut("app1"); + controller->SetRefocusURLPatternForTest( + shortcut_id, GURL("http://www.example.com/path1/*")); + + EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); + + // Create new tab which would be the running app. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example.com/path1/bar.html"), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + // There should never be two items active at the same time. + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[browser_index].status); + + tab_strip->ActivateTabAt(0, false); + EXPECT_EQ(ash::STATUS_RUNNING, model_->ItemByID(shortcut_id)->status); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); + + tab_strip->CloseWebContentsAt(1, TabStripModel::CLOSE_NONE); + EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); + EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[browser_index].status); + + ash::wm::DeactivateWindow(browser()->window()->GetNativeWindow()); + EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); + EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[browser_index].status); +} + +// Check that the launcher activation state for a V1 application stays closed +// even after an asynchronous browser event comes in after the tab got +// destroyed. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, + AsyncActivationStateCheck) { + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + TabStripModel* tab_strip = browser()->tab_strip_model(); + + ash::LauncherID shortcut_id = CreateShortcut("app1"); + controller->SetRefocusURLPatternForTest( + shortcut_id, GURL("http://www.example.com/path1/*")); + + EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); + + // Create new tab which would be the running app. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.example.com/path1/bar.html"), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + EXPECT_EQ(ash::STATUS_ACTIVE, model_->ItemByID(shortcut_id)->status); + // To address the issue of crbug.com/174050, the tab we are about to close + // has to be active. + tab_strip->ActivateTabAt(1, false); + EXPECT_EQ(1, tab_strip->active_index()); + + // Close the web contents. + tab_strip->CloseWebContentsAt(1, TabStripModel::CLOSE_NONE); + // The status should now be set to closed. + EXPECT_EQ(ash::STATUS_CLOSED, model_->ItemByID(shortcut_id)->status); +} + +// Checks that a windowed application does not add an item to the browser list. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTestNoDefaultBrowser, + WindowedAppDoesNotAddToBrowser) { + // Get the number of items in the browser menu. + size_t items = NumberOfDetectedLauncherBrowsers(false); + size_t running_browser = chrome::GetTotalBrowserCount(); + EXPECT_EQ(0u, items); + EXPECT_EQ(0u, running_browser); + + LoadAndLaunchExtension("app1", extension_misc::LAUNCH_WINDOW, NEW_WINDOW); + + // No new browser should get detected, even though one more is running. + EXPECT_EQ(0u, NumberOfDetectedLauncherBrowsers(false)); + EXPECT_EQ(++running_browser, chrome::GetTotalBrowserCount()); + + LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); + + // A new browser should get detected and one more should be running. + EXPECT_EQ(NumberOfDetectedLauncherBrowsers(false), 1u); + EXPECT_EQ(++running_browser, chrome::GetTotalBrowserCount()); +} + +// Checks the functionality to enumerate all browsers vs. all tabs. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTestNoDefaultBrowser, + EnumerateALlBrowsersAndTabs) { + // Create at least one browser. + LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); + size_t browsers = NumberOfDetectedLauncherBrowsers(false); + size_t tabs = NumberOfDetectedLauncherBrowsers(true); + + // Create a second browser. + LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); + + EXPECT_EQ(++browsers, NumberOfDetectedLauncherBrowsers(false)); + EXPECT_EQ(++tabs, NumberOfDetectedLauncherBrowsers(true)); + + // Create only a tab. + LoadAndLaunchExtension("app1", + extension_misc::LAUNCH_TAB, + NEW_FOREGROUND_TAB); + + EXPECT_EQ(browsers, NumberOfDetectedLauncherBrowsers(false)); + EXPECT_EQ(++tabs, NumberOfDetectedLauncherBrowsers(true)); +} + +// Check that the keyboard activation of a launcher item tabs properly through +// the items at hand. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, AltNumberTabsTabbing) { + TabStripModel* tab_strip = browser()->tab_strip_model(); + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + + ash::LauncherID shortcut_id = CreateShortcut("app"); + controller->SetRefocusURLPatternForTest( + shortcut_id, GURL("http://www.example.com/path/*")); + std::string url = "http://www.example.com/path/bla"; + + int shortcut_index = model_->ItemIndexByID(shortcut_id); + + // Create an application handled browser tab. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL(url), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + content::WebContents* content1 = tab_strip->GetActiveWebContents(); + + // Create some other browser tab. + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL("http://www.test.com"), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + content::WebContents* content1a = tab_strip->GetActiveWebContents(); + + // Make sure that the active tab is now our handled tab. + EXPECT_NE(content1a, content1); + + // The active tab should still be the unnamed tab. Then we switch and reach + // the first app and stay there. + EXPECT_EQ(content1a, tab_strip->GetActiveWebContents()); + ActivateLauncherItem(shortcut_index); + EXPECT_EQ(content1, tab_strip->GetActiveWebContents()); + ActivateLauncherItem(shortcut_index); + EXPECT_EQ(content1, tab_strip->GetActiveWebContents()); + + ui_test_utils::NavigateToURLWithDisposition( + browser(), + GURL(url), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + content::WebContents* content2 = tab_strip->GetActiveWebContents(); + + EXPECT_EQ(content2, browser()->tab_strip_model()->GetActiveWebContents()); + ActivateLauncherItem(shortcut_index); + EXPECT_EQ(content1, browser()->tab_strip_model()->GetActiveWebContents()); + ActivateLauncherItem(shortcut_index); + EXPECT_EQ(content2, browser()->tab_strip_model()->GetActiveWebContents()); +} + +// Check that the keyboard activation of a launcher item tabs properly through +// the items at hand. +IN_PROC_BROWSER_TEST_F(LauncherPlatformPerAppAppBrowserTest, + AltNumberAppsTabbing) { + // First run app. + const Extension* extension1 = LoadAndLaunchPlatformApp("launch"); + ui::BaseWindow* window1 = CreateShellWindow(extension1)->GetBaseWindow(); + const ash::LauncherItem& item1 = GetLastLauncherItem(); + ash::LauncherID app_id = item1.id; + int app_index = launcher_model()->ItemIndexByID(app_id); + + EXPECT_EQ(ash::TYPE_PLATFORM_APP, item1.type); + EXPECT_EQ(ash::STATUS_ACTIVE, item1.status); + + const Extension* extension2 = LoadAndLaunchPlatformApp("launch_2"); + ui::BaseWindow* window2 = CreateShellWindow(extension2)->GetBaseWindow(); + + // By now the browser should be active. Issue Alt keystrokes several times to + // see that we stay on that application. + EXPECT_TRUE(window2->IsActive()); + ActivateLauncherItem(app_index); + EXPECT_TRUE(window1->IsActive()); + ActivateLauncherItem(app_index); + EXPECT_TRUE(window1->IsActive()); + + ui::BaseWindow* window1a = CreateShellWindow(extension1)->GetBaseWindow(); + + EXPECT_TRUE(window1a->IsActive()); + EXPECT_FALSE(window1->IsActive()); + ActivateLauncherItem(app_index); + EXPECT_TRUE(window1->IsActive()); + ActivateLauncherItem(app_index); + EXPECT_TRUE(window1a->IsActive()); +} + +// Checks that the browser Alt "tabbing" is properly done. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTestNoDefaultBrowser, + AltNumberBrowserTabbing) { + // Get the number of items in the browser menu. + EXPECT_EQ(0u, chrome::GetTotalBrowserCount()); + // The first activation should create a browser. + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(1u, chrome::GetTotalBrowserCount()); + // A second activation should not create a new instance. + launcher_->ActivateLauncherItem(0); + Browser* browser1 = chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow()); + EXPECT_TRUE(browser1); + aura::Window* window1 = browser1->window()->GetNativeWindow(); + Browser* browser2 = CreateBrowser(profile()); + aura::Window* window2 = browser2->window()->GetNativeWindow(); + + EXPECT_EQ(2u, chrome::GetTotalBrowserCount()); + EXPECT_NE(window1, window2); + EXPECT_EQ(window2, ash::wm::GetActiveWindow()); + + // Activate multiple times the switcher to see that the windows get activated. + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(window1, ash::wm::GetActiveWindow()); + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(window2, ash::wm::GetActiveWindow()); + + // Create a third browser - make sure that we do not toggle simply between + // two windows. + Browser* browser3 = CreateBrowser(profile()); + aura::Window* window3 = browser3->window()->GetNativeWindow(); + + EXPECT_EQ(3u, chrome::GetTotalBrowserCount()); + EXPECT_NE(window1, window3); + EXPECT_NE(window2, window3); + EXPECT_EQ(window3, ash::wm::GetActiveWindow()); + + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(window1, ash::wm::GetActiveWindow()); + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(window2, ash::wm::GetActiveWindow()); + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(window3, ash::wm::GetActiveWindow()); + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(window1, ash::wm::GetActiveWindow()); + + // Create anther app and make sure that none of our browsers is active. + LoadAndLaunchExtension("app1", extension_misc::LAUNCH_TAB, NEW_WINDOW); + EXPECT_NE(window1, ash::wm::GetActiveWindow()); + EXPECT_NE(window2, ash::wm::GetActiveWindow()); + + // After activation our browser should be active again. + launcher_->ActivateLauncherItem(0); + EXPECT_EQ(window1, ash::wm::GetActiveWindow()); +} + +// Checks that after a session restore, we do not start applications on an +// activation. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, + ActivateAfterSessionRestore) { + EXPECT_EQ(1u, chrome::GetTotalBrowserCount()); + + // Create a known application. + ChromeLauncherController* controller = + static_cast<ChromeLauncherController*>(launcher_->delegate()); + ash::LauncherID shortcut_id = CreateShortcut("app1"); + + // Create a new browser - without activating it - and load an "app" into it. + Browser::CreateParams params = + Browser::CreateParams(profile(), chrome::GetActiveDesktop()); + params.initial_show_state = ui::SHOW_STATE_INACTIVE; + Browser* browser2 = new Browser(params); + controller->SetRefocusURLPatternForTest( + shortcut_id, GURL("http://www.example.com/path/*")); + std::string url = "http://www.example.com/path/bla"; + ui_test_utils::NavigateToURLWithDisposition( + browser2, + GURL(url), + NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + // Remember the number of tabs for each browser. + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count1 = tab_strip->count(); + TabStripModel* tab_strip2 = browser2->tab_strip_model(); + int tab_count2 = tab_strip2->count(); + + // Check that we have two browsers and the inactive browser remained inactive. + EXPECT_EQ(2u, chrome::GetTotalBrowserCount()); + EXPECT_EQ(chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow()), + browser()); + // Check that the LRU browser list does only contain the original browser. + BrowserList* ash_browser_list = + BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH); + BrowserList::const_reverse_iterator it = + ash_browser_list->begin_last_active(); + EXPECT_EQ(*it, browser()); + ++it; + EXPECT_EQ(it, ash_browser_list->end_last_active()); + + // Now request to either activate an existing app or create a new one. + controller->ItemSelected(*model_->ItemByID(shortcut_id), + ui::KeyEvent(ui::ET_KEY_RELEASED, + ui::VKEY_RETURN, + 0, + false)); + + // Check that we have set focus on the existing application and nothing new + // was created. + EXPECT_EQ(2u, chrome::GetTotalBrowserCount()); + EXPECT_EQ(tab_count1, tab_strip->count()); + EXPECT_EQ(tab_count2, tab_strip2->count()); + EXPECT_EQ(chrome::FindBrowserWithWindow(ash::wm::GetActiveWindow()), + browser2); +} + +// Do various drag and drop interaction tests between the application list and +// the launcher. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, DragAndDrop) { + // Get a number of interfaces we need. + aura::test::EventGenerator generator( + ash::Shell::GetPrimaryRootWindow(), gfx::Point()); + ash::test::LauncherViewTestAPI test(launcher_->GetLauncherViewForTest()); + AppListService* service = AppListService::Get(); + + // There should be two items in our launcher by this time. + EXPECT_EQ(2, model_->item_count()); + EXPECT_FALSE(service->IsAppListVisible()); + + // Open the app list menu and check that the drag and drop host was set. + gfx::Rect app_list_bounds = + test.launcher_view()->GetAppListButtonView()->GetBoundsInScreen(); + generator.MoveMouseTo(app_list_bounds.CenterPoint().x(), + app_list_bounds.CenterPoint().y()); + base::MessageLoop::current()->RunUntilIdle(); + generator.ClickLeftButton(); + + EXPECT_TRUE(service->IsAppListVisible()); + app_list::AppsGridView* grid_view = + app_list::AppsGridView::GetLastGridViewForTest(); + ASSERT_TRUE(grid_view); + ASSERT_TRUE(grid_view->has_drag_and_drop_host_for_test()); + + // There should be 2 items in our application list. + const views::ViewModel* vm_grid = grid_view->view_model_for_test(); + EXPECT_EQ(2, vm_grid->view_size()); + + // Test #1: Drag an app list which does not exist yet item into the + // launcher. Keeping it dragged, see that a new item gets created. Continuing + // to drag it out should remove it again. + + // Get over item #1 of the application list and press the mouse button. + views::View* item1 = vm_grid->view_at(1); + gfx::Rect bounds_grid_1 = item1->GetBoundsInScreen(); + generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), + bounds_grid_1.CenterPoint().y()); + base::MessageLoop::current()->RunUntilIdle(); + generator.PressLeftButton(); + + EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); + + // Drag the item into the launcher and check that a new item gets created. + const views::ViewModel* vm_launcher = + test.launcher_view()->view_model_for_test(); + views::View* launcher1 = vm_launcher->view_at(1); + gfx::Rect bounds_launcher_1 = launcher1->GetBoundsInScreen(); + generator.MoveMouseTo(bounds_launcher_1.CenterPoint().x(), + bounds_launcher_1.CenterPoint().y()); + base::MessageLoop::current()->RunUntilIdle(); + + // Check that a new item got created. + EXPECT_EQ(3, model_->item_count()); + EXPECT_TRUE(grid_view->forward_events_to_drag_and_drop_host_for_test()); + + // Move it where the item originally was and check that it disappears again. + generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), + bounds_grid_1.CenterPoint().y()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(2, model_->item_count()); + EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); + + // Dropping it should keep the launcher as it originally was. + generator.ReleaseLeftButton(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(2, model_->item_count()); + // There are a few animations which need finishing before we can continue. + test.RunMessageLoopUntilAnimationsDone(); + // Move the mouse outside of the launcher. + generator.MoveMouseTo(0, 0); + + // Test #2: Check that the unknown item dropped into the launcher will + // create a new item. + generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), + bounds_grid_1.CenterPoint().y()); + generator.PressLeftButton(); + generator.MoveMouseTo(bounds_launcher_1.CenterPoint().x(), + bounds_launcher_1.CenterPoint().y()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(3, model_->item_count()); + EXPECT_TRUE(grid_view->forward_events_to_drag_and_drop_host_for_test()); + generator.ReleaseLeftButton(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); + EXPECT_EQ(3, model_->item_count()); // It should be still there. + test.RunMessageLoopUntilAnimationsDone(); + + // Test #3: Check that the now known item dropped into the launcher will + // not create a new item. + generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), + bounds_grid_1.CenterPoint().y()); + generator.PressLeftButton(); + generator.MoveMouseTo(bounds_launcher_1.CenterPoint().x(), + bounds_launcher_1.CenterPoint().y()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(3, model_->item_count()); // No new item got added. + EXPECT_TRUE(grid_view->forward_events_to_drag_and_drop_host_for_test()); + generator.ReleaseLeftButton(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_FALSE(grid_view->forward_events_to_drag_and_drop_host_for_test()); + EXPECT_EQ(3, model_->item_count()); // And it remains that way. +} + +// Check that clicking on an app launcher item launches a new browser. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, ClickItem) { + // Get a number of interfaces we need. + aura::test::EventGenerator generator( + ash::Shell::GetPrimaryRootWindow(), gfx::Point()); + ash::test::LauncherViewTestAPI test(launcher_->GetLauncherViewForTest()); + AppListService* service = AppListService::Get(); + // There should be two items in our launcher by this time. + EXPECT_EQ(2, model_->item_count()); + EXPECT_FALSE(service->IsAppListVisible()); + + // Open the app list menu and check that the drag and drop host was set. + gfx::Rect app_list_bounds = + test.launcher_view()->GetAppListButtonView()->GetBoundsInScreen(); + generator.MoveMouseTo(app_list_bounds.CenterPoint().x(), + app_list_bounds.CenterPoint().y()); + generator.ClickLeftButton(); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_TRUE(service->IsAppListVisible()); + app_list::AppsGridView* grid_view = + app_list::AppsGridView::GetLastGridViewForTest(); + ASSERT_TRUE(grid_view); + const views::ViewModel* vm_grid = grid_view->view_model_for_test(); + EXPECT_EQ(2, vm_grid->view_size()); + gfx::Rect bounds_grid_1 = vm_grid->view_at(1)->GetBoundsInScreen(); + // Test now that a click does create a new application tab. + TabStripModel* tab_strip = browser()->tab_strip_model(); + int tab_count = tab_strip->count(); + generator.MoveMouseTo(bounds_grid_1.CenterPoint().x(), + bounds_grid_1.CenterPoint().y()); + generator.ClickLeftButton(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(tab_count + 1, tab_strip->count()); +} + +// Check LauncherItemController of Browser Shortcut functionality. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTestNoDefaultBrowser, + BrowserShortcutLauncherItemController) { + ChromeLauncherControllerPerApp* controller = + static_cast<ChromeLauncherControllerPerApp*>(launcher_->delegate()); + LauncherItemController* item_controller = + controller->GetBrowserShortcutLauncherItemController(); + + // Get the number of browsers. + size_t running_browser = chrome::GetTotalBrowserCount(); + EXPECT_EQ(0u, running_browser); + EXPECT_FALSE(item_controller->IsOpen()); + + // Activate. This creates new browser + item_controller->Activate(); + // New Window is created. + running_browser = chrome::GetTotalBrowserCount(); + EXPECT_EQ(1u, running_browser); + EXPECT_TRUE(item_controller->IsOpen()); + + // Minimize Window. + aura::Window* window = ash::wm::GetActiveWindow(); + ash::wm::MinimizeWindow(window); + EXPECT_TRUE(ash::wm::IsWindowMinimized(window)); + + // Activate again. This doesn't create new browser. + // It activates window. + item_controller->Activate(); + running_browser = chrome::GetTotalBrowserCount(); + EXPECT_EQ(1u, running_browser); + EXPECT_TRUE(item_controller->IsOpen()); + EXPECT_FALSE(ash::wm::IsWindowMinimized(window)); +} + +// Check that GetIDByWindow() returns |LauncherID| of the active tab. +IN_PROC_BROWSER_TEST_F(LauncherPerAppAppBrowserTest, + MatchingLauncherIDandActiveTab) { + ChromeLauncherControllerPerApp* controller = + static_cast<ChromeLauncherControllerPerApp*>(launcher_->delegate()); + + EXPECT_EQ(1u, chrome::GetTotalBrowserCount()); + EXPECT_EQ(1, browser()->tab_strip_model()->count()); + EXPECT_EQ(0, browser()->tab_strip_model()->active_index()); + EXPECT_EQ(2, model_->item_count()); + + aura::Window* window = browser()->window()->GetNativeWindow(); + + int browser_index = ash::launcher::GetBrowserItemIndex(*model_); + ash::LauncherID browser_id = model_->items()[browser_index].id; + EXPECT_EQ(browser_id, controller->GetIDByWindow(window)); + + ash::LauncherID app_id = CreateShortcut("app1"); + EXPECT_EQ(3, model_->item_count()); + + // Creates a new tab for "app1" and checks that GetIDByWindow() returns + // |LauncherID| of "app1". + ActivateLauncherItem(model_->ItemIndexByID(app_id)); + EXPECT_EQ(2, browser()->tab_strip_model()->count()); + EXPECT_EQ(1, browser()->tab_strip_model()->active_index()); + EXPECT_EQ(app_id, controller->GetIDByWindow(window)); + + // Makes tab at index 0(NTP) as an active tab and checks that GetIDByWindow() + // returns |LauncherID| of browser shortcut. + browser()->tab_strip_model()->ActivateTabAt(0, false); + EXPECT_EQ(0, browser()->tab_strip_model()->active_index()); + EXPECT_EQ(browser_id, controller->GetIDByWindow(window)); +} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc index c08d2f6d..ea3861f 100644 --- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc @@ -1,8 +1,8 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// 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/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" #include <algorithm> #include <string> @@ -35,7 +35,6 @@ #include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/testing_pref_service_syncable.h" #include "chrome/test/base/testing_profile.h" -#include "content/public/browser/web_contents.h" #include "content/public/test/test_browser_thread.h" #include "extensions/common/manifest_constants.h" #include "testing/gtest/include/gtest/gtest.h" @@ -136,55 +135,16 @@ class TestAppIconLoaderImpl : public extensions::AppIconLoader { DISALLOW_COPY_AND_ASSIGN(TestAppIconLoaderImpl); }; -// Test implementation of AppTabHelper. -class TestAppTabHelperImpl : public ChromeLauncherController::AppTabHelper { - public: - TestAppTabHelperImpl() {} - virtual ~TestAppTabHelperImpl() {} - - // Sets the id for the specified tab. The id is removed if Remove() is - // invoked. - void SetAppID(content::WebContents* tab, const std::string& id) { - tab_id_map_[tab] = id; - } - - // Returns true if there is an id registered for |tab|. - bool HasAppID(content::WebContents* tab) const { - return tab_id_map_.find(tab) != tab_id_map_.end(); - } - - // AppTabHelper implementation: - virtual std::string GetAppID(content::WebContents* tab) OVERRIDE { - return tab_id_map_.find(tab) != tab_id_map_.end() ? tab_id_map_[tab] : - std::string(); - } - - virtual bool IsValidID(const std::string& id) OVERRIDE { - for (TabToStringMap::const_iterator i = tab_id_map_.begin(); - i != tab_id_map_.end(); ++i) { - if (i->second == id) - return true; - } - return false; - } - - private: - typedef std::map<content::WebContents*, std::string> TabToStringMap; - - TabToStringMap tab_id_map_; - - DISALLOW_COPY_AND_ASSIGN(TestAppTabHelperImpl); -}; - } // namespace -class ChromeLauncherControllerTest : public BrowserWithTestWindowTest { +class ChromeLauncherControllerPerAppTest : public BrowserWithTestWindowTest { protected: - ChromeLauncherControllerTest() : extension_service_(NULL) { + ChromeLauncherControllerPerAppTest() + : extension_service_(NULL) { SetHostDesktopType(chrome::HOST_DESKTOP_TYPE_ASH); } - virtual ~ChromeLauncherControllerTest() { + virtual ~ChromeLauncherControllerPerAppTest() { } virtual void SetUp() OVERRIDE { @@ -256,7 +216,7 @@ class ChromeLauncherControllerTest : public BrowserWithTestWindowTest { void InitLauncherController() { launcher_controller_.reset( - new ChromeLauncherController(profile(), model_.get())); + new ChromeLauncherControllerPerApp(profile(), model_.get())); launcher_controller_->Init(); } @@ -270,10 +230,6 @@ class ChromeLauncherControllerTest : public BrowserWithTestWindowTest { launcher_controller_->SetAppIconLoaderForTest(loader); } - void SetAppTabHelper(ChromeLauncherController::AppTabHelper* helper) { - launcher_controller_->SetAppTabHelperForTest(helper); - } - void InsertPrefValue(base::ListValue* pref_value, int index, const std::string& extension_id) { @@ -283,12 +239,12 @@ class ChromeLauncherControllerTest : public BrowserWithTestWindowTest { } // Gets the currently configured app launchers from the controller. - void GetAppLaunchers(ChromeLauncherController* controller, + void GetAppLaunchers(ChromeLauncherControllerPerApp* controller, std::vector<std::string>* launchers) { launchers->clear(); for (ash::LauncherItems::const_iterator iter(model_->items().begin()); iter != model_->items().end(); ++iter) { - ChromeLauncherController::IDToItemControllerMap::const_iterator + ChromeLauncherControllerPerApp::IDToItemControllerMap::const_iterator entry(controller->id_to_item_controller_map_.find(iter->id)); if (iter->type == ash::TYPE_APP_SHORTCUT && entry != controller->id_to_item_controller_map_.end()) { @@ -341,30 +297,30 @@ class ChromeLauncherControllerTest : public BrowserWithTestWindowTest { scoped_refptr<Extension> extension2_; scoped_refptr<Extension> extension3_; scoped_refptr<Extension> extension4_; - scoped_ptr<ChromeLauncherController> launcher_controller_; + scoped_ptr<ChromeLauncherControllerPerApp> launcher_controller_; scoped_ptr<TestLauncherModelObserver> model_observer_; scoped_ptr<ash::LauncherModel> model_; ExtensionService* extension_service_; - DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerTest); + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerAppTest); }; // The testing framework to test the alternate shelf layout. -class AlternateLayoutChromeLauncherControllerTest - : public ChromeLauncherControllerTest { +class AlternateLayoutChromeLauncherControllerPerAppTest + : public ChromeLauncherControllerPerAppTest { protected: - AlternateLayoutChromeLauncherControllerTest() { + AlternateLayoutChromeLauncherControllerPerAppTest() { } - virtual ~AlternateLayoutChromeLauncherControllerTest() { + virtual ~AlternateLayoutChromeLauncherControllerPerAppTest() { } // Overwrite the Setup function to add the Alternate Shelf layout option. virtual void SetUp() OVERRIDE { CommandLine::ForCurrentProcess()->AppendSwitch( ash::switches::kAshUseAlternateShelfLayout); - ChromeLauncherControllerTest::SetUp(); + ChromeLauncherControllerPerAppTest::SetUp(); } // Set the index at which the chrome icon should be. @@ -375,11 +331,11 @@ class AlternateLayoutChromeLauncherControllerTest private: - DISALLOW_COPY_AND_ASSIGN(AlternateLayoutChromeLauncherControllerTest); + DISALLOW_COPY_AND_ASSIGN(AlternateLayoutChromeLauncherControllerPerAppTest); }; -TEST_F(ChromeLauncherControllerTest, DefaultApps) { +TEST_F(ChromeLauncherControllerPerAppTest, DefaultApps) { InitLauncherController(); // Model should only contain the browser shortcut and app list items. EXPECT_EQ(2, model_->item_count()); @@ -398,7 +354,7 @@ TEST_F(ChromeLauncherControllerTest, DefaultApps) { // Check that the restauration of launcher items is happening in the same order // as the user has pinned them (on another system) when they are synced reverse // order. -TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsReverseOrder) { +TEST_F(ChromeLauncherControllerPerAppTest, RestoreDefaultAppsReverseOrder) { InitLauncherController(); base::ListValue policy_value; @@ -437,7 +393,7 @@ TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsReverseOrder) { // Check that the restauration of launcher items is happening in the same order // as the user has pinned them (on another system) when they are synced random // order. -TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsRandomOrder) { +TEST_F(ChromeLauncherControllerPerAppTest, RestoreDefaultAppsRandomOrder) { InitLauncherController(); base::ListValue policy_value; @@ -475,7 +431,8 @@ TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsRandomOrder) { // Check that the restauration of launcher items is happening in the same order // as the user has pinned / moved them (on another system) when they are synced // random order - including the chrome icon. -TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsRandomOrderChromeMoved) { +TEST_F(ChromeLauncherControllerPerAppTest, + RestoreDefaultAppsRandomOrderChromeMoved) { InitLauncherController(); base::ListValue policy_value; @@ -513,7 +470,7 @@ TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsRandomOrderChromeMoved) { } // Check that syncing to a different state does the correct thing. -TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsResyncOrder) { +TEST_F(ChromeLauncherControllerPerAppTest, RestoreDefaultAppsResyncOrder) { InitLauncherController(); base::ListValue policy_value; InsertPrefValue(&policy_value, 0, extension1_->id()); @@ -548,7 +505,7 @@ TEST_F(ChromeLauncherControllerTest, RestoreDefaultAppsResyncOrder) { EXPECT_EQ("App2, Chrome, App3, App1, AppList, ", GetPinnedAppStatus()); } -TEST_F(AlternateLayoutChromeLauncherControllerTest, DefaultApps) { +TEST_F(AlternateLayoutChromeLauncherControllerPerAppTest, DefaultApps) { InitLauncherController(); // Model should only contain the browser shortcut and app list items. EXPECT_EQ(2, model_->item_count()); @@ -567,7 +524,7 @@ TEST_F(AlternateLayoutChromeLauncherControllerTest, DefaultApps) { // Check that the restauration of launcher items is happening in the same order // as the user has pinned them (on another system) when they are synced reverse // order. -TEST_F(AlternateLayoutChromeLauncherControllerTest, +TEST_F(AlternateLayoutChromeLauncherControllerPerAppTest, RestoreDefaultAppsReverseOrder) { InitLauncherController(); @@ -607,7 +564,7 @@ TEST_F(AlternateLayoutChromeLauncherControllerTest, // Check that the restauration of launcher items is happening in the same order // as the user has pinned them (on another system) when they are synced random // order. -TEST_F(AlternateLayoutChromeLauncherControllerTest, +TEST_F(AlternateLayoutChromeLauncherControllerPerAppTest, RestoreDefaultAppsRandomOrder) { InitLauncherController(); @@ -646,7 +603,7 @@ TEST_F(AlternateLayoutChromeLauncherControllerTest, // Check that the restauration of launcher items is happening in the same order // as the user has pinned / moved them (on another system) when they are synced // random order - including the chrome icon - using the alternate shelf layout. -TEST_F(AlternateLayoutChromeLauncherControllerTest, +TEST_F(AlternateLayoutChromeLauncherControllerPerAppTest, RestoreDefaultAppsRandomOrderChromeMoved) { InitLauncherController(); @@ -685,7 +642,7 @@ TEST_F(AlternateLayoutChromeLauncherControllerTest, // Check that syncing to a different state does the correct thing with the // alternate shelf layout. -TEST_F(AlternateLayoutChromeLauncherControllerTest, +TEST_F(AlternateLayoutChromeLauncherControllerPerAppTest, RestoreDefaultAppsResyncOrder) { InitLauncherController(); base::ListValue policy_value; @@ -747,7 +704,7 @@ TEST_F(AlternateLayoutChromeLauncherControllerTest, } // Check that simple locking of an application will 'create' a launcher item. -TEST_F(ChromeLauncherControllerTest, CheckLockApps) { +TEST_F(ChromeLauncherControllerPerAppTest, CheckLockApps) { InitLauncherController(); // Model should only contain the browser shortcut and app list items. EXPECT_EQ(2, model_->item_count()); @@ -780,7 +737,7 @@ TEST_F(ChromeLauncherControllerTest, CheckLockApps) { } // Check that multiple locks of an application will be properly handled. -TEST_F(ChromeLauncherControllerTest, CheckMukltiLockApps) { +TEST_F(ChromeLauncherControllerPerAppTest, CheckMukltiLockApps) { InitLauncherController(); // Model should only contain the browser shortcut and app list items. EXPECT_EQ(2, model_->item_count()); @@ -817,7 +774,7 @@ TEST_F(ChromeLauncherControllerTest, CheckMukltiLockApps) { } // Check that already pinned items are not effected by locks. -TEST_F(ChromeLauncherControllerTest, CheckAlreadyPinnedLockApps) { +TEST_F(ChromeLauncherControllerPerAppTest, CheckAlreadyPinnedLockApps) { InitLauncherController(); // Model should only contain the browser shortcut and app list items. EXPECT_EQ(2, model_->item_count()); @@ -857,7 +814,7 @@ TEST_F(ChromeLauncherControllerTest, CheckAlreadyPinnedLockApps) { } // Check that already pinned items which get locked stay after unpinning. -TEST_F(ChromeLauncherControllerTest, CheckPinnedAppsStayAfterUnlock) { +TEST_F(ChromeLauncherControllerPerAppTest, CheckPinnedAppsStayAfterUnlock) { InitLauncherController(); // Model should only contain the browser shortcut and app list items. EXPECT_EQ(2, model_->item_count()); @@ -894,7 +851,7 @@ TEST_F(ChromeLauncherControllerTest, CheckPinnedAppsStayAfterUnlock) { } // Check that lock -> pin -> unlock -> unpin does properly transition. -TEST_F(ChromeLauncherControllerTest, CheckLockPinUnlockUnpin) { +TEST_F(ChromeLauncherControllerPerAppTest, CheckLockPinUnlockUnpin) { InitLauncherController(); // Model should only contain the browser shortcut and app list items. EXPECT_EQ(2, model_->item_count()); @@ -930,7 +887,7 @@ TEST_F(ChromeLauncherControllerTest, CheckLockPinUnlockUnpin) { EXPECT_EQ(2, model_->item_count()); } -TEST_F(ChromeLauncherControllerTest, Policy) { +TEST_F(ChromeLauncherControllerPerAppTest, Policy) { extension_service_->AddExtension(extension1_.get()); extension_service_->AddExtension(extension3_.get()); @@ -970,7 +927,7 @@ TEST_F(ChromeLauncherControllerTest, Policy) { EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id())); } -TEST_F(ChromeLauncherControllerTest, UnpinWithUninstall) { +TEST_F(ChromeLauncherControllerPerAppTest, UnpinWithUninstall) { extension_service_->AddExtension(extension3_.get()); extension_service_->AddExtension(extension4_.get()); @@ -986,7 +943,7 @@ TEST_F(ChromeLauncherControllerTest, UnpinWithUninstall) { EXPECT_TRUE(launcher_controller_->IsAppPinned(extension4_->id())); } -TEST_F(ChromeLauncherControllerTest, PrefUpdates) { +TEST_F(ChromeLauncherControllerPerAppTest, PrefUpdates) { extension_service_->AddExtension(extension2_.get()); extension_service_->AddExtension(extension3_.get()); extension_service_->AddExtension(extension4_.get()); @@ -1042,7 +999,7 @@ TEST_F(ChromeLauncherControllerTest, PrefUpdates) { EXPECT_EQ(expected_launchers, actual_launchers); } -TEST_F(ChromeLauncherControllerTest, PendingInsertionOrder) { +TEST_F(ChromeLauncherControllerPerAppTest, PendingInsertionOrder) { extension_service_->AddExtension(extension1_.get()); extension_service_->AddExtension(extension3_.get()); @@ -1075,7 +1032,7 @@ TEST_F(ChromeLauncherControllerTest, PendingInsertionOrder) { // found item count against the |expected_items|. The |title| list contains the // menu titles in the order of their appearance in the menu (not including the // application name). -bool CheckMenuCreation(ChromeLauncherController* controller, +bool CheckMenuCreation(ChromeLauncherControllerPerApp* controller, const ash::LauncherItem& item, size_t expected_items, string16 title[], @@ -1117,7 +1074,7 @@ bool CheckMenuCreation(ChromeLauncherController* controller, } // Check that browsers get reflected correctly in the launcher menu. -TEST_F(ChromeLauncherControllerTest, BrowserMenuGeneration) { +TEST_F(ChromeLauncherControllerPerAppTest, BrowserMenuGeneration) { EXPECT_EQ(1U, chrome::GetTotalBrowserCount()); chrome::NewTab(browser()); @@ -1165,7 +1122,7 @@ TEST_F(ChromeLauncherControllerTest, BrowserMenuGeneration) { // refocus logic. // Note that the extension matching logic is tested by the extension system // and does not need a separate test here. -TEST_F(ChromeLauncherControllerTest, V1AppMenuGeneration) { +TEST_F(ChromeLauncherControllerPerAppTest, V1AppMenuGeneration) { EXPECT_EQ(1U, chrome::GetTotalBrowserCount()); EXPECT_EQ(0, browser()->tab_strip_model()->count()); @@ -1238,7 +1195,7 @@ TEST_F(ChromeLauncherControllerTest, V1AppMenuGeneration) { } // Checks that the generated menu list properly activates items. -TEST_F(ChromeLauncherControllerTest, V1AppMenuExecution) { +TEST_F(ChromeLauncherControllerPerAppTest, V1AppMenuExecution) { InitLauncherControllerWithBrowser(); // Add |extension3_| to the launcher and add two items. @@ -1287,7 +1244,7 @@ TEST_F(ChromeLauncherControllerTest, V1AppMenuExecution) { } // Checks that the generated menu list properly deletes items. -TEST_F(ChromeLauncherControllerTest, V1AppMenuDeletionExecution) { +TEST_F(ChromeLauncherControllerPerAppTest, V1AppMenuDeletionExecution) { InitLauncherControllerWithBrowser(); // Add |extension3_| to the launcher and add two items. @@ -1328,9 +1285,8 @@ TEST_F(ChromeLauncherControllerTest, V1AppMenuDeletionExecution) { } // Tests that panels create launcher items correctly -TEST_F(ChromeLauncherControllerTest, AppPanels) { +TEST_F(ChromeLauncherControllerPerAppTest, AppPanels) { InitLauncherControllerWithBrowser(); - // Browser shortcut LauncherItem is added. EXPECT_EQ(1, model_observer_->added()); TestAppIconLoaderImpl* app_icon_loader = new TestAppIconLoaderImpl(); @@ -1374,7 +1330,7 @@ TEST_F(ChromeLauncherControllerTest, AppPanels) { // Tests that the Gmail extension matches more then the app itself claims with // the manifest file. -TEST_F(ChromeLauncherControllerTest, GmailMatching) { +TEST_F(ChromeLauncherControllerPerAppTest, GmailMatching) { InitLauncherControllerWithBrowser(); // Create a Gmail browser tab. @@ -1406,7 +1362,7 @@ TEST_F(ChromeLauncherControllerTest, GmailMatching) { } // Tests that the Gmail extension does not match the offline verison. -TEST_F(ChromeLauncherControllerTest, GmailOfflineMatching) { +TEST_F(ChromeLauncherControllerPerAppTest, GmailOfflineMatching) { InitLauncherControllerWithBrowser(); // Create a Gmail browser tab. @@ -1429,102 +1385,3 @@ TEST_F(ChromeLauncherControllerTest, GmailOfflineMatching) { // The content should not be able to be handled by the app. EXPECT_FALSE(launcher_controller_->ContentCanBeHandledByGmailApp(content)); } - -// Verify that the launcher item positions are persisted and restored. -TEST_F(ChromeLauncherControllerTest, PersistLauncherItemPositions) { - InitLauncherController(); - - TestAppTabHelperImpl* app_tab_helper = new TestAppTabHelperImpl; - SetAppTabHelper(app_tab_helper); - - EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[0].type); - EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[1].type); - - TabStripModel* tab_strip_model = browser()->tab_strip_model(); - EXPECT_EQ(0, tab_strip_model->count()); - chrome::NewTab(browser()); - chrome::NewTab(browser()); - EXPECT_EQ(2, tab_strip_model->count()); - app_tab_helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1"); - app_tab_helper->SetAppID(tab_strip_model->GetWebContentsAt(1), "2"); - - EXPECT_FALSE(launcher_controller_->IsAppPinned("1")); - launcher_controller_->PinAppWithID("1"); - EXPECT_TRUE(launcher_controller_->IsAppPinned("1")); - launcher_controller_->PinAppWithID("2"); - - EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[0].type); - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[1].type); - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type); - EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[3].type); - - // Move browser shortcut item from index 0 to index 2. - model_->Move(0, 2); - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[0].type); - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[1].type); - EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[2].type); - EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[3].type); - - launcher_controller_.reset(); - model_.reset(new ash::LauncherModel); - launcher_controller_.reset( - ChromeLauncherController::CreateInstance(profile(), model_.get())); - app_tab_helper = new TestAppTabHelperImpl; - app_tab_helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1"); - app_tab_helper->SetAppID(tab_strip_model->GetWebContentsAt(1), "2"); - SetAppTabHelper(app_tab_helper); - - launcher_controller_->Init(); - - // Check LauncherItems are restored after resetting ChromeLauncherController. - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[0].type); - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[1].type); - EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[2].type); - EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[3].type); -} - -// Verifies pinned apps are persisted and restored. -TEST_F(ChromeLauncherControllerTest, PersistPinned) { - InitLauncherControllerWithBrowser(); - size_t initial_size = model_->items().size(); - - TabStripModel* tab_strip_model = browser()->tab_strip_model(); - EXPECT_EQ(1, tab_strip_model->count()); - - TestAppTabHelperImpl* app_tab_helper = new TestAppTabHelperImpl; - app_tab_helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1"); - SetAppTabHelper(app_tab_helper); - - TestAppIconLoaderImpl* app_icon_loader = new TestAppIconLoaderImpl; - SetAppIconLoader(app_icon_loader); - EXPECT_EQ(0, app_icon_loader->fetch_count()); - - launcher_controller_->PinAppWithID("1"); - ash::LauncherID id = launcher_controller_->GetLauncherIDForAppID("1"); - int app_index = model_->ItemIndexByID(id); - EXPECT_EQ(1, app_icon_loader->fetch_count()); - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[app_index].type); - EXPECT_TRUE(launcher_controller_->IsAppPinned("1")); - EXPECT_FALSE(launcher_controller_->IsAppPinned("0")); - EXPECT_EQ(initial_size + 1, model_->items().size()); - - launcher_controller_.reset(); - model_.reset(new ash::LauncherModel); - launcher_controller_.reset( - ChromeLauncherController::CreateInstance(profile(), model_.get())); - app_tab_helper = new TestAppTabHelperImpl; - app_tab_helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1"); - SetAppTabHelper(app_tab_helper); - app_icon_loader = new TestAppIconLoaderImpl; - SetAppIconLoader(app_icon_loader); - launcher_controller_->Init(); - - EXPECT_EQ(1, app_icon_loader->fetch_count()); - ASSERT_EQ(initial_size + 1, model_->items().size()); - EXPECT_TRUE(launcher_controller_->IsAppPinned("1")); - EXPECT_FALSE(launcher_controller_->IsAppPinned("0")); - EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[app_index].type); - - launcher_controller_->UnpinAppsWithID("1"); - ASSERT_EQ(initial_size, model_->items().size()); -} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc new file mode 100644 index 0000000..4836677 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc @@ -0,0 +1,1476 @@ +// 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/ash/launcher/chrome_launcher_controller_per_browser.h" + +#include <vector> + +#include "ash/ash_switches.h" +#include "ash/launcher/launcher_model.h" +#include "ash/root_window_controller.h" +#include "ash/shelf/shelf_layout_manager.h" +#include "ash/shelf/shelf_widget.h" +#include "ash/shell.h" +#include "ash/wm/window_util.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" +#include "chrome/browser/app_mode/app_mode_utils.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/app_icon_loader_impl.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/prefs/incognito_mode_prefs.h" +#include "chrome/browser/prefs/pref_service_syncable.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/ash/app_sync_ui_state.h" +#include "chrome/browser/ui/ash/chrome_launcher_prefs.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "chrome/browser/ui/ash/launcher/launcher_app_tab_helper.h" +#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/browser/ui/ash/launcher/shell_window_launcher_controller.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/extensions/application_launch.h" +#include "chrome/browser/ui/extensions/extension_enable_flow.h" +#include "chrome/browser/ui/host_desktop.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/web_applications/web_app.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" +#include "chrome/common/extensions/manifest_handlers/icons_handler.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/web_contents.h" +#include "extensions/common/url_pattern.h" +#include "grit/chromium_strings.h" +#include "grit/theme_resources.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/base/l10n/l10n_util.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/login/default_pinned_apps_field_trial.h" +#endif + +using content::WebContents; +using extensions::Extension; + +namespace { + +// Item controller for an app shortcut. Shortcuts track app and launcher ids, +// but do not have any associated windows (opening a shortcut will replace the +// item with the appropriate LauncherItemController type). +class AppShortcutLauncherItemController : public LauncherItemController { + public: + AppShortcutLauncherItemController( + const std::string& app_id, + ChromeLauncherControllerPerBrowser* controller) + : LauncherItemController(TYPE_SHORTCUT, app_id, controller) { + // Google Drive should just refocus to it's main app UI. + // TODO(davemoore): Generalize this for other applications. + if (app_id == "apdfllckaahabafndbhieahigkjlhalf") { + const Extension* extension = + launcher_controller()->GetExtensionForAppID(app_id); + refocus_url_ = GURL( + extensions::AppLaunchInfo::GetLaunchWebURL(extension).spec() + "*"); + } + } + + virtual ~AppShortcutLauncherItemController() {} + + // LauncherItemController overrides: + virtual string16 GetTitle() OVERRIDE { + return GetAppTitle(); + } + + virtual bool IsCurrentlyShownInWindow(aura::Window* window) const OVERRIDE { + return false; + } + + virtual bool IsOpen() const OVERRIDE { + return false; + } + + virtual bool IsVisible() const OVERRIDE { + return false; + } + + virtual void Launch(int event_flags) OVERRIDE { + launcher_controller()->LaunchApp(app_id(), event_flags); + } + + virtual void Activate() OVERRIDE { + launcher_controller()->ActivateApp(app_id(), ui::EF_NONE); + } + + virtual void Close() OVERRIDE { + // TODO: maybe should treat as unpin? + } + + virtual void Clicked(const ui::Event& event) OVERRIDE { + Activate(); + } + + virtual void OnRemoved() OVERRIDE { + // AppShortcutLauncherItemController is unowned; delete on removal. + delete this; + } + + virtual void LauncherItemChanged( + int model_index, + const ash::LauncherItem& old_item) OVERRIDE { + } + + virtual ChromeLauncherAppMenuItems GetApplicationList( + int event_flags) OVERRIDE { + ChromeLauncherAppMenuItems items; + return items.Pass(); + } + + // Stores the optional refocus url pattern for this item. + const GURL& refocus_url() const { return refocus_url_; } + void set_refocus_url(const GURL& refocus_url) { refocus_url_ = refocus_url; } + + private: + GURL refocus_url_; + DISALLOW_COPY_AND_ASSIGN(AppShortcutLauncherItemController); +}; + +std::string GetPrefKeyForRootWindow(aura::RootWindow* root_window) { + gfx::Display display = gfx::Screen::GetScreenFor( + root_window)->GetDisplayNearestWindow(root_window); + DCHECK(display.is_valid()); + + return base::Int64ToString(display.id()); +} + +void UpdatePerDisplayPref(PrefService* pref_service, + aura::RootWindow* root_window, + const char* pref_key, + const std::string& value) { + std::string key = GetPrefKeyForRootWindow(root_window); + if (key.empty()) + return; + + DictionaryPrefUpdate update(pref_service, prefs::kShelfPreferences); + base::DictionaryValue* shelf_prefs = update.Get(); + base::DictionaryValue* prefs = NULL; + if (!shelf_prefs->GetDictionary(key, &prefs)) { + prefs = new base::DictionaryValue(); + shelf_prefs->Set(key, prefs); + } + prefs->SetStringWithoutPathExpansion(pref_key, value); +} + +// Returns a pref value in |pref_service| for the display of |root_window|. The +// pref value is stored in |local_path| and |path|, but |pref_service| may have +// per-display preferences and the value can be specified by policy. Here is +// the priority: +// * A value managed by policy. This is a single value that applies to all +// displays. +// * A user-set value for the specified display. +// * A user-set value in |local_path| or |path|, if no per-display settings are +// ever specified (see http://crbug.com/173719 for why). |local_path| is +// preferred. See comment in |kShelfAlignment| as to why we consider two +// prefs and why |local_path| is preferred. +// * A value recommended by policy. This is a single value that applies to all +// root windows. +// * The default value for |local_path| if the value is not recommended by +// policy. +std::string GetPrefForRootWindow(PrefService* pref_service, + aura::RootWindow* root_window, + const char* local_path, + const char* path) { + const PrefService::Preference* local_pref = + pref_service->FindPreference(local_path); + const std::string value(pref_service->GetString(local_path)); + if (local_pref->IsManaged()) + return value; + + std::string pref_key = GetPrefKeyForRootWindow(root_window); + bool has_per_display_prefs = false; + if (!pref_key.empty()) { + const base::DictionaryValue* shelf_prefs = pref_service->GetDictionary( + prefs::kShelfPreferences); + const base::DictionaryValue* display_pref = NULL; + std::string per_display_value; + if (shelf_prefs->GetDictionary(pref_key, &display_pref) && + display_pref->GetString(path, &per_display_value)) + return per_display_value; + + // If the pref for the specified display is not found, scan the whole prefs + // and check if the prefs for other display is already specified. + std::string unused_value; + for (base::DictionaryValue::Iterator iter(*shelf_prefs); + !iter.IsAtEnd(); iter.Advance()) { + const base::DictionaryValue* display_pref = NULL; + if (iter.value().GetAsDictionary(&display_pref) && + display_pref->GetString(path, &unused_value)) { + has_per_display_prefs = true; + break; + } + } + } + + if (local_pref->IsRecommended() || !has_per_display_prefs) + return value; + + const base::Value* default_value = + pref_service->GetDefaultPrefValue(local_path); + std::string default_string; + default_value->GetAsString(&default_string); + return default_string; +} + +// If prefs have synced and no user-set value exists at |local_path|, the value +// from |synced_path| is copied to |local_path|. +void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service, + const char* local_path, + const char* synced_path) { + if (!pref_service->FindPreference(local_path)->HasUserSetting() && + pref_service->IsSyncing()) { + // First time the user is using this machine, propagate from remote to + // local. + pref_service->SetString(local_path, pref_service->GetString(synced_path)); + } +} + +} // namespace + +// ChromeLauncherControllerPerBrowser ----------------------------------------- + +ChromeLauncherControllerPerBrowser::ChromeLauncherControllerPerBrowser( + Profile* profile, + ash::LauncherModel* model) + : model_(model), + profile_(profile), + app_sync_ui_state_(NULL), + ignore_persist_pinned_state_change_(false) { + if (!profile_) { + // Use the original profile as on chromeos we may get a temporary off the + // record profile. + profile_ = ProfileManager::GetDefaultProfile()->GetOriginalProfile(); + + app_sync_ui_state_ = AppSyncUIState::Get(profile_); + if (app_sync_ui_state_) + app_sync_ui_state_->AddObserver(this); + } + + model_->AddObserver(this); + // Right now ash::Shell isn't created for tests. + // TODO(mukai): Allows it to observe display change and write tests. + if (ash::Shell::HasInstance()) + ash::Shell::GetInstance()->display_controller()->AddObserver(this); + // TODO(stevenjb): Find a better owner for shell_window_controller_? + shell_window_controller_.reset(new ShellWindowLauncherController(this)); + app_tab_helper_.reset(new LauncherAppTabHelper(profile_)); + app_icon_loader_.reset(new extensions::AppIconLoaderImpl( + profile_, extension_misc::EXTENSION_ICON_SMALL, this)); + + notification_registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_LOADED, + content::Source<Profile>(profile_)); + notification_registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_UNLOADED, + content::Source<Profile>(profile_)); + pref_change_registrar_.Init(profile_->GetPrefs()); + pref_change_registrar_.Add( + prefs::kPinnedLauncherApps, + base::Bind(&ChromeLauncherControllerPerBrowser:: + UpdateAppLaunchersFromPref, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kShelfAlignmentLocal, + base::Bind(&ChromeLauncherControllerPerBrowser:: + SetShelfAlignmentFromPrefs, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kShelfAutoHideBehaviorLocal, + base::Bind(&ChromeLauncherControllerPerBrowser:: + SetShelfAutoHideBehaviorFromPrefs, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kShelfPreferences, + base::Bind(&ChromeLauncherControllerPerBrowser:: + SetShelfBehaviorsFromPrefs, + base::Unretained(this))); +} + +ChromeLauncherControllerPerBrowser::~ChromeLauncherControllerPerBrowser() { + // Reset the shell window controller here since it has a weak pointer to + // this. + shell_window_controller_.reset(); + + for (std::set<ash::Launcher*>::iterator iter = launchers_.begin(); + iter != launchers_.end(); + ++iter) + (*iter)->shelf_widget()->shelf_layout_manager()->RemoveObserver(this); + + model_->RemoveObserver(this); + if (ash::Shell::HasInstance()) + ash::Shell::GetInstance()->display_controller()->RemoveObserver(this); + for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + i->second->OnRemoved(); + model_->RemoveItemAt(model_->ItemIndexByID(i->first)); + } + + if (ash::Shell::HasInstance()) + ash::Shell::GetInstance()->RemoveShellObserver(this); + + if (app_sync_ui_state_) + app_sync_ui_state_->RemoveObserver(this); + + PrefServiceSyncable::FromProfile(profile_)->RemoveObserver(this); +} + +void ChromeLauncherControllerPerBrowser::Init() { + UpdateAppLaunchersFromPref(); + CreateBrowserShortcutLauncherItem(); + + // TODO(sky): update unit test so that this test isn't necessary. + if (ash::Shell::HasInstance()) { + SetShelfAutoHideBehaviorFromPrefs(); + SetShelfAlignmentFromPrefs(); + PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); + if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() || + !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal)-> + HasUserSetting()) { + // This causes OnIsSyncingChanged to be called when the value of + // PrefService::IsSyncing() changes. + prefs->AddObserver(this); + } + ash::Shell::GetInstance()->AddShellObserver(this); + } +} + +ChromeLauncherControllerPerApp* +ChromeLauncherControllerPerBrowser::GetPerAppInterface() { + return NULL; +} + +ash::LauncherID ChromeLauncherControllerPerBrowser::CreateTabbedLauncherItem( + LauncherItemController* controller, + IncognitoState is_incognito, + ash::LauncherItemStatus status) { + ash::LauncherID id = model_->next_id(); + DCHECK(!HasItemController(id)); + DCHECK(controller); + id_to_item_controller_map_[id] = controller; + controller->set_launcher_id(id); + + ash::LauncherItem item; + item.type = ash::TYPE_TABBED; + item.is_incognito = (is_incognito == STATE_INCOGNITO); + item.status = status; + model_->Add(item); + return id; +} + +ash::LauncherID ChromeLauncherControllerPerBrowser::CreateAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status) { + DCHECK(controller); + int index = 0; + // Panels are inserted on the left so as not to push all existing panels over. + if (controller->GetLauncherItemType() != ash::TYPE_APP_PANEL) { + index = model_->item_count(); + // For the alternate shelf layout increment index (insert after app icon). + if (ash::switches::UseAlternateShelfLayout()) + ++index; + } + return InsertAppLauncherItem(controller, app_id, status, index); +} + +void ChromeLauncherControllerPerBrowser::SetItemStatus( + ash::LauncherID id, + ash::LauncherItemStatus status) { + int index = model_->ItemIndexByID(id); + DCHECK_GE(index, 0); + ash::LauncherItem item = model_->items()[index]; + item.status = status; + model_->Set(index, item); +} + +void ChromeLauncherControllerPerBrowser::SetItemController( + ash::LauncherID id, + LauncherItemController* controller) { + IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); + DCHECK(iter != id_to_item_controller_map_.end()); + iter->second->OnRemoved(); + iter->second = controller; + controller->set_launcher_id(id); +} + +void ChromeLauncherControllerPerBrowser::CloseLauncherItem( + ash::LauncherID id) { + if (IsPinned(id)) { + // Create a new shortcut controller. + IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); + DCHECK(iter != id_to_item_controller_map_.end()); + SetItemStatus(id, ash::STATUS_CLOSED); + std::string app_id = iter->second->app_id(); + iter->second->OnRemoved(); + iter->second = new AppShortcutLauncherItemController(app_id, this); + iter->second->set_launcher_id(id); + } else { + LauncherItemClosed(id); + } +} + +void ChromeLauncherControllerPerBrowser::Unpin(ash::LauncherID id) { + DCHECK(HasItemController(id)); + + LauncherItemController* controller = id_to_item_controller_map_[id]; + if (controller->type() == LauncherItemController::TYPE_APP) { + int index = model_->ItemIndexByID(id); + ash::LauncherItem item = model_->items()[index]; + item.type = ash::TYPE_PLATFORM_APP; + model_->Set(index, item); + } else { + LauncherItemClosed(id); + } + if (CanPin()) + PersistPinnedState(); +} + +void ChromeLauncherControllerPerBrowser::Pin(ash::LauncherID id) { + DCHECK(HasItemController(id)); + + int index = model_->ItemIndexByID(id); + ash::LauncherItem item = model_->items()[index]; + + if (item.type != ash::TYPE_PLATFORM_APP) + return; + + item.type = ash::TYPE_APP_SHORTCUT; + model_->Set(index, item); + + if (CanPin()) + PersistPinnedState(); +} + +bool ChromeLauncherControllerPerBrowser::IsPinned(ash::LauncherID id) { + int index = model_->ItemIndexByID(id); + ash::LauncherItemType type = model_->items()[index].type; + return (type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_BROWSER_SHORTCUT); +} + +void ChromeLauncherControllerPerBrowser::TogglePinned(ash::LauncherID id) { + if (!HasItemController(id)) + return; // May happen if item closed with menu open. + + if (IsPinned(id)) + Unpin(id); + else + Pin(id); +} + +bool ChromeLauncherControllerPerBrowser::IsPinnable(ash::LauncherID id) const { + int index = model_->ItemIndexByID(id); + if (index == -1) + return false; + + ash::LauncherItemType type = model_->items()[index].type; + return ((type == ash::TYPE_APP_SHORTCUT || type == ash::TYPE_PLATFORM_APP) && + CanPin()); +} + +void ChromeLauncherControllerPerBrowser::LockV1AppWithID( + const std::string& app_id) { +} + +void ChromeLauncherControllerPerBrowser::UnlockV1AppWithID( + const std::string& app_id) { +} + +void ChromeLauncherControllerPerBrowser::Launch( + ash::LauncherID id, int event_flags) { + if (!HasItemController(id)) + return; // In case invoked from menu and item closed while menu up. + id_to_item_controller_map_[id]->Launch(event_flags); +} + +void ChromeLauncherControllerPerBrowser::Close(ash::LauncherID id) { + if (!HasItemController(id)) + return; // May happen if menu closed. + id_to_item_controller_map_[id]->Close(); +} + +bool ChromeLauncherControllerPerBrowser::IsOpen(ash::LauncherID id) { + if (!HasItemController(id)) + return false; + return id_to_item_controller_map_[id]->IsOpen(); +} + +bool ChromeLauncherControllerPerBrowser::IsPlatformApp(ash::LauncherID id) { + if (!HasItemController(id)) + return false; + + std::string app_id = GetAppIDForLauncherID(id); + const Extension* extension = GetExtensionForAppID(app_id); + DCHECK(extension); + return extension->is_platform_app(); +} + +void ChromeLauncherControllerPerBrowser::LaunchApp(const std::string& app_id, + int event_flags) { + // |extension| could be NULL when it is being unloaded for updating. + const Extension* extension = GetExtensionForAppID(app_id); + if (!extension) + return; + + const ExtensionService* service = + extensions::ExtensionSystem::Get(profile_)->extension_service(); + if (!service->IsExtensionEnabledForLauncher(app_id)) { + // Do nothing if there is already a running enable flow. + if (extension_enable_flow_) + return; + + extension_enable_flow_.reset( + new ExtensionEnableFlow(profile_, app_id, this)); + extension_enable_flow_->StartForNativeWindow(NULL); + return; + } + + chrome::OpenApplication(chrome::AppLaunchParams(GetProfileForNewWindows(), + extension, + event_flags)); +} + +void ChromeLauncherControllerPerBrowser::ActivateApp(const std::string& app_id, + int event_flags) { + if (app_id == extension_misc::kChromeAppId) { + BrowserShortcutClicked(event_flags); + return; + } + + // If there is an existing non-shortcut controller for this app, open it. + ash::LauncherID id = GetLauncherIDForAppID(app_id); + URLPattern refocus_pattern(URLPattern::SCHEME_ALL); + refocus_pattern.SetMatchAllURLs(true); + + if (id > 0) { + LauncherItemController* controller = id_to_item_controller_map_[id]; + if (controller->type() != LauncherItemController::TYPE_SHORTCUT) { + controller->Activate(); + return; + } + + AppShortcutLauncherItemController* app_controller = + static_cast<AppShortcutLauncherItemController*>(controller); + const GURL refocus_url = app_controller->refocus_url(); + + if (!refocus_url.is_empty()) + refocus_pattern.Parse(refocus_url.spec()); + } + + // Check if there are any open tabs for this app. + AppIDToWebContentsListMap::iterator app_i = + app_id_to_web_contents_list_.find(app_id); + if (app_i != app_id_to_web_contents_list_.end()) { + for (WebContentsList::iterator tab_i = app_i->second.begin(); + tab_i != app_i->second.end(); + ++tab_i) { + WebContents* tab = *tab_i; + const GURL tab_url = tab->GetURL(); + if (refocus_pattern.MatchesURL(tab_url)) { + Browser* browser = chrome::FindBrowserWithWebContents(tab); + TabStripModel* tab_strip = browser->tab_strip_model(); + int index = tab_strip->GetIndexOfWebContents(tab); + DCHECK_NE(TabStripModel::kNoTab, index); + tab_strip->ActivateTabAt(index, false); + browser->window()->Show(); + ash::wm::ActivateWindow(browser->window()->GetNativeWindow()); + return; + } + } + } + + LaunchApp(app_id, event_flags); +} + +extensions::ExtensionPrefs::LaunchType + ChromeLauncherControllerPerBrowser::GetLaunchType(ash::LauncherID id) { + DCHECK(HasItemController(id)); + + const Extension* extension = GetExtensionForAppID( + id_to_item_controller_map_[id]->app_id()); + return profile_->GetExtensionService()->extension_prefs()->GetLaunchType( + extension, + extensions::ExtensionPrefs::LAUNCH_DEFAULT); +} + +std::string ChromeLauncherControllerPerBrowser::GetAppID( + content::WebContents* tab) { + return app_tab_helper_->GetAppID(tab); +} + +ash::LauncherID ChromeLauncherControllerPerBrowser::GetLauncherIDForAppID( + const std::string& app_id) { + for (IDToItemControllerMap::const_iterator i = + id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + if (i->second->type() == LauncherItemController::TYPE_APP_PANEL) + continue; // Don't include panels + if (i->second->app_id() == app_id) + return i->first; + } + return 0; +} + +std::string ChromeLauncherControllerPerBrowser::GetAppIDForLauncherID( + ash::LauncherID id) { + DCHECK(HasItemController(id)); + return id_to_item_controller_map_[id]->app_id(); +} + +void ChromeLauncherControllerPerBrowser::SetAppImage( + const std::string& id, + const gfx::ImageSkia& image) { + // TODO: need to get this working for shortcuts. + + for (IDToItemControllerMap::const_iterator i = + id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + if (i->second->app_id() != id) + continue; + + int index = model_->ItemIndexByID(i->first); + ash::LauncherItem item = model_->items()[index]; + item.image = image; + model_->Set(index, item); + // It's possible we're waiting on more than one item, so don't break. + } +} + +void ChromeLauncherControllerPerBrowser::OnAutoHideBehaviorChanged( + aura::RootWindow* root_window, + ash::ShelfAutoHideBehavior new_behavior) { + SetShelfAutoHideBehaviorPrefs(new_behavior, root_window); +} + +void ChromeLauncherControllerPerBrowser::SetLauncherItemImage( + ash::LauncherID launcher_id, + const gfx::ImageSkia& image) { + int index = model_->ItemIndexByID(launcher_id); + if (index == -1) + return; + ash::LauncherItem item = model_->items()[index]; + item.image = image; + model_->Set(index, item); +} + +bool ChromeLauncherControllerPerBrowser::IsAppPinned( + const std::string& app_id) { + // Check the LauncherModel since there is no controller for the browser item. + if (app_id == extension_misc::kChromeAppId) { + for (size_t index = 0; index < model_->items().size(); index++) { + if (model_->items()[index].type == ash::TYPE_BROWSER_SHORTCUT) + return true; + } + return false; + } + for (IDToItemControllerMap::const_iterator i = + id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + if (IsPinned(i->first) && i->second->app_id() == app_id) + return true; + } + return false; +} + +void ChromeLauncherControllerPerBrowser::PinAppWithID( + const std::string& app_id) { + if (CanPin()) + DoPinAppWithID(app_id); + else + NOTREACHED(); +} + +void ChromeLauncherControllerPerBrowser::SetLaunchType( + ash::LauncherID id, + extensions::ExtensionPrefs::LaunchType launch_type) { + if (!HasItemController(id)) + return; + + return profile_->GetExtensionService()->extension_prefs()->SetLaunchType( + id_to_item_controller_map_[id]->app_id(), launch_type); +} + +void ChromeLauncherControllerPerBrowser::UnpinAppsWithID( + const std::string& app_id) { + if (CanPin()) + DoUnpinAppsWithID(app_id); + else + NOTREACHED(); +} + +bool ChromeLauncherControllerPerBrowser::IsLoggedInAsGuest() { + return ProfileManager::GetDefaultProfileOrOffTheRecord()->IsOffTheRecord(); +} + +void ChromeLauncherControllerPerBrowser::CreateNewWindow() { + chrome::NewEmptyWindow( + GetProfileForNewWindows(), chrome::HOST_DESKTOP_TYPE_ASH); +} + +void ChromeLauncherControllerPerBrowser::CreateNewIncognitoWindow() { + chrome::NewEmptyWindow(GetProfileForNewWindows()->GetOffTheRecordProfile(), + chrome::HOST_DESKTOP_TYPE_ASH); +} + +bool ChromeLauncherControllerPerBrowser::CanPin() const { + const PrefService::Preference* pref = + profile_->GetPrefs()->FindPreference(prefs::kPinnedLauncherApps); + return pref && pref->IsUserModifiable(); +} + +ash::ShelfAutoHideBehavior + ChromeLauncherControllerPerBrowser::GetShelfAutoHideBehavior( + aura::RootWindow* root_window) const { + // Don't show the shelf in the app mode. + if (chrome::IsRunningInAppMode()) + return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN; + + // See comment in |kShelfAlignment| as to why we consider two prefs. + const std::string behavior_value( + GetPrefForRootWindow(profile_->GetPrefs(), + root_window, + prefs::kShelfAutoHideBehaviorLocal, + prefs::kShelfAutoHideBehavior)); + + // Note: To maintain sync compatibility with old images of chrome/chromeos + // the set of values that may be encountered includes the now-extinct + // "Default" as well as "Never" and "Always", "Default" should now + // be treated as "Never" (http://crbug.com/146773). + if (behavior_value == ash::kShelfAutoHideBehaviorAlways) + return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; + return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER; +} + +bool ChromeLauncherControllerPerBrowser::CanUserModifyShelfAutoHideBehavior( + aura::RootWindow* root_window) const { + return profile_->GetPrefs()-> + FindPreference(prefs::kShelfAutoHideBehaviorLocal)->IsUserModifiable(); +} + +void ChromeLauncherControllerPerBrowser::ToggleShelfAutoHideBehavior( + aura::RootWindow* root_window) { + ash::ShelfAutoHideBehavior behavior = GetShelfAutoHideBehavior(root_window) == + ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ? + ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER : + ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; + SetShelfAutoHideBehaviorPrefs(behavior, root_window); + return; +} + +void ChromeLauncherControllerPerBrowser::RemoveTabFromRunningApp( + WebContents* tab, + const std::string& app_id) { + web_contents_to_app_id_.erase(tab); + AppIDToWebContentsListMap::iterator i_app_id = + app_id_to_web_contents_list_.find(app_id); + if (i_app_id != app_id_to_web_contents_list_.end()) { + WebContentsList* tab_list = &i_app_id->second; + tab_list->remove(tab); + if (tab_list->empty()) { + app_id_to_web_contents_list_.erase(i_app_id); + i_app_id = app_id_to_web_contents_list_.end(); + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (id > 0) + SetItemStatus(id, ash::STATUS_CLOSED); + } + } +} + +void ChromeLauncherControllerPerBrowser::UpdateAppState( + content::WebContents* contents, + AppState app_state) { + std::string app_id = GetAppID(contents); + + // Check the old |app_id| for a tab. If the contents has changed we need to + // remove it from the previous app. + if (web_contents_to_app_id_.find(contents) != web_contents_to_app_id_.end()) { + std::string last_app_id = web_contents_to_app_id_[contents]; + if (last_app_id != app_id) + RemoveTabFromRunningApp(contents, last_app_id); + } + + if (app_id.empty()) + return; + + web_contents_to_app_id_[contents] = app_id; + + if (app_state == APP_STATE_REMOVED) { + // The tab has gone away. + RemoveTabFromRunningApp(contents, app_id); + } else { + WebContentsList& tab_list(app_id_to_web_contents_list_[app_id]); + + if (app_state == APP_STATE_INACTIVE) { + WebContentsList::const_iterator i_tab = + std::find(tab_list.begin(), tab_list.end(), contents); + if (i_tab == tab_list.end()) + tab_list.push_back(contents); + if (i_tab != tab_list.begin()) { + // Going inactive, but wasn't the front tab, indicating that a new + // tab has already become active. + return; + } + } else { + tab_list.remove(contents); + tab_list.push_front(contents); + } + ash::LauncherID id = GetLauncherIDForAppID(app_id); + if (id > 0) { + // If the window is active, mark the app as active. + SetItemStatus(id, app_state == APP_STATE_WINDOW_ACTIVE ? + ash::STATUS_ACTIVE : ash::STATUS_RUNNING); + } + } +} + +void ChromeLauncherControllerPerBrowser::SetRefocusURLPatternForTest( + ash::LauncherID id, + const GURL& url) { + DCHECK(HasItemController(id)); + LauncherItemController* controller = id_to_item_controller_map_[id]; + + int index = model_->ItemIndexByID(id); + if (index == -1) { + NOTREACHED() << "Invalid launcher id"; + return; + } + + ash::LauncherItemType type = model_->items()[index].type; + if (type == ash::TYPE_APP_SHORTCUT) { + AppShortcutLauncherItemController* app_controller = + static_cast<AppShortcutLauncherItemController*>(controller); + app_controller->set_refocus_url(url); + } else { + NOTREACHED() << "Invalid launcher type"; + } +} + +const Extension* ChromeLauncherControllerPerBrowser::GetExtensionForAppID( + const std::string& app_id) const { + return profile_->GetExtensionService()->GetInstalledExtension(app_id); +} + +void ChromeLauncherControllerPerBrowser::ActivateWindowOrMinimizeIfActive( + ui::BaseWindow* window, + bool allow_minimize) { + window->Show(); + window->Activate(); +} + +void ChromeLauncherControllerPerBrowser::BrowserShortcutClicked( + int event_flags) { +#if defined(OS_CHROMEOS) + chromeos::default_pinned_apps_field_trial::RecordShelfClick( + chromeos::default_pinned_apps_field_trial::CHROME); +#endif + if (event_flags & ui::EF_CONTROL_DOWN) { + CreateNewWindow(); + return; + } + + Browser* last_browser = chrome::FindTabbedBrowser( + GetProfileForNewWindows(), true, chrome::HOST_DESKTOP_TYPE_ASH); + + if (!last_browser) { + CreateNewWindow(); + return; + } + + aura::Window* window = last_browser->window()->GetNativeWindow(); + window->Show(); + ash::wm::ActivateWindow(window); +} + +void ChromeLauncherControllerPerBrowser::ItemSelected( + const ash::LauncherItem& item, + const ui::Event& event) { + if (item.type == ash::TYPE_BROWSER_SHORTCUT) { + BrowserShortcutClicked(event.flags()); + return; + } + + DCHECK(HasItemController(item.id)); + LauncherItemController* item_controller = id_to_item_controller_map_[item.id]; +#if defined(OS_CHROMEOS) + if (!item_controller->app_id().empty()) { + chromeos::default_pinned_apps_field_trial::RecordShelfAppClick( + item_controller->app_id()); + } +#endif + item_controller->Clicked(event); +} + +string16 ChromeLauncherControllerPerBrowser::GetTitle( + const ash::LauncherItem& item) { + if (item.type == ash::TYPE_BROWSER_SHORTCUT) + return l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); + + DCHECK(HasItemController(item.id)); + return id_to_item_controller_map_[item.id]->GetTitle(); +} + +ui::MenuModel* ChromeLauncherControllerPerBrowser::CreateContextMenu( + const ash::LauncherItem& item, + aura::RootWindow* root_window) { + return new LauncherContextMenu(this, &item, root_window); +} + +ash::LauncherMenuModel* +ChromeLauncherControllerPerBrowser::CreateApplicationMenu( + const ash::LauncherItem& item, + int event_flags) { + // Not used by this launcher type. + return NULL; +} + +ash::LauncherID ChromeLauncherControllerPerBrowser::GetIDByWindow( + aura::Window* window) { + for (IDToItemControllerMap::const_iterator i = + id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ++i) { + if (i->second->IsCurrentlyShownInWindow(window)) + return i->first; + } + return 0; +} + +bool ChromeLauncherControllerPerBrowser::IsDraggable( + const ash::LauncherItem& item) { + return item.type == ash::TYPE_APP_SHORTCUT ? CanPin() : true; +} + +bool ChromeLauncherControllerPerBrowser::ShouldShowTooltip( + const ash::LauncherItem& item) { + if (item.type == ash::TYPE_APP_PANEL && + id_to_item_controller_map_[item.id]->IsVisible()) + return false; + return true; +} + +void ChromeLauncherControllerPerBrowser::OnLauncherCreated( + ash::Launcher* launcher) { + launchers_.insert(launcher); + launcher->shelf_widget()->shelf_layout_manager()->AddObserver(this); +} + +void ChromeLauncherControllerPerBrowser::OnLauncherDestroyed( + ash::Launcher* launcher) { + launchers_.erase(launcher); + // RemoveObserver is not called here, since by the time this method is called + // Launcher is already in its destructor. +} + +void ChromeLauncherControllerPerBrowser::LauncherItemAdded(int index) { +} + +void ChromeLauncherControllerPerBrowser::LauncherItemRemoved( + int index, + ash::LauncherID id) { +} + +void ChromeLauncherControllerPerBrowser::LauncherItemMoved( + int start_index, + int target_index) { + ash::LauncherID id = model_->items()[target_index].id; + if (HasItemController(id) && IsPinned(id)) + PersistPinnedState(); + else if (!HasItemController(id) && + model_->items()[target_index].type == ash::TYPE_BROWSER_SHORTCUT) + PersistPinnedState(); +} + +void ChromeLauncherControllerPerBrowser::LauncherItemChanged( + int index, + const ash::LauncherItem& old_item) { + ash::LauncherID id = model_->items()[index].id; + id_to_item_controller_map_[id]->LauncherItemChanged(index, old_item); +} + +void ChromeLauncherControllerPerBrowser::LauncherStatusChanged() { +} + +void ChromeLauncherControllerPerBrowser::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_EXTENSION_LOADED: { + const Extension* extension = + content::Details<const Extension>(details).ptr(); + if (IsAppPinned(extension->id())) { + // Clear and re-fetch to ensure icon is up-to-date. + app_icon_loader_->ClearImage(extension->id()); + app_icon_loader_->FetchImage(extension->id()); + } + + UpdateAppLaunchersFromPref(); + break; + } + case chrome::NOTIFICATION_EXTENSION_UNLOADED: { + const content::Details<extensions::UnloadedExtensionInfo>& unload_info( + details); + const Extension* extension = unload_info->extension; + if (IsAppPinned(extension->id())) { + if (unload_info->reason == extension_misc::UNLOAD_REASON_UNINSTALL) { + DoUnpinAppsWithID(extension->id()); + app_icon_loader_->ClearImage(extension->id()); + } else { + app_icon_loader_->UpdateImage(extension->id()); + } + } + break; + } + default: + NOTREACHED() << "Unexpected notification type=" << type; + } +} + +void ChromeLauncherControllerPerBrowser::OnShelfAlignmentChanged( + aura::RootWindow* root_window) { + const char* pref_value = NULL; + switch (ash::Shell::GetInstance()->GetShelfAlignment(root_window)) { + case ash::SHELF_ALIGNMENT_BOTTOM: + pref_value = ash::kShelfAlignmentBottom; + break; + case ash::SHELF_ALIGNMENT_LEFT: + pref_value = ash::kShelfAlignmentLeft; + break; + case ash::SHELF_ALIGNMENT_RIGHT: + pref_value = ash::kShelfAlignmentRight; + break; + case ash::SHELF_ALIGNMENT_TOP: + pref_value = ash::kShelfAlignmentTop; + break; + } + + UpdatePerDisplayPref( + profile_->GetPrefs(), root_window, prefs::kShelfAlignment, pref_value); + + if (root_window == ash::Shell::GetPrimaryRootWindow()) { + // See comment in |kShelfAlignment| about why we have two prefs here. + profile_->GetPrefs()->SetString(prefs::kShelfAlignmentLocal, pref_value); + profile_->GetPrefs()->SetString(prefs::kShelfAlignment, pref_value); + } +} + +void ChromeLauncherControllerPerBrowser::OnDisplayConfigurationChanging() { +} + +void ChromeLauncherControllerPerBrowser::OnDisplayConfigurationChanged() { + SetShelfBehaviorsFromPrefs(); +} + +void ChromeLauncherControllerPerBrowser::OnIsSyncingChanged() { + PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); + MaybePropagatePrefToLocal(prefs, + prefs::kShelfAlignmentLocal, + prefs::kShelfAlignment); + MaybePropagatePrefToLocal(prefs, + prefs::kShelfAutoHideBehaviorLocal, + prefs::kShelfAutoHideBehavior); +} + +void ChromeLauncherControllerPerBrowser::OnAppSyncUIStatusChanged() { + if (app_sync_ui_state_->status() == AppSyncUIState::STATUS_SYNCING) + model_->SetStatus(ash::LauncherModel::STATUS_LOADING); + else + model_->SetStatus(ash::LauncherModel::STATUS_NORMAL); +} + +void ChromeLauncherControllerPerBrowser::ExtensionEnableFlowFinished() { + LaunchApp(extension_enable_flow_->extension_id(), ui::EF_NONE); + extension_enable_flow_.reset(); +} + +void ChromeLauncherControllerPerBrowser::ExtensionEnableFlowAborted( + bool user_initiated) { + extension_enable_flow_.reset(); +} + +void ChromeLauncherControllerPerBrowser::PersistPinnedState() { + if (ignore_persist_pinned_state_change_) + return; + // It is a coding error to call PersistPinnedState() if the pinned apps are + // not user-editable. The code should check earlier and not perform any + // modification actions that trigger persisting the state. + if (!CanPin()) { + NOTREACHED() << "Can't pin but pinned state being updated"; + return; + } + + // Mutating kPinnedLauncherApps is going to notify us and trigger us to + // process the change. We don't want that to happen so remove ourselves as a + // listener. + pref_change_registrar_.Remove(prefs::kPinnedLauncherApps); + { + ListPrefUpdate updater(profile_->GetPrefs(), prefs::kPinnedLauncherApps); + updater->Clear(); + for (size_t i = 0; i < model_->items().size(); ++i) { + if (model_->items()[i].type == ash::TYPE_APP_SHORTCUT) { + ash::LauncherID id = model_->items()[i].id; + if (HasItemController(id) && IsPinned(id)) { + base::DictionaryValue* app_value = ash::CreateAppDict( + id_to_item_controller_map_[id]->app_id()); + if (app_value) + updater->Append(app_value); + } + } else if (model_->items()[i].type == ash::TYPE_BROWSER_SHORTCUT) { + SetChromeIconIndexToPref(i); + } + } + } + pref_change_registrar_.Add( + prefs::kPinnedLauncherApps, + base::Bind(&ChromeLauncherControllerPerBrowser:: + UpdateAppLaunchersFromPref, + base::Unretained(this))); +} + +ash::LauncherModel* ChromeLauncherControllerPerBrowser::model() { + return model_; +} + +Profile* ChromeLauncherControllerPerBrowser::profile() { + return profile_; +} + +Profile* ChromeLauncherControllerPerBrowser::GetProfileForNewWindows() { + return ProfileManager::GetDefaultProfileOrOffTheRecord(); +} + +void ChromeLauncherControllerPerBrowser::LauncherItemClosed( + ash::LauncherID id) { + IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); + DCHECK(iter != id_to_item_controller_map_.end()); + app_icon_loader_->ClearImage(iter->second->app_id()); + iter->second->OnRemoved(); + id_to_item_controller_map_.erase(iter); + model_->RemoveItemAt(model_->ItemIndexByID(id)); +} + +void ChromeLauncherControllerPerBrowser::DoPinAppWithID( + const std::string& app_id) { + // If there is an item, do nothing and return. + if (IsAppPinned(app_id)) + return; + + ash::LauncherID launcher_id = GetLauncherIDForAppID(app_id); + if (launcher_id) { + // App item exists, pin it + Pin(launcher_id); + } else { + // Otherwise, create a shortcut item for it. + CreateAppShortcutLauncherItem(app_id, model_->item_count()); + if (CanPin()) + PersistPinnedState(); + } +} + +void ChromeLauncherControllerPerBrowser::DoUnpinAppsWithID( + const std::string& app_id) { + for (IDToItemControllerMap::iterator i = id_to_item_controller_map_.begin(); + i != id_to_item_controller_map_.end(); ) { + IDToItemControllerMap::iterator current(i); + ++i; + if (current->second->app_id() == app_id && IsPinned(current->first)) + Unpin(current->first); + } +} + +void ChromeLauncherControllerPerBrowser::UpdateAppLaunchersFromPref() { + // Construct a vector representation of to-be-pinned apps from the pref. + std::vector<std::string> pinned_apps; + int chrome_icon_index = GetChromeIconIndexFromPref(); + const base::ListValue* pinned_apps_pref = + profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); + for (base::ListValue::const_iterator it(pinned_apps_pref->begin()); + it != pinned_apps_pref->end(); ++it) { + // To preserve the Chrome icon position, we insert a dummy slot for it - if + // the model has a Chrome item. While initializing we can come here with no + // item in which case the count would be 1 or below. + if (it - pinned_apps_pref->begin() == chrome_icon_index && + model_->item_count() > 1) { + pinned_apps.push_back(extension_misc::kChromeAppId); + } + DictionaryValue* app = NULL; + std::string app_id; + if ((*it)->GetAsDictionary(&app) && + app->GetString(ash::kPinnedAppsPrefAppIDPath, &app_id) && + std::find(pinned_apps.begin(), pinned_apps.end(), app_id) == + pinned_apps.end() && + app_tab_helper_->IsValidID(app_id)) { + pinned_apps.push_back(app_id); + } + } + + // Walk the model and |pinned_apps| from the pref lockstep, adding and + // removing items as necessary. NB: This code uses plain old indexing instead + // of iterators because of model mutations as part of the loop. + std::vector<std::string>::const_iterator pref_app_id(pinned_apps.begin()); + int index = 0; + int max_index = model_->item_count(); + if (ash::switches::UseAlternateShelfLayout()) { + ++index; + ++max_index; + } + for (; index < model_->item_count() && pref_app_id != pinned_apps.end(); + ++index) { + // If the next app launcher according to the pref is present in the model, + // delete all app launcher entries in between. + if (*pref_app_id == extension_misc::kChromeAppId || + IsAppPinned(*pref_app_id)) { + for (; index < model_->item_count(); ++index) { + const ash::LauncherItem& item(model_->items()[index]); + if (item.type != ash::TYPE_APP_SHORTCUT && + item.type != ash::TYPE_BROWSER_SHORTCUT) + continue; + + IDToItemControllerMap::const_iterator entry = + id_to_item_controller_map_.find(item.id); + if ((extension_misc::kChromeAppId == *pref_app_id && + item.type == ash::TYPE_BROWSER_SHORTCUT) || + (entry != id_to_item_controller_map_.end() && + entry->second->app_id() == *pref_app_id)) { + ++pref_app_id; + break; + } else { + if (item.type == ash::TYPE_BROWSER_SHORTCUT) { + // We cannot delete the browser shortcut. As such we move it up by + // one. To avoid any side effects from our pinned state observer, we + // do not call the model directly. + MoveItemWithoutPinnedStateChangeNotification(index, index + 1); + } else { + LauncherItemClosed(item.id); + --max_index; + } + --index; + } + } + // If the item wasn't found, that means id_to_item_controller_map_ + // is out of sync. + DCHECK(index < model_->item_count()); + } else { + // This app wasn't pinned before, insert a new entry. + ash::LauncherID id = CreateAppShortcutLauncherItem(*pref_app_id, index); + index = model_->ItemIndexByID(id); + ++pref_app_id; + } + } + + // Remove any trailing existing items. + while (index < model_->item_count()) { + const ash::LauncherItem& item(model_->items()[index]); + if (item.type == ash::TYPE_APP_SHORTCUT) + LauncherItemClosed(item.id); + else + ++index; + } + + // Append unprocessed items from the pref to the end of the model. + for (; pref_app_id != pinned_apps.end(); ++pref_app_id) { + // Ignore the chrome icon. + if (*pref_app_id != extension_misc::kChromeAppId) + DoPinAppWithID(*pref_app_id); + } +} + +void ChromeLauncherControllerPerBrowser::SetShelfAutoHideBehaviorPrefs( + ash::ShelfAutoHideBehavior behavior, + aura::RootWindow* root_window) { + const char* value = NULL; + switch (behavior) { + case ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: + value = ash::kShelfAutoHideBehaviorAlways; + break; + case ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER: + value = ash::kShelfAutoHideBehaviorNever; + break; + case ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN: + // This one should not be a valid preference option for now. We only want + // to completely hide it when we run app mode. + NOTREACHED(); + return; + } + + UpdatePerDisplayPref( + profile_->GetPrefs(), root_window, prefs::kShelfAutoHideBehavior, value); + + if (root_window == ash::Shell::GetPrimaryRootWindow()) { + // See comment in |kShelfAlignment| about why we have two prefs here. + profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehaviorLocal, value); + profile_->GetPrefs()->SetString(prefs::kShelfAutoHideBehavior, value); + } +} + +void ChromeLauncherControllerPerBrowser::SetShelfAutoHideBehaviorFromPrefs() { + ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); + + for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin(); + iter != root_windows.end(); ++iter) { + ash::Shell::GetInstance()->SetShelfAutoHideBehavior( + GetShelfAutoHideBehavior(*iter), *iter); + } +} + +void ChromeLauncherControllerPerBrowser::SetShelfAlignmentFromPrefs() { + if (!ash::ShelfWidget::ShelfAlignmentAllowed()) + return; + + ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); + + for (ash::Shell::RootWindowList::const_iterator iter = root_windows.begin(); + iter != root_windows.end(); ++iter) { + // See comment in |kShelfAlignment| as to why we consider two prefs. + const std::string alignment_value( + GetPrefForRootWindow(profile_->GetPrefs(), + *iter, + prefs::kShelfAlignmentLocal, + prefs::kShelfAlignment)); + ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM; + if (alignment_value == ash::kShelfAlignmentLeft) + alignment = ash::SHELF_ALIGNMENT_LEFT; + else if (alignment_value == ash::kShelfAlignmentRight) + alignment = ash::SHELF_ALIGNMENT_RIGHT; + else if (alignment_value == ash::kShelfAlignmentTop) + alignment = ash::SHELF_ALIGNMENT_TOP; + ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter); + } +} + +void ChromeLauncherControllerPerBrowser::SetShelfBehaviorsFromPrefs() { + SetShelfAutoHideBehaviorFromPrefs(); + SetShelfAlignmentFromPrefs(); +} + +WebContents* ChromeLauncherControllerPerBrowser::GetLastActiveWebContents( + const std::string& app_id) { + AppIDToWebContentsListMap::const_iterator i = + app_id_to_web_contents_list_.find(app_id); + if (i == app_id_to_web_contents_list_.end()) + return NULL; + DCHECK_GT(i->second.size(), 0u); + return *i->second.begin(); +} + +ash::LauncherID ChromeLauncherControllerPerBrowser::InsertAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status, + int index) { + ash::LauncherID id = model_->next_id(); + DCHECK(!HasItemController(id)); + DCHECK(controller); + id_to_item_controller_map_[id] = controller; + controller->set_launcher_id(id); + + ash::LauncherItem item; + item.type = controller->GetLauncherItemType(); + item.is_incognito = false; + item.image = extensions::IconsInfo::GetDefaultAppIcon(); + + WebContents* active_tab = GetLastActiveWebContents(app_id); + if (active_tab) { + Browser* browser = chrome::FindBrowserWithWebContents(active_tab); + DCHECK(browser); + if (browser->window()->IsActive()) + status = ash::STATUS_ACTIVE; + else + status = ash::STATUS_RUNNING; + } + item.status = status; + + model_->AddAt(index, item); + + app_icon_loader_->FetchImage(app_id); + + return id; +} + +bool ChromeLauncherControllerPerBrowser::HasItemController( + ash::LauncherID id) const { + return id_to_item_controller_map_.find(id) != + id_to_item_controller_map_.end(); +} + +ash::LauncherID +ChromeLauncherControllerPerBrowser::CreateBrowserShortcutLauncherItem() { + ash::LauncherItem browser_shortcut; + browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; + browser_shortcut.is_incognito = false; + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32); + ash::LauncherID id = model_->next_id(); + size_t index = GetChromeIconIndexFromPref(); + model_->AddAt(index, browser_shortcut); + return id; +} + +void ChromeLauncherControllerPerBrowser::SetChromeIconIndexToPref(int index) { + profile_->GetPrefs()->SetInteger(prefs::kShelfChromeIconIndex, index); +} + +int ChromeLauncherControllerPerBrowser::GetChromeIconIndexFromPref() const { + size_t index = profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex); + const base::ListValue* pinned_apps_pref = + profile_->GetPrefs()->GetList(prefs::kPinnedLauncherApps); + if (ash::switches::UseAlternateShelfLayout()) + return std::max(static_cast<size_t>(1), + std::min(pinned_apps_pref->GetSize(), index)); + return std::max(static_cast<size_t>(0), + std::min(pinned_apps_pref->GetSize(), index)); +} + +ash::LauncherID +ChromeLauncherControllerPerBrowser::CreateAppShortcutLauncherItem( + const std::string& app_id, + int index) { + AppShortcutLauncherItemController* controller = + new AppShortcutLauncherItemController(app_id, this); + ash::LauncherID launcher_id = InsertAppLauncherItem( + controller, app_id, ash::STATUS_CLOSED, index); + return launcher_id; +} + +void ChromeLauncherControllerPerBrowser::SetAppTabHelperForTest( + AppTabHelper* helper) { + app_tab_helper_.reset(helper); +} + +void ChromeLauncherControllerPerBrowser::SetAppIconLoaderForTest( + extensions::AppIconLoader* loader) { + app_icon_loader_.reset(loader); +} + +const std::string& +ChromeLauncherControllerPerBrowser::GetAppIdFromLauncherIdForTest( + ash::LauncherID id) { + return id_to_item_controller_map_[id]->app_id(); +} + +void ChromeLauncherControllerPerBrowser:: + MoveItemWithoutPinnedStateChangeNotification(int source_index, + int target_index) { + base::AutoReset<bool> auto_reset(&ignore_persist_pinned_state_change_, true); + model_->Move(source_index, target_index); +} diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h new file mode 100644 index 0000000..0204501 --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h @@ -0,0 +1,430 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_BROWSER_H_ +#define CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_BROWSER_H_ + +#include <list> +#include <map> +#include <string> + +#include "ash/display/display_controller.h" +#include "ash/launcher/launcher_delegate.h" +#include "ash/launcher/launcher_model_observer.h" +#include "ash/launcher/launcher_types.h" +#include "ash/shelf/shelf_layout_manager_observer.h" +#include "ash/shelf/shelf_types.h" +#include "ash/shell_observer.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/prefs/pref_change_registrar.h" +#include "chrome/browser/extensions/extension_prefs.h" +#include "chrome/browser/prefs/pref_service_syncable_observer.h" +#include "chrome/browser/ui/ash/app_sync_ui_state_observer.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "ui/aura/window_observer.h" + +class AppSyncUIState; +class Browser; +class BrowserLauncherItemControllerTest; +class ExtensionEnableFlow; +class LauncherItemController; +class Profile; +class ShellWindowLauncherController; + +namespace ash { +class LauncherModel; +} + +namespace aura { +class Window; +} + +namespace content { +class WebContents; +} + +namespace ui { +class BaseWindow; +} + +// ChromeLauncherControllerPerBrowser manages the launcher items needed for +// content windows. Launcher items have a type, an optional app id, and a +// controller. This incarnation manages the items on a per browser base using +// browser proxies and application icons. +// * Tabbed browsers and browser app windows have BrowserLauncherItemController, +// owned by the BrowserView instance. +// * App shell windows have ShellWindowLauncherItemController, owned by +// ShellWindowLauncherController. +// * Shortcuts have no LauncherItemController. +class ChromeLauncherControllerPerBrowser + : public ash::LauncherModelObserver, + public ash::ShellObserver, + public ash::DisplayController::Observer, + public ChromeLauncherController, + public content::NotificationObserver, + public PrefServiceSyncableObserver, + public AppSyncUIStateObserver, + public ExtensionEnableFlowDelegate, + public ash::ShelfLayoutManagerObserver { + public: + ChromeLauncherControllerPerBrowser(Profile* profile, + ash::LauncherModel* model); + virtual ~ChromeLauncherControllerPerBrowser(); + + // ChromeLauncherController overrides: + + // Initializes this ChromeLauncherControllerPerBrowser. + virtual void Init() OVERRIDE; + + // Returns the new per application interface of the given launcher. If it is + // a per browser (old) controller, it will return NULL; + // TODO(skuhne): Remove when we rip out the old launcher. + virtual ChromeLauncherControllerPerApp* GetPerAppInterface() OVERRIDE; + + // Creates a new tabbed item on the launcher for |controller|. + virtual ash::LauncherID CreateTabbedLauncherItem( + LauncherItemController* controller, + IncognitoState is_incognito, + ash::LauncherItemStatus status) OVERRIDE; + + // Creates a new app item on the launcher for |controller|. + virtual ash::LauncherID CreateAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status) OVERRIDE; + + // Updates the running status of an item. + virtual void SetItemStatus(ash::LauncherID id, + ash::LauncherItemStatus status) OVERRIDE; + + // Updates the controller associated with id (which should be a shortcut). + // |controller| remains owned by caller. + virtual void SetItemController(ash::LauncherID id, + LauncherItemController* controller) OVERRIDE; + + // Closes or unpins the launcher item. + virtual void CloseLauncherItem(ash::LauncherID id) OVERRIDE; + + // Pins the specified id. Currently only supports platform apps. + virtual void Pin(ash::LauncherID id) OVERRIDE; + + // Unpins the specified id, closing if not running. + virtual void Unpin(ash::LauncherID id) OVERRIDE; + + // Returns true if the item identified by |id| is pinned. + virtual bool IsPinned(ash::LauncherID id) OVERRIDE; + + // Pins/unpins the specified id. + virtual void TogglePinned(ash::LauncherID id) OVERRIDE; + + // Returns true if the specified item can be pinned or unpinned. Only apps can + // be pinned. + virtual bool IsPinnable(ash::LauncherID id) const OVERRIDE; + + // If there is no launcher item in the launcher for application |app_id|, one + // gets created. The (existing or created) launcher items get then locked + // against a users un-pinning removal. + virtual void LockV1AppWithID(const std::string& app_id) OVERRIDE; + + // A previously locked launcher item of type |app_id| gets unlocked. If the + // lock count reaches 0 and the item is not pinned it will go away. + virtual void UnlockV1AppWithID(const std::string& app_id) OVERRIDE; + + // Requests that the launcher item controller specified by |id| open a new + // instance of the app. |event_flags| holds the flags of the event which + // triggered this command. + virtual void Launch(ash::LauncherID id, int event_flags) OVERRIDE; + + // Closes the specified item. + virtual void Close(ash::LauncherID id) OVERRIDE; + + // Returns true if the specified item is open. + virtual bool IsOpen(ash::LauncherID id) OVERRIDE; + + // Returns true if the specified item is for a platform app. + virtual bool IsPlatformApp(ash::LauncherID id) OVERRIDE; + + // Opens a new instance of the application identified by |app_id|. + // Used by the app-list, and by pinned-app launcher items. + virtual void LaunchApp(const std::string& app_id, int event_flags) OVERRIDE; + + // If |app_id| is running, reactivates the app's most recently active window, + // otherwise launches and activates the app. + // Used by the app-list, and by pinned-app launcher items. + virtual void ActivateApp(const std::string& app_id, int event_flags) OVERRIDE; + + // Returns the launch type of app for the specified id. + virtual extensions::ExtensionPrefs::LaunchType GetLaunchType( + ash::LauncherID id) OVERRIDE; + + // Returns the id of the app for the specified tab. + virtual std::string GetAppID(content::WebContents* tab) OVERRIDE; + + virtual ash::LauncherID GetLauncherIDForAppID( + const std::string& app_id) OVERRIDE; + virtual std::string GetAppIDForLauncherID(ash::LauncherID id) OVERRIDE; + + // Set the image for a specific launcher item (e.g. when set by the app). + virtual void SetLauncherItemImage(ash::LauncherID launcher_id, + const gfx::ImageSkia& image) OVERRIDE; + + // Returns true if a pinned launcher item with given |app_id| could be found. + virtual bool IsAppPinned(const std::string& app_id) OVERRIDE; + + // Pins an app with |app_id| to launcher. If there is a running instance in + // launcher, the running instance is pinned. If there is no running instance, + // a new launcher item is created and pinned. + virtual void PinAppWithID(const std::string& app_id) OVERRIDE; + + // Updates the launche type of the app for the specified id to |launch_type|. + virtual void SetLaunchType( + ash::LauncherID id, + extensions::ExtensionPrefs::LaunchType launch_type) OVERRIDE; + + // Unpins any app items whose id is |app_id|. + virtual void UnpinAppsWithID(const std::string& app_id) OVERRIDE; + + // Returns true if the user is currently logged in as a guest. + virtual bool IsLoggedInAsGuest() OVERRIDE; + + // Invoked when user clicks on button in the launcher and there is no last + // used window (or CTRL is held with the click). + virtual void CreateNewWindow() OVERRIDE; + + // Invoked when the user clicks on button in the launcher to create a new + // incognito window. + virtual void CreateNewIncognitoWindow() OVERRIDE; + + // Checks whether the user is allowed to pin apps. Pinning may be disallowed + // by policy in case there is a pre-defined set of pinned apps. + virtual bool CanPin() const OVERRIDE; + + // Updates the pinned pref state. The pinned state consists of a list pref. + // Each item of the list is a dictionary. The key |kAppIDPath| gives the + // id of the app. + virtual void PersistPinnedState() OVERRIDE; + + virtual ash::LauncherModel* model() OVERRIDE; + + virtual Profile* profile() OVERRIDE; + + // Gets the shelf auto-hide behavior on |root_window|. + virtual ash::ShelfAutoHideBehavior GetShelfAutoHideBehavior( + aura::RootWindow* root_window) const OVERRIDE; + + // Returns |true| if the user is allowed to modify the shelf auto-hide + // behavior on |root_window|. + virtual bool CanUserModifyShelfAutoHideBehavior( + aura::RootWindow* root_window) const OVERRIDE; + + // Toggles the shelf auto-hide behavior on |root_window|. Does nothing if the + // user is not allowed to modify the auto-hide behavior. + virtual void ToggleShelfAutoHideBehavior( + aura::RootWindow* root_window) OVERRIDE; + + // The tab no longer represents its previously identified application. + virtual void RemoveTabFromRunningApp(content::WebContents* tab, + const std::string& app_id) OVERRIDE; + + // Notify the controller that the state of an non platform app's tabs + // have changed, + virtual void UpdateAppState(content::WebContents* contents, + AppState app_state) OVERRIDE; + + // Limits application refocusing to urls that match |url| for |id|. + virtual void SetRefocusURLPatternForTest(ash::LauncherID id, + const GURL& url) OVERRIDE; + + // Returns the extension identified by |app_id|. + virtual const extensions::Extension* GetExtensionForAppID( + const std::string& app_id) const OVERRIDE; + + // Activates a |window|. If |allow_minimize| is true and the system allows + // it, the the window will get minimized instead. + virtual void ActivateWindowOrMinimizeIfActive(ui::BaseWindow* window, + bool allow_minimize) OVERRIDE; + + // ash::LauncherDelegate overrides: + virtual void ItemSelected(const ash::LauncherItem& item, + const ui::Event& event) OVERRIDE; + virtual string16 GetTitle(const ash::LauncherItem& item) OVERRIDE; + virtual ui::MenuModel* CreateContextMenu( + const ash::LauncherItem& item, aura::RootWindow* root) OVERRIDE; + virtual ash::LauncherMenuModel* CreateApplicationMenu( + const ash::LauncherItem& item, + int event_flags) OVERRIDE; + virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE; + virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE; + virtual bool ShouldShowTooltip(const ash::LauncherItem& item) OVERRIDE; + virtual void OnLauncherCreated(ash::Launcher* launcher) OVERRIDE; + virtual void OnLauncherDestroyed(ash::Launcher* launcher) OVERRIDE; + + // ash::LauncherModelObserver overrides: + virtual void LauncherItemAdded(int index) OVERRIDE; + virtual void LauncherItemRemoved(int index, ash::LauncherID id) OVERRIDE; + virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE; + virtual void LauncherItemChanged(int index, + const ash::LauncherItem& old_item) OVERRIDE; + virtual void LauncherStatusChanged() OVERRIDE; + + // content::NotificationObserver overrides: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // ash::ShellObserver overrides: + virtual void OnShelfAlignmentChanged(aura::RootWindow* root_window) OVERRIDE; + + // ash::DisplayController::Observer overrides: + virtual void OnDisplayConfigurationChanging() OVERRIDE; + virtual void OnDisplayConfigurationChanged() OVERRIDE; + + // PrefServiceSyncableObserver overrides: + virtual void OnIsSyncingChanged() OVERRIDE; + + // AppSyncUIStateObserver overrides: + virtual void OnAppSyncUIStatusChanged() OVERRIDE; + + // ExtensionEnableFlowDelegate overrides: + virtual void ExtensionEnableFlowFinished() OVERRIDE; + virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE; + + // extensions::AppIconLoader overrides: + virtual void SetAppImage(const std::string& app_id, + const gfx::ImageSkia& image) OVERRIDE; + + // ash::ShelfLayoutManagerObserver overrides: + virtual void OnAutoHideBehaviorChanged( + aura::RootWindow* root_window, + ash::ShelfAutoHideBehavior new_behavior) OVERRIDE; + + protected: + // ChromeLauncherController overrides: + + // Creates a new app shortcut item and controller on the launcher at |index|. + // Use kInsertItemAtEnd to add a shortcut as the last item. + virtual ash::LauncherID CreateAppShortcutLauncherItem( + const std::string& app_id, + int index) OVERRIDE; + + // Sets the AppTabHelper/AppIconLoader, taking ownership of the helper class. + // These are intended for testing. + virtual void SetAppTabHelperForTest(AppTabHelper* helper) OVERRIDE; + virtual void SetAppIconLoaderForTest( + extensions::AppIconLoader* loader) OVERRIDE; + virtual const std::string& GetAppIdFromLauncherIdForTest( + ash::LauncherID id) OVERRIDE; + + private: + friend class ChromeLauncherControllerPerBrowserTest; + + typedef std::map<ash::LauncherID, LauncherItemController*> + IDToItemControllerMap; + typedef std::list<content::WebContents*> WebContentsList; + typedef std::map<std::string, WebContentsList> AppIDToWebContentsListMap; + typedef std::map<content::WebContents*, std::string> WebContentsToAppIDMap; + + // Returns the profile used for new windows. + Profile* GetProfileForNewWindows(); + + // Invoked when the associated browser or app is closed. + void LauncherItemClosed(ash::LauncherID id); + + // Internal helpers for pinning and unpinning that handle both + // client-triggered and internal pinning operations. + void DoPinAppWithID(const std::string& app_id); + void DoUnpinAppsWithID(const std::string& app_id); + + // Re-syncs launcher model with prefs::kPinnedLauncherApps. + void UpdateAppLaunchersFromPref(); + + // Persists the shelf auto-hide behavior to prefs. + void SetShelfAutoHideBehaviorPrefs(ash::ShelfAutoHideBehavior behavior, + aura::RootWindow* root_window); + + // Sets the shelf auto-hide behavior from prefs. + void SetShelfAutoHideBehaviorFromPrefs(); + + // Sets the shelf alignment from prefs. + void SetShelfAlignmentFromPrefs(); + + // Sets both of auto-hide behavior and alignment from prefs. + void SetShelfBehaviorsFromPrefs(); + + // Returns the most recently active WebContents for an app. + content::WebContents* GetLastActiveWebContents(const std::string& app_id); + + // Creates an app launcher to insert at |index|. Note that |index| may be + // adjusted by the model to meet ordering constraints. + ash::LauncherID InsertAppLauncherItem( + LauncherItemController* controller, + const std::string& app_id, + ash::LauncherItemStatus status, + int index); + + bool HasItemController(ash::LauncherID id) const; + + // Create LauncherItem for Browser Shortcut. + ash::LauncherID CreateBrowserShortcutLauncherItem(); + + // Update browser shortcut's index. + void SetChromeIconIndexToPref(int index); + + // Get browser shortcut's index from pref. + int GetChromeIconIndexFromPref() const; + + // Invoked when browser shortcut is clicked. + void BrowserShortcutClicked(int event_flags); + + // Move an item internally (ignoring pinned state changes) from |index| to + // |target_index|. + void MoveItemWithoutPinnedStateChangeNotification(int source_index, + int target_index); + + ash::LauncherModel* model_; + + // Profile used for prefs and loading extensions. This is NOT necessarily the + // profile new windows are created with. + Profile* profile_; + + IDToItemControllerMap id_to_item_controller_map_; + + // Maintains activation order of web contents for each app. + AppIDToWebContentsListMap app_id_to_web_contents_list_; + + // Direct access to app_id for a web contents. + WebContentsToAppIDMap web_contents_to_app_id_; + + // Used to track shell windows. + scoped_ptr<ShellWindowLauncherController> shell_window_controller_; + + // Used to get app info for tabs. + scoped_ptr<AppTabHelper> app_tab_helper_; + + // Used to load the image for an app item. + scoped_ptr<extensions::AppIconLoader> app_icon_loader_; + + content::NotificationRegistrar notification_registrar_; + + PrefChangeRegistrar pref_change_registrar_; + + AppSyncUIState* app_sync_ui_state_; + + scoped_ptr<ExtensionEnableFlow> extension_enable_flow_; + + // Launchers that are currently being observed. + std::set<ash::Launcher*> launchers_; + + // If true, incoming pinned state changes should be ignored. + bool ignore_persist_pinned_state_change_; + + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerBrowser); +}; + +#endif // CHROME_BROWSER_UI_ASH_LAUNCHER_CHROME_LAUNCHER_CONTROLLER_PER_BROWSER_H_ diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc new file mode 100644 index 0000000..f91430b --- /dev/null +++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc @@ -0,0 +1,462 @@ +// 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/ash/launcher/chrome_launcher_controller_per_browser.h" + +#include <algorithm> +#include <string> +#include <vector> + +#include "ash/launcher/launcher_model.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/values.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/test_extension_system.h" +#include "chrome/browser/ui/ash/chrome_launcher_prefs.h" +#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/testing_pref_service_syncable.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/chromeos/settings/device_settings_service.h" +#endif + +using extensions::Extension; +using extensions::Manifest; + +class ChromeLauncherControllerPerBrowserTest : public testing::Test { + protected: + ChromeLauncherControllerPerBrowserTest() + : profile_(new TestingProfile()), + extension_service_(NULL) { + DictionaryValue manifest; + manifest.SetString("name", "launcher controller test extension"); + manifest.SetString("version", "1"); + manifest.SetString("description", "for testing pinned apps"); + + extensions::TestExtensionSystem* extension_system( + static_cast<extensions::TestExtensionSystem*>( + extensions::ExtensionSystem::Get(profile_.get()))); + extension_service_ = extension_system->CreateExtensionService( + CommandLine::ForCurrentProcess(), base::FilePath(), false); + + std::string error; + extension1_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, + manifest, + Extension::NO_FLAGS, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + &error); + extension2_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, + manifest, + Extension::NO_FLAGS, + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + &error); + // Fake gmail extension. + extension3_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, + manifest, + Extension::NO_FLAGS, + extension_misc::kGmailAppId, + &error); + // Fake search extension. + extension4_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, + manifest, + Extension::NO_FLAGS, + extension_misc::kGoogleSearchAppId, + &error); + } + + virtual void TearDown() OVERRIDE { + profile_.reset(); + // Execute any pending deletion tasks. + base::RunLoop().RunUntilIdle(); + } + + void InsertPrefValue(base::ListValue* pref_value, + int index, + const std::string& extension_id) { + base::DictionaryValue* entry = new DictionaryValue(); + entry->SetString(ash::kPinnedAppsPrefAppIDPath, extension_id); + pref_value->Insert(index, entry); + } + + // Gets the currently configured app launchers from the controller. + void GetAppLaunchers(ChromeLauncherControllerPerBrowser* controller, + std::vector<std::string>* launchers) { + launchers->clear(); + for (ash::LauncherItems::const_iterator iter(model_.items().begin()); + iter != model_.items().end(); ++iter) { + ChromeLauncherControllerPerBrowser::IDToItemControllerMap::const_iterator + entry(controller->id_to_item_controller_map_.find(iter->id)); + if (iter->type == ash::TYPE_APP_SHORTCUT && + entry != controller->id_to_item_controller_map_.end()) { + launchers->push_back(entry->second->app_id()); + } + } + } + + std::string GetPinnedAppStatus( + ChromeLauncherController* launcher_controller) { + std::string result; + for (int i = 0; i < model_.item_count(); i++) { + switch (model_.items()[i].type) { + case ash::TYPE_APP_SHORTCUT: { + const std::string& app = + launcher_controller->GetAppIDForLauncherID( + model_.items()[i].id); + if (app == extension1_->id()) { + result += "App1, "; + EXPECT_TRUE(launcher_controller->IsAppPinned(extension1_->id())); + } else if (app == extension2_->id()) { + result += "App2, "; + EXPECT_TRUE(launcher_controller->IsAppPinned(extension2_->id())); + } else if (app == extension3_->id()) { + result += "App3, "; + EXPECT_TRUE(launcher_controller->IsAppPinned(extension3_->id())); + } else { + result += "unknown"; + } + break; + } + case ash::TYPE_BROWSER_SHORTCUT: + result += "Chrome, "; + break; + case ash::TYPE_APP_LIST: + result += "AppList"; + break; + default: + result += "Unknown"; + break; + } + } + return result; + } + + // Needed for extension service & friends to work. + content::TestBrowserThreadBundle thread_bundle_; + +#if defined OS_CHROMEOS + chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; + chromeos::ScopedTestCrosSettings test_cros_settings_; + chromeos::ScopedTestUserManager test_user_manager_; +#endif + + scoped_refptr<Extension> extension1_; + scoped_refptr<Extension> extension2_; + scoped_refptr<Extension> extension3_; + scoped_refptr<Extension> extension4_; + scoped_ptr<TestingProfile> profile_; + ash::LauncherModel model_; + + ExtensionService* extension_service_; + + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerBrowserTest); +}; + +TEST_F(ChromeLauncherControllerPerBrowserTest, DefaultApps) { + ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), + &model_); + launcher_controller.Init(); + + // Model should only contain the browser shortcut and app list items. + EXPECT_EQ(2, model_.item_count()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + + // Installing |extension3_| should add it to the launcher - behind the + // chrome icon. + extension_service_->AddExtension(extension3_.get()); + EXPECT_EQ("Chrome, App3, AppList", GetPinnedAppStatus(&launcher_controller)); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); +} + +// Check that the restauration of launcher items is happening in the same order +// as the user has pinned them (on another system) when they are synced reverse +// order. +TEST_F(ChromeLauncherControllerPerBrowserTest, RestoreDefaultAppsReverseOrder) { + ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), + &model_); + launcher_controller.Init(); + + base::ListValue policy_value; + InsertPrefValue(&policy_value, 0, extension1_->id()); + InsertPrefValue(&policy_value, 1, extension2_->id()); + InsertPrefValue(&policy_value, 2, extension3_->id()); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + policy_value.DeepCopy()); + EXPECT_EQ(0, profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex)); + // Model should only contain the browser shortcut and app list items. + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_EQ("Chrome, AppList", GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension3_| should add it to the launcher - behind the + // chrome icon. + ash::LauncherItem item; + extension_service_->AddExtension(extension3_.get()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_EQ("Chrome, App3, AppList", GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension2_| should add it to the launcher - behind the + // chrome icon, but in first location. + extension_service_->AddExtension(extension2_.get()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_EQ("Chrome, App2, App3, AppList", + GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension1_| should add it to the launcher - behind the + // chrome icon, but in first location. + extension_service_->AddExtension(extension1_.get()); + EXPECT_EQ("Chrome, App1, App2, App3, AppList", + GetPinnedAppStatus(&launcher_controller)); +} + +// Check that the restauration of launcher items is happening in the same order +// as the user has pinned them (on another system) when they are synced random +// order. +TEST_F(ChromeLauncherControllerPerBrowserTest, RestoreDefaultAppsRandomOrder) { + ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), + &model_); + launcher_controller.Init(); + + base::ListValue policy_value; + InsertPrefValue(&policy_value, 0, extension1_->id()); + InsertPrefValue(&policy_value, 1, extension2_->id()); + InsertPrefValue(&policy_value, 2, extension3_->id()); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + policy_value.DeepCopy()); + EXPECT_EQ(0, profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex)); + // Model should only contain the browser shortcut and app list items. + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_EQ("Chrome, AppList", GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension2_| should add it to the launcher - behind the + // chrome icon. + extension_service_->AddExtension(extension2_.get()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_EQ("Chrome, App2, AppList", GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension1_| should add it to the launcher - behind the + // chrome icon, but in first location. + extension_service_->AddExtension(extension1_.get()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_EQ("Chrome, App1, App2, AppList", + GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension3_| should add it to the launcher - behind the + // chrome icon, but in first location. + extension_service_->AddExtension(extension3_.get()); + EXPECT_EQ("Chrome, App1, App2, App3, AppList", + GetPinnedAppStatus(&launcher_controller)); +} + +// Check that the restauration of launcher items is happening in the same order +// as the user has pinned / moved them (on another system) when they are synced +// random order - including the chrome icon. +TEST_F(ChromeLauncherControllerPerBrowserTest, + RestoreDefaultAppsRandomOrderChromeMoved) { + ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), + &model_); + launcher_controller.Init(); + base::ListValue policy_value; + InsertPrefValue(&policy_value, 0, extension1_->id()); + InsertPrefValue(&policy_value, 1, extension2_->id()); + InsertPrefValue(&policy_value, 2, extension3_->id()); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + policy_value.DeepCopy()); + profile_->GetTestingPrefService()->SetInteger(prefs::kShelfChromeIconIndex, + 1); + // Model should only contain the browser shortcut and app list items. + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_EQ("Chrome, AppList", GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension2_| should add it to the launcher - behind the + // chrome icon. + ash::LauncherItem item; + extension_service_->AddExtension(extension2_.get()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_EQ("Chrome, App2, AppList", GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension1_| should add it to the launcher - behind the + // chrome icon, but in first location. + extension_service_->AddExtension(extension1_.get()); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_EQ("App1, Chrome, App2, AppList", + GetPinnedAppStatus(&launcher_controller)); + + // Installing |extension3_| should add it to the launcher - behind the + // chrome icon, but in first location. + extension_service_->AddExtension(extension3_.get()); + EXPECT_EQ("App1, Chrome, App2, App3, AppList", + GetPinnedAppStatus(&launcher_controller)); +} + +TEST_F(ChromeLauncherControllerPerBrowserTest, Policy) { + extension_service_->AddExtension(extension1_.get()); + extension_service_->AddExtension(extension3_.get()); + + base::ListValue policy_value; + InsertPrefValue(&policy_value, 0, extension1_->id()); + InsertPrefValue(&policy_value, 1, extension2_->id()); + profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, + policy_value.DeepCopy()); + + // Only |extension1_| should get pinned. |extension2_| is specified but not + // installed, and |extension3_| is part of the default set, but that shouldn't + // take effect when the policy override is in place. + ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), + &model_); + launcher_controller.Init(); + EXPECT_EQ(3, model_.item_count()); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); + EXPECT_TRUE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_.items()[0].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); + EXPECT_EQ(ash::TYPE_APP_LIST, model_.items()[2].type); + + // Installing |extension2_| should add it to the launcher. + extension_service_->AddExtension(extension2_.get()); + EXPECT_EQ(4, model_.item_count()); + EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_.items()[0].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[2].type); + EXPECT_EQ(ash::TYPE_APP_LIST, model_.items()[3].type); + EXPECT_TRUE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_TRUE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + + // Removing |extension1_| from the policy should be reflected in the launcher. + policy_value.Remove(0, NULL); + profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, + policy_value.DeepCopy()); + EXPECT_EQ(3, model_.item_count()); + EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); + EXPECT_TRUE(launcher_controller.IsAppPinned(extension2_->id())); + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); +} + +TEST_F(ChromeLauncherControllerPerBrowserTest, UnpinWithUninstall) { + extension_service_->AddExtension(extension3_.get()); + extension_service_->AddExtension(extension4_.get()); + + ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), + &model_); + launcher_controller.Init(); + + EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_TRUE(launcher_controller.IsAppPinned(extension4_->id())); + + extension_service_->UnloadExtension(extension3_->id(), + extension_misc::UNLOAD_REASON_UNINSTALL); + + EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); + EXPECT_TRUE(launcher_controller.IsAppPinned(extension4_->id())); +} + +TEST_F(ChromeLauncherControllerPerBrowserTest, PrefUpdates) { + extension_service_->AddExtension(extension2_.get()); + extension_service_->AddExtension(extension3_.get()); + extension_service_->AddExtension(extension4_.get()); + ChromeLauncherControllerPerBrowser controller(profile_.get(), &model_); + controller.Init(); + + std::vector<std::string> expected_launchers; + std::vector<std::string> actual_launchers; + base::ListValue pref_value; + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + pref_value.DeepCopy()); + GetAppLaunchers(&controller, &actual_launchers); + EXPECT_EQ(expected_launchers, actual_launchers); + + // Unavailable extensions don't create launcher items. + InsertPrefValue(&pref_value, 0, extension1_->id()); + InsertPrefValue(&pref_value, 1, extension2_->id()); + InsertPrefValue(&pref_value, 2, extension4_->id()); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + pref_value.DeepCopy()); + expected_launchers.push_back(extension2_->id()); + expected_launchers.push_back(extension4_->id()); + GetAppLaunchers(&controller, &actual_launchers); + EXPECT_EQ(expected_launchers, actual_launchers); + + // Redundant pref entries show up only once. + InsertPrefValue(&pref_value, 2, extension3_->id()); + InsertPrefValue(&pref_value, 2, extension3_->id()); + InsertPrefValue(&pref_value, 5, extension3_->id()); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + pref_value.DeepCopy()); + expected_launchers.insert(expected_launchers.begin() + 1, extension3_->id()); + GetAppLaunchers(&controller, &actual_launchers); + EXPECT_EQ(expected_launchers, actual_launchers); + + // Order changes are reflected correctly. + pref_value.Clear(); + InsertPrefValue(&pref_value, 0, extension4_->id()); + InsertPrefValue(&pref_value, 1, extension3_->id()); + InsertPrefValue(&pref_value, 2, extension2_->id()); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + pref_value.DeepCopy()); + std::reverse(expected_launchers.begin(), expected_launchers.end()); + GetAppLaunchers(&controller, &actual_launchers); + EXPECT_EQ(expected_launchers, actual_launchers); + + // Clearing works. + pref_value.Clear(); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + pref_value.DeepCopy()); + expected_launchers.clear(); + GetAppLaunchers(&controller, &actual_launchers); + EXPECT_EQ(expected_launchers, actual_launchers); +} + +TEST_F(ChromeLauncherControllerPerBrowserTest, PendingInsertionOrder) { + extension_service_->AddExtension(extension1_.get()); + extension_service_->AddExtension(extension3_.get()); + ChromeLauncherControllerPerBrowser controller(profile_.get(), &model_); + controller.Init(); + + base::ListValue pref_value; + InsertPrefValue(&pref_value, 0, extension1_->id()); + InsertPrefValue(&pref_value, 1, extension2_->id()); + InsertPrefValue(&pref_value, 2, extension3_->id()); + profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, + pref_value.DeepCopy()); + + std::vector<std::string> expected_launchers; + expected_launchers.push_back(extension1_->id()); + expected_launchers.push_back(extension3_->id()); + std::vector<std::string> actual_launchers; + + GetAppLaunchers(&controller, &actual_launchers); + EXPECT_EQ(expected_launchers, actual_launchers); + + // Install |extension2| and verify it shows up between the other two. + extension_service_->AddExtension(extension2_.get()); + expected_launchers.insert(expected_launchers.begin() + 1, extension2_->id()); + GetAppLaunchers(&controller, &actual_launchers); + EXPECT_EQ(expected_launchers, actual_launchers); +} diff --git a/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.cc b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.cc index b380e97..61c9881 100644 --- a/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.cc +++ b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.cc @@ -4,7 +4,7 @@ #include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h" -#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" LauncherApplicationMenuItemModel::LauncherApplicationMenuItemModel( ChromeLauncherAppMenuItems item_list) diff --git a/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h index 46a483f..48816f4 100644 --- a/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h +++ b/chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h @@ -7,12 +7,7 @@ #include "ash/launcher/launcher_delegate.h" #include "base/memory/scoped_ptr.h" -#include "base/memory/scoped_vector.h" - -class ChromeLauncherAppMenuItem; - -// A list of the elements which makes up a simple menu description. -typedef ScopedVector<ChromeLauncherAppMenuItem> ChromeLauncherAppMenuItems; +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" // A menu model that builds the contents of a menu for a launcher item // containing a list of running applications. diff --git a/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc b/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc index 96bb851..dbcf723 100644 --- a/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc +++ b/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc @@ -13,18 +13,21 @@ #include "chrome/browser/prefs/incognito_mode_prefs.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h" #include "chrome/test/base/testing_profile.h" #include "ui/aura/root_window.h" -class TestChromeLauncherController : public ChromeLauncherController { +class TestChromeLauncherControllerPerBrowser : + public ChromeLauncherControllerPerBrowser { public: - TestChromeLauncherController(Profile* profile, ash::LauncherModel* model) - : ChromeLauncherController(profile, model) {} + TestChromeLauncherControllerPerBrowser( + Profile* profile, ash::LauncherModel* model) + : ChromeLauncherControllerPerBrowser(profile, model) {} virtual bool IsLoggedInAsGuest() OVERRIDE { return false; } private: - DISALLOW_COPY_AND_ASSIGN(TestChromeLauncherController); + DISALLOW_COPY_AND_ASSIGN(TestChromeLauncherControllerPerBrowser); }; class LauncherContextMenuTest : public ash::test::AshTestBase { @@ -40,7 +43,8 @@ class LauncherContextMenuTest : public ash::test::AshTestBase { virtual void SetUp() OVERRIDE { ash::test::AshTestBase::SetUp(); controller_.reset( - new TestChromeLauncherController(profile(), &launcher_model_)); + new TestChromeLauncherControllerPerBrowser(profile(), + &launcher_model_)); } virtual void TearDown() OVERRIDE { diff --git a/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.cc index 0a52893..b4d562d85 100644 --- a/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.cc +++ b/chrome/browser/ui/ash/launcher/shell_window_launcher_item_controller.cc @@ -11,6 +11,7 @@ #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" +#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h" #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" #include "content/public/browser/web_contents.h" #include "ui/aura/client/aura_constants.h" @@ -154,7 +155,8 @@ void ShellWindowLauncherItemController::Clicked(const ui::Event& event) { } else { ShowAndActivateOrMinimize(panel); } - } else { + } else if (launcher_controller()->GetPerAppInterface() || + shell_windows_.size() == 1) { ShellWindow* window_to_show = last_active_shell_window_ ? last_active_shell_window_ : shell_windows_.front(); // If the event was triggered by a keystroke, we try to advance to the next @@ -166,6 +168,20 @@ void ShellWindowLauncherItemController::Clicked(const ui::Event& event) { } else { ShowAndActivateOrMinimize(window_to_show); } + } else { + // TODO(stevenjb): Deprecate + if (!last_active_shell_window_ || + last_active_shell_window_->GetBaseWindow()->IsActive()) { + // Restore all windows since there is no other way to restore them. + for (ShellWindowList::iterator iter = shell_windows_.begin(); + iter != shell_windows_.end(); ++iter) { + ShellWindow* shell_window = *iter; + if (shell_window->GetBaseWindow()->IsMinimized()) + shell_window->GetBaseWindow()->Restore(); + } + } + if (last_active_shell_window_) + ShowAndActivateOrMinimize(last_active_shell_window_); } } @@ -190,7 +206,7 @@ ShellWindowLauncherItemController::GetApplicationList(int event_flags) { shell_window->GetTitle(), image.get(), // Will be copied app_id(), - launcher_controller(), + launcher_controller()->GetPerAppInterface(), index, index == 0 /* has_leading_separator */)); ++index; diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 0d1a1a8..d7dc080 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -236,6 +236,10 @@ 'browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h', 'browser/ui/ash/launcher/chrome_launcher_controller.cc', 'browser/ui/ash/launcher/chrome_launcher_controller.h', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_app.cc', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_app.h', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_browser.cc', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_browser.h', 'browser/ui/ash/launcher/launcher_app_tab_helper.cc', 'browser/ui/ash/launcher/launcher_app_tab_helper.h', 'browser/ui/ash/launcher/launcher_context_menu.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index fab07e4..57cddd0 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1549,6 +1549,7 @@ 'browser/ui/ash/caps_lock_delegate_chromeos_browsertest.cc', 'browser/ui/ash/chrome_shell_delegate_browsertest.cc', 'browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_app_browsertest.cc', 'browser/ui/ash/launcher/launcher_favicon_loader_browsertest.cc', 'browser/ui/ash/shelf_browsertest.cc', 'browser/ui/ash/volume_controller_browsertest_chromeos.cc', @@ -1959,6 +1960,7 @@ 'browser/chromeos/system/tray_accessibility_browsertest.cc', 'browser/ui/ash/caps_lock_delegate_chromeos_browsertest.cc', 'browser/ui/ash/chrome_shell_delegate_browsertest.cc', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_app_browsertest.cc', 'browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc', 'browser/ui/ash/launcher/launcher_favicon_loader_browsertest.cc', 'browser/ui/ash/shelf_browsertest.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 4a2abcd..075c1ea 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1355,7 +1355,9 @@ 'browser/ui/android/tab_model/tab_model_unittest.cc', 'browser/ui/ash/event_rewriter_unittest.cc', 'browser/ui/ash/ime_controller_chromeos_unittest.cc', - 'browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc', + 'browser/ui/ash/launcher/browser_launcher_item_controller_unittest.cc', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_app_unittest.cc', + 'browser/ui/ash/launcher/chrome_launcher_controller_per_browser_unittest.cc', 'browser/ui/ash/launcher/launcher_context_menu_unittest.cc', 'browser/ui/ash/screenshot_taker_unittest.cc', 'browser/ui/ash/window_positioner_unittest.cc', |