blob: ac5ce9e368efdf171b3045dd726c1b07c06f90fa [file] [log] [blame]
// Copyright 2013 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/app_list/app_context_menu.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/extensions/context_menu_matcher.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
#include "chrome/browser/extensions/management_policy.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "content/public/common/context_menu_params.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "net/base/url_util.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(USE_ASH)
#include "ash/shell.h"
#endif
using extensions::Extension;
namespace app_list {
namespace {
enum CommandId {
LAUNCH_NEW = 100,
TOGGLE_PIN,
CREATE_SHORTCUTS,
OPTIONS,
UNINSTALL,
DETAILS,
MENU_NEW_WINDOW,
MENU_NEW_INCOGNITO_WINDOW,
// Order matters in LAUNCHER_TYPE_xxxx and must match LaunchType.
LAUNCH_TYPE_START = 200,
LAUNCH_TYPE_PINNED_TAB = LAUNCH_TYPE_START,
LAUNCH_TYPE_REGULAR_TAB,
LAUNCH_TYPE_FULLSCREEN,
LAUNCH_TYPE_WINDOW,
LAUNCH_TYPE_LAST,
};
// ExtensionUninstaller runs the extension uninstall flow. It shows the
// extension uninstall dialog and wait for user to confirm or cancel the
// uninstall.
class ExtensionUninstaller : public ExtensionUninstallDialog::Delegate {
public:
ExtensionUninstaller(Profile* profile,
const std::string& extension_id,
AppListControllerDelegate* controller)
: profile_(profile),
app_id_(extension_id),
controller_(controller) {
}
void Run() {
const Extension* extension =
extensions::ExtensionSystem::Get(profile_)->extension_service()->
GetInstalledExtension(app_id_);
if (!extension) {
CleanUp();
return;
}
controller_->OnShowExtensionPrompt();
dialog_.reset(ExtensionUninstallDialog::Create(profile_, NULL, this));
dialog_->ConfirmUninstall(extension);
}
private:
// Overridden from ExtensionUninstallDialog::Delegate:
virtual void ExtensionUninstallAccepted() OVERRIDE {
ExtensionService* service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
const Extension* extension = service->GetInstalledExtension(app_id_);
if (extension) {
service->UninstallExtension(app_id_,
false, /* external_uninstall*/
NULL);
}
controller_->OnCloseExtensionPrompt();
CleanUp();
}
virtual void ExtensionUninstallCanceled() OVERRIDE {
controller_->OnCloseExtensionPrompt();
CleanUp();
}
void CleanUp() {
delete this;
}
Profile* profile_;
std::string app_id_;
AppListControllerDelegate* controller_;
scoped_ptr<ExtensionUninstallDialog> dialog_;
DISALLOW_COPY_AND_ASSIGN(ExtensionUninstaller);
};
extensions::ExtensionPrefs::LaunchType GetExtensionLaunchType(
Profile* profile,
const Extension* extension) {
ExtensionService* service =
extensions::ExtensionSystem::Get(profile)->extension_service();
return service->extension_prefs()->
GetLaunchType(extension, extensions::ExtensionPrefs::LAUNCH_DEFAULT);
}
void SetExtensionLaunchType(
Profile* profile,
const std::string& extension_id,
extensions::ExtensionPrefs::LaunchType launch_type) {
ExtensionService* service =
extensions::ExtensionSystem::Get(profile)->extension_service();
service->extension_prefs()->SetLaunchType(extension_id, launch_type);
}
bool MenuItemHasLauncherContext(const extensions::MenuItem* item) {
return item->contexts().Contains(extensions::MenuItem::LAUNCHER);
}
} // namespace
AppContextMenu::AppContextMenu(AppContextMenuDelegate* delegate,
Profile* profile,
const std::string& app_id,
AppListControllerDelegate* controller,
bool is_platform_app,
bool is_search_result)
: delegate_(delegate),
profile_(profile),
app_id_(app_id),
controller_(controller),
is_platform_app_(is_platform_app),
is_search_result_(is_search_result) {
}
AppContextMenu::~AppContextMenu() {}
ui::MenuModel* AppContextMenu::GetMenuModel() {
const Extension* extension = GetExtension();
if (!extension)
return NULL;
if (menu_model_.get())
return menu_model_.get();
menu_model_.reset(new ui::SimpleMenuModel(this));
if (app_id_ == extension_misc::kChromeAppId) {
menu_model_->AddItemWithStringId(
MENU_NEW_WINDOW,
IDS_APP_LIST_NEW_WINDOW);
if (!profile_->IsOffTheRecord()) {
menu_model_->AddItemWithStringId(
MENU_NEW_INCOGNITO_WINDOW,
IDS_APP_LIST_NEW_INCOGNITO_WINDOW);
}
} else {
extension_menu_items_.reset(new extensions::ContextMenuMatcher(
profile_, this, menu_model_.get(),
base::Bind(MenuItemHasLauncherContext)));
if (!is_platform_app_)
menu_model_->AddItem(LAUNCH_NEW, string16());
int index = 0;
extension_menu_items_->AppendExtensionItems(app_id_, string16(),
&index);
// Show Pin/Unpin option if shelf is available.
if (controller_->GetPinnable() != AppListControllerDelegate::NO_PIN) {
menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
menu_model_->AddItemWithStringId(
TOGGLE_PIN,
controller_->IsAppPinned(app_id_) ?
IDS_APP_LIST_CONTEXT_MENU_UNPIN :
IDS_APP_LIST_CONTEXT_MENU_PIN);
}
if (controller_->CanDoCreateShortcutsFlow()) {
menu_model_->AddItemWithStringId(CREATE_SHORTCUTS,
IDS_NEW_TAB_APP_CREATE_SHORTCUT);
}
if (!is_platform_app_) {
menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
menu_model_->AddCheckItemWithStringId(
LAUNCH_TYPE_REGULAR_TAB,
IDS_APP_CONTEXT_MENU_OPEN_REGULAR);
menu_model_->AddCheckItemWithStringId(
LAUNCH_TYPE_PINNED_TAB,
IDS_APP_CONTEXT_MENU_OPEN_PINNED);
#if defined(OS_MACOSX)
// Mac does not support standalone web app browser windows or maximize.
menu_model_->AddCheckItemWithStringId(
LAUNCH_TYPE_FULLSCREEN,
IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN);
#else
menu_model_->AddCheckItemWithStringId(
LAUNCH_TYPE_WINDOW,
IDS_APP_CONTEXT_MENU_OPEN_WINDOW);
// Even though the launch type is Full Screen it is more accurately
// described as Maximized in Ash.
menu_model_->AddCheckItemWithStringId(
LAUNCH_TYPE_FULLSCREEN,
IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED);
#endif
menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS);
}
menu_model_->AddItemWithStringId(DETAILS, IDS_NEW_TAB_APP_DETAILS);
menu_model_->AddItemWithStringId(
UNINSTALL,
is_platform_app_ ? IDS_APP_LIST_UNINSTALL_ITEM
: IDS_EXTENSIONS_UNINSTALL);
}
return menu_model_.get();
}
const Extension* AppContextMenu::GetExtension() const {
const ExtensionService* service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
const Extension* extension = service->GetInstalledExtension(app_id_);
return extension;
}
void AppContextMenu::ShowExtensionOptions() {
const Extension* extension = GetExtension();
if (!extension)
return;
chrome::NavigateParams params(
profile_,
extensions::ManifestURL::GetOptionsPage(extension),
content::PAGE_TRANSITION_LINK);
chrome::Navigate(&params);
}
void AppContextMenu::ShowExtensionDetails() {
const Extension* extension = GetExtension();
if (!extension)
return;
const GURL url = extensions::ManifestURL::GetDetailsURL(extension);
DCHECK_NE(url, GURL::EmptyGURL());
const std::string source = AppListControllerDelegate::AppListSourceToString(
is_search_result_ ?
AppListControllerDelegate::LAUNCH_FROM_APP_LIST_SEARCH :
AppListControllerDelegate::LAUNCH_FROM_APP_LIST);
chrome::NavigateParams params(
profile_,
net::AppendQueryParameter(url,
extension_urls::kWebstoreSourceField,
source),
content::PAGE_TRANSITION_LINK);
chrome::Navigate(&params);
}
void AppContextMenu::StartExtensionUninstall() {
// ExtensionUninstall deletes itself when done or aborted.
ExtensionUninstaller* uninstaller = new ExtensionUninstaller(profile_,
app_id_,
controller_);
uninstaller->Run();
}
bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const {
return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW;
}
string16 AppContextMenu::GetLabelForCommandId(int command_id) const {
if (command_id == TOGGLE_PIN) {
return controller_->IsAppPinned(app_id_) ?
l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) :
l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN);
} else if (command_id == LAUNCH_NEW) {
#if defined(OS_MACOSX)
// Even fullscreen windows launch in a browser tab on Mac.
const bool launches_in_tab = true;
#else
const bool launches_in_tab = IsCommandIdChecked(LAUNCH_TYPE_PINNED_TAB) ||
IsCommandIdChecked(LAUNCH_TYPE_REGULAR_TAB);
#endif
return launches_in_tab ?
l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB) :
l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW);
} else {
NOTREACHED();
return string16();
}
}
bool AppContextMenu::IsCommandIdChecked(int command_id) const {
if (command_id >= LAUNCH_TYPE_START && command_id < LAUNCH_TYPE_LAST) {
return static_cast<int>(GetExtensionLaunchType(profile_, GetExtension())) +
LAUNCH_TYPE_START == command_id;
} else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
return extension_menu_items_->IsCommandIdChecked(command_id);
}
return false;
}
bool AppContextMenu::IsCommandIdEnabled(int command_id) const {
if (command_id == TOGGLE_PIN) {
return controller_->GetPinnable() ==
AppListControllerDelegate::PIN_EDITABLE;
} else if (command_id == OPTIONS) {
const ExtensionService* service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
const Extension* extension = GetExtension();
return service->IsExtensionEnabledForLauncher(app_id_) &&
extension &&
!extensions::ManifestURL::GetOptionsPage(extension).is_empty();
} else if (command_id == UNINSTALL) {
const Extension* extension = GetExtension();
const extensions::ManagementPolicy* policy =
extensions::ExtensionSystem::Get(profile_)->management_policy();
return extension &&
policy->UserMayModifySettings(extension, NULL);
} else if (command_id == DETAILS) {
const Extension* extension = GetExtension();
return extension && extension->from_webstore();
} else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
return extension_menu_items_->IsCommandIdEnabled(command_id);
} else if (command_id == MENU_NEW_WINDOW) {
// "Normal" windows are not allowed when incognito is enforced.
return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
IncognitoModePrefs::FORCED;
} else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
// Incognito windows are not allowed when incognito is disabled.
return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
IncognitoModePrefs::DISABLED;
}
return true;
}
bool AppContextMenu::GetAcceleratorForCommandId(
int command_id,
ui::Accelerator* acclelrator) {
return false;
}
void AppContextMenu::ExecuteCommand(int command_id, int event_flags) {
if (command_id == LAUNCH_NEW) {
delegate_->ExecuteLaunchCommand(event_flags);
} else if (command_id == TOGGLE_PIN && controller_->GetPinnable() ==
AppListControllerDelegate::PIN_EDITABLE) {
if (controller_->IsAppPinned(app_id_))
controller_->UnpinApp(app_id_);
else
controller_->PinApp(app_id_);
} else if (command_id == CREATE_SHORTCUTS) {
controller_->DoCreateShortcutsFlow(profile_, app_id_);
} else if (command_id >= LAUNCH_TYPE_START &&
command_id < LAUNCH_TYPE_LAST) {
SetExtensionLaunchType(profile_,
app_id_,
static_cast<extensions::ExtensionPrefs::LaunchType>(
command_id - LAUNCH_TYPE_START));
} else if (command_id == OPTIONS) {
ShowExtensionOptions();
} else if (command_id == UNINSTALL) {
StartExtensionUninstall();
} else if (command_id == DETAILS) {
ShowExtensionDetails();
} else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
extension_menu_items_->ExecuteCommand(command_id, NULL,
content::ContextMenuParams());
} else if (command_id == MENU_NEW_WINDOW) {
controller_->CreateNewWindow(profile_, false);
} else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
controller_->CreateNewWindow(profile_, true);
}
}
} // namespace app_list