Home | History | Annotate | Download | only in app_list
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/app_list/app_context_menu.h"
      6 
      7 #include "chrome/app/chrome_command_ids.h"
      8 #include "chrome/browser/extensions/context_menu_matcher.h"
      9 #include "chrome/browser/extensions/extension_service.h"
     10 #include "chrome/browser/extensions/extension_system.h"
     11 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
     12 #include "chrome/browser/extensions/management_policy.h"
     13 #include "chrome/browser/prefs/incognito_mode_prefs.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
     16 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
     17 #include "chrome/browser/ui/browser_navigator.h"
     18 #include "chrome/common/extensions/manifest_url_handler.h"
     19 #include "content/public/common/context_menu_params.h"
     20 #include "grit/chromium_strings.h"
     21 #include "grit/generated_resources.h"
     22 #include "ui/base/l10n/l10n_util.h"
     23 
     24 #if defined(USE_ASH)
     25 #include "ash/shell.h"
     26 #endif
     27 
     28 using extensions::Extension;
     29 
     30 namespace app_list {
     31 
     32 namespace {
     33 
     34 enum CommandId {
     35   LAUNCH_NEW = 100,
     36   TOGGLE_PIN,
     37   CREATE_SHORTCUTS,
     38   OPTIONS,
     39   UNINSTALL,
     40   DETAILS,
     41   MENU_NEW_WINDOW,
     42   MENU_NEW_INCOGNITO_WINDOW,
     43   // Order matters in LAUNCHER_TYPE_xxxx and must match LaunchType.
     44   LAUNCH_TYPE_START = 200,
     45   LAUNCH_TYPE_PINNED_TAB = LAUNCH_TYPE_START,
     46   LAUNCH_TYPE_REGULAR_TAB,
     47   LAUNCH_TYPE_FULLSCREEN,
     48   LAUNCH_TYPE_WINDOW,
     49   LAUNCH_TYPE_LAST,
     50 };
     51 
     52 // ExtensionUninstaller runs the extension uninstall flow. It shows the
     53 // extension uninstall dialog and wait for user to confirm or cancel the
     54 // uninstall.
     55 class ExtensionUninstaller : public ExtensionUninstallDialog::Delegate {
     56  public:
     57   ExtensionUninstaller(Profile* profile,
     58                        const std::string& extension_id,
     59                        AppListControllerDelegate* controller)
     60       : profile_(profile),
     61         app_id_(extension_id),
     62         controller_(controller) {
     63   }
     64 
     65   void Run() {
     66     const Extension* extension =
     67         extensions::ExtensionSystem::Get(profile_)->extension_service()->
     68             GetExtensionById(app_id_, true);
     69     if (!extension) {
     70       CleanUp();
     71       return;
     72     }
     73     controller_->OnShowExtensionPrompt();
     74     dialog_.reset(ExtensionUninstallDialog::Create(profile_, NULL, this));
     75     dialog_->ConfirmUninstall(extension);
     76   }
     77 
     78  private:
     79   // Overridden from ExtensionUninstallDialog::Delegate:
     80   virtual void ExtensionUninstallAccepted() OVERRIDE {
     81     ExtensionService* service =
     82         extensions::ExtensionSystem::Get(profile_)->extension_service();
     83     const Extension* extension = service->GetInstalledExtension(app_id_);
     84     if (extension) {
     85       service->UninstallExtension(app_id_,
     86                                   false, /* external_uninstall*/
     87                                   NULL);
     88     }
     89     controller_->OnCloseExtensionPrompt();
     90     CleanUp();
     91   }
     92 
     93   virtual void ExtensionUninstallCanceled() OVERRIDE {
     94     controller_->OnCloseExtensionPrompt();
     95     CleanUp();
     96   }
     97 
     98   void CleanUp() {
     99     delete this;
    100   }
    101 
    102   Profile* profile_;
    103   std::string app_id_;
    104   AppListControllerDelegate* controller_;
    105   scoped_ptr<ExtensionUninstallDialog> dialog_;
    106 
    107   DISALLOW_COPY_AND_ASSIGN(ExtensionUninstaller);
    108 };
    109 
    110 extensions::ExtensionPrefs::LaunchType GetExtensionLaunchType(
    111     Profile* profile,
    112     const Extension* extension) {
    113   ExtensionService* service =
    114       extensions::ExtensionSystem::Get(profile)->extension_service();
    115   return service->extension_prefs()->
    116       GetLaunchType(extension, extensions::ExtensionPrefs::LAUNCH_DEFAULT);
    117 }
    118 
    119 void SetExtensionLaunchType(
    120     Profile* profile,
    121     const std::string& extension_id,
    122     extensions::ExtensionPrefs::LaunchType launch_type) {
    123   ExtensionService* service =
    124       extensions::ExtensionSystem::Get(profile)->extension_service();
    125   service->extension_prefs()->SetLaunchType(extension_id, launch_type);
    126 }
    127 
    128 bool MenuItemHasLauncherContext(const extensions::MenuItem* item) {
    129   return item->contexts().Contains(extensions::MenuItem::LAUNCHER);
    130 }
    131 
    132 }  // namespace
    133 
    134 AppContextMenu::AppContextMenu(AppContextMenuDelegate* delegate,
    135                                Profile* profile,
    136                                const std::string& app_id,
    137                                AppListControllerDelegate* controller,
    138                                bool is_platform_app)
    139     : delegate_(delegate),
    140       profile_(profile),
    141       app_id_(app_id),
    142       controller_(controller),
    143       is_platform_app_(is_platform_app) {
    144 }
    145 
    146 AppContextMenu::~AppContextMenu() {}
    147 
    148 ui::MenuModel* AppContextMenu::GetMenuModel() {
    149   const Extension* extension = GetExtension();
    150   if (!extension)
    151     return NULL;
    152 
    153   if (menu_model_.get())
    154     return menu_model_.get();
    155 
    156   menu_model_.reset(new ui::SimpleMenuModel(this));
    157 
    158   if (app_id_ == extension_misc::kChromeAppId) {
    159     menu_model_->AddItemWithStringId(
    160         MENU_NEW_WINDOW,
    161         IDS_APP_LIST_NEW_WINDOW);
    162     if (!profile_->IsOffTheRecord()) {
    163       menu_model_->AddItemWithStringId(
    164           MENU_NEW_INCOGNITO_WINDOW,
    165           IDS_APP_LIST_NEW_INCOGNITO_WINDOW);
    166     }
    167   } else {
    168     extension_menu_items_.reset(new extensions::ContextMenuMatcher(
    169         profile_, this, menu_model_.get(),
    170         base::Bind(MenuItemHasLauncherContext)));
    171 
    172     if (!is_platform_app_)
    173       menu_model_->AddItem(LAUNCH_NEW, string16());
    174 
    175     int index = 0;
    176     extension_menu_items_->AppendExtensionItems(app_id_, string16(),
    177                                                 &index);
    178 
    179     if (controller_->CanPin()) {
    180       menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
    181       menu_model_->AddItemWithStringId(
    182           TOGGLE_PIN,
    183           controller_->IsAppPinned(app_id_) ?
    184               IDS_APP_LIST_CONTEXT_MENU_UNPIN :
    185               IDS_APP_LIST_CONTEXT_MENU_PIN);
    186     }
    187 
    188     if (controller_->CanDoCreateShortcutsFlow(is_platform_app_)) {
    189       menu_model_->AddItemWithStringId(CREATE_SHORTCUTS,
    190                                        IDS_NEW_TAB_APP_CREATE_SHORTCUT);
    191     }
    192 
    193     if (!is_platform_app_) {
    194       menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
    195       menu_model_->AddCheckItemWithStringId(
    196           LAUNCH_TYPE_REGULAR_TAB,
    197           IDS_APP_CONTEXT_MENU_OPEN_REGULAR);
    198       menu_model_->AddCheckItemWithStringId(
    199           LAUNCH_TYPE_PINNED_TAB,
    200           IDS_APP_CONTEXT_MENU_OPEN_PINNED);
    201 #if defined(USE_ASH)
    202       if (!ash::Shell::IsForcedMaximizeMode())
    203 #endif
    204       {
    205 #if defined(OS_MACOSX)
    206         // Mac does not support standalone web app browser windows or maximize.
    207         menu_model_->AddCheckItemWithStringId(
    208             LAUNCH_TYPE_FULLSCREEN,
    209             IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN);
    210 #else
    211         menu_model_->AddCheckItemWithStringId(
    212             LAUNCH_TYPE_WINDOW,
    213             IDS_APP_CONTEXT_MENU_OPEN_WINDOW);
    214         // Even though the launch type is Full Screen it is more accurately
    215         // described as Maximized in Ash.
    216         menu_model_->AddCheckItemWithStringId(
    217             LAUNCH_TYPE_FULLSCREEN,
    218             IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED);
    219 #endif
    220       }
    221       menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
    222       menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS);
    223     }
    224 
    225     menu_model_->AddItemWithStringId(DETAILS, IDS_NEW_TAB_APP_DETAILS);
    226     menu_model_->AddItemWithStringId(
    227         UNINSTALL,
    228         is_platform_app_ ? IDS_APP_LIST_UNINSTALL_ITEM
    229                          : IDS_EXTENSIONS_UNINSTALL);
    230   }
    231 
    232   return menu_model_.get();
    233 }
    234 
    235 const Extension* AppContextMenu::GetExtension() const {
    236   const ExtensionService* service =
    237       extensions::ExtensionSystem::Get(profile_)->extension_service();
    238   const Extension* extension = service->GetInstalledExtension(app_id_);
    239   return extension;
    240 }
    241 
    242 void AppContextMenu::ShowExtensionOptions() {
    243   const Extension* extension = GetExtension();
    244   if (!extension)
    245     return;
    246 
    247   chrome::NavigateParams params(
    248       profile_,
    249       extensions::ManifestURL::GetOptionsPage(extension),
    250       content::PAGE_TRANSITION_LINK);
    251   chrome::Navigate(&params);
    252 }
    253 
    254 void AppContextMenu::ShowExtensionDetails() {
    255   const Extension* extension = GetExtension();
    256   if (!extension)
    257     return;
    258 
    259   chrome::NavigateParams params(
    260       profile_,
    261       extensions::ManifestURL::GetDetailsURL(extension),
    262       content::PAGE_TRANSITION_LINK);
    263   chrome::Navigate(&params);
    264 }
    265 
    266 void AppContextMenu::StartExtensionUninstall() {
    267   // ExtensionUninstall deletes itself when done or aborted.
    268   ExtensionUninstaller* uninstaller = new ExtensionUninstaller(profile_,
    269                                                                app_id_,
    270                                                                controller_);
    271   uninstaller->Run();
    272 }
    273 
    274 bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const {
    275   return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW;
    276 }
    277 
    278 string16 AppContextMenu::GetLabelForCommandId(int command_id) const {
    279   if (command_id == TOGGLE_PIN) {
    280     return controller_->IsAppPinned(app_id_) ?
    281         l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) :
    282         l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN);
    283   } else if (command_id == LAUNCH_NEW) {
    284 #if defined(OS_MACOSX)
    285     // Even fullscreen windows launch in a browser tab on Mac.
    286     const bool launches_in_tab = true;
    287 #else
    288     const bool launches_in_tab = IsCommandIdChecked(LAUNCH_TYPE_PINNED_TAB) ||
    289         IsCommandIdChecked(LAUNCH_TYPE_REGULAR_TAB);
    290 #endif
    291     return launches_in_tab ?
    292         l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB) :
    293         l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW);
    294   } else {
    295     NOTREACHED();
    296     return string16();
    297   }
    298 }
    299 
    300 bool AppContextMenu::IsCommandIdChecked(int command_id) const {
    301   if (command_id >= LAUNCH_TYPE_START && command_id < LAUNCH_TYPE_LAST) {
    302     return static_cast<int>(GetExtensionLaunchType(profile_, GetExtension())) +
    303         LAUNCH_TYPE_START == command_id;
    304   } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
    305              command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
    306     return extension_menu_items_->IsCommandIdChecked(command_id);
    307   }
    308   return false;
    309 }
    310 
    311 bool AppContextMenu::IsCommandIdEnabled(int command_id) const {
    312   if (command_id == TOGGLE_PIN) {
    313     return controller_->CanPin();
    314   } else if (command_id == OPTIONS) {
    315     const ExtensionService* service =
    316         extensions::ExtensionSystem::Get(profile_)->extension_service();
    317     const Extension* extension = GetExtension();
    318     return service->IsExtensionEnabledForLauncher(app_id_) &&
    319            extension &&
    320            !extensions::ManifestURL::GetOptionsPage(extension).is_empty();
    321   } else if (command_id == UNINSTALL) {
    322     const Extension* extension = GetExtension();
    323     const extensions::ManagementPolicy* policy =
    324         extensions::ExtensionSystem::Get(profile_)->management_policy();
    325     return extension &&
    326            policy->UserMayModifySettings(extension, NULL);
    327   } else if (command_id == DETAILS) {
    328     const Extension* extension = GetExtension();
    329     return extension && extension->from_webstore();
    330   } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
    331              command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
    332     return extension_menu_items_->IsCommandIdEnabled(command_id);
    333   } else if (command_id == MENU_NEW_WINDOW) {
    334     // "Normal" windows are not allowed when incognito is enforced.
    335     return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
    336         IncognitoModePrefs::FORCED;
    337   } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
    338     // Incognito windows are not allowed when incognito is disabled.
    339     return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
    340         IncognitoModePrefs::DISABLED;
    341   }
    342   return true;
    343 }
    344 
    345 bool AppContextMenu::GetAcceleratorForCommandId(
    346     int command_id,
    347     ui::Accelerator* acclelrator) {
    348   return false;
    349 }
    350 
    351 void AppContextMenu::ExecuteCommand(int command_id, int event_flags) {
    352   if (command_id == LAUNCH_NEW) {
    353     delegate_->ExecuteLaunchCommand(event_flags);
    354   } else if (command_id == TOGGLE_PIN && controller_->CanPin()) {
    355     if (controller_->IsAppPinned(app_id_))
    356       controller_->UnpinApp(app_id_);
    357     else
    358       controller_->PinApp(app_id_);
    359   } else if (command_id == CREATE_SHORTCUTS) {
    360     controller_->DoCreateShortcutsFlow(profile_, app_id_);
    361   } else if (command_id >= LAUNCH_TYPE_START &&
    362              command_id < LAUNCH_TYPE_LAST) {
    363     SetExtensionLaunchType(profile_,
    364                            app_id_,
    365                            static_cast<extensions::ExtensionPrefs::LaunchType>(
    366                                command_id - LAUNCH_TYPE_START));
    367   } else if (command_id == OPTIONS) {
    368     ShowExtensionOptions();
    369   } else if (command_id == UNINSTALL) {
    370     StartExtensionUninstall();
    371   } else if (command_id == DETAILS) {
    372     ShowExtensionDetails();
    373   } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
    374              command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
    375     extension_menu_items_->ExecuteCommand(command_id, NULL,
    376                                           content::ContextMenuParams());
    377   } else if (command_id == MENU_NEW_WINDOW) {
    378     controller_->CreateNewWindow(profile_, false);
    379   } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
    380     controller_->CreateNewWindow(profile_, true);
    381   }
    382 }
    383 
    384 }  // namespace app_list
    385