1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/extension_context_menu_model.h"
6
7#include "base/prefs/pref_service.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/app/chrome_command_ids.h"
10#include "chrome/browser/extensions/active_script_controller.h"
11#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
12#include "chrome/browser/extensions/context_menu_matcher.h"
13#include "chrome/browser/extensions/extension_action.h"
14#include "chrome/browser/extensions/extension_action_manager.h"
15#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/extensions/extension_tab_util.h"
17#include "chrome/browser/extensions/menu_manager.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/browser/sessions/session_tab_helper.h"
20#include "chrome/browser/ui/browser.h"
21#include "chrome/browser/ui/browser_window.h"
22#include "chrome/browser/ui/chrome_pages.h"
23#include "chrome/browser/ui/tabs/tab_strip_model.h"
24#include "chrome/common/extensions/extension_constants.h"
25#include "chrome/common/extensions/manifest_url_handler.h"
26#include "chrome/common/pref_names.h"
27#include "chrome/common/url_constants.h"
28#include "chrome/grit/chromium_strings.h"
29#include "chrome/grit/generated_resources.h"
30#include "content/public/browser/web_contents.h"
31#include "content/public/common/context_menu_params.h"
32#include "extensions/browser/extension_prefs.h"
33#include "extensions/browser/extension_registry.h"
34#include "extensions/browser/extension_system.h"
35#include "extensions/browser/management_policy.h"
36#include "extensions/browser/uninstall_reason.h"
37#include "extensions/common/extension.h"
38#include "extensions/common/feature_switch.h"
39#include "extensions/common/manifest_handlers/options_page_info.h"
40#include "ui/base/l10n/l10n_util.h"
41
42using content::OpenURLParams;
43using content::Referrer;
44using content::WebContents;
45using extensions::Extension;
46using extensions::ExtensionActionAPI;
47using extensions::ExtensionPrefs;
48using extensions::MenuItem;
49using extensions::MenuManager;
50
51namespace {
52
53// Returns true if the given |item| is of the given |type|.
54bool MenuItemMatchesAction(ExtensionContextMenuModel::ActionType type,
55                           const MenuItem* item) {
56  if (type == ExtensionContextMenuModel::NO_ACTION)
57    return false;
58
59  const MenuItem::ContextList& contexts = item->contexts();
60
61  if (contexts.Contains(MenuItem::ALL))
62    return true;
63  if (contexts.Contains(MenuItem::PAGE_ACTION) &&
64      (type == ExtensionContextMenuModel::PAGE_ACTION))
65    return true;
66  if (contexts.Contains(MenuItem::BROWSER_ACTION) &&
67      (type == ExtensionContextMenuModel::BROWSER_ACTION))
68    return true;
69
70  return false;
71}
72
73// Returns the id for the visibility command for the given |extension|, or -1
74// if none should be shown.
75int GetVisibilityStringId(Profile* profile, const Extension* extension) {
76  DCHECK(profile);
77  int string_id = -1;
78  if (!extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) {
79    // Without the toolbar redesign, we only show the visibility toggle for
80    // browser actions, and only give the option to hide.
81    if (extensions::ExtensionActionManager::Get(profile)->GetBrowserAction(
82            *extension)) {
83      string_id = IDS_EXTENSIONS_HIDE_BUTTON;
84    }
85  } else {
86    // With the redesign, we display "show" or "hide" based on the icon's
87    // visibility.
88    bool visible = ExtensionActionAPI::GetBrowserActionVisibility(
89                       ExtensionPrefs::Get(profile), extension->id());
90    string_id =
91        visible ? IDS_EXTENSIONS_HIDE_BUTTON : IDS_EXTENSIONS_SHOW_BUTTON;
92  }
93  return string_id;
94}
95
96}  // namespace
97
98ExtensionContextMenuModel::ExtensionContextMenuModel(const Extension* extension,
99                                                     Browser* browser,
100                                                     PopupDelegate* delegate)
101    : SimpleMenuModel(this),
102      extension_id_(extension->id()),
103      browser_(browser),
104      profile_(browser->profile()),
105      delegate_(delegate),
106      action_type_(NO_ACTION),
107      extension_items_count_(0) {
108  InitMenu(extension);
109
110  if (profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode) &&
111      delegate_) {
112    AddSeparator(ui::NORMAL_SEPARATOR);
113    AddItemWithStringId(INSPECT_POPUP, IDS_EXTENSION_ACTION_INSPECT_POPUP);
114  }
115}
116
117ExtensionContextMenuModel::ExtensionContextMenuModel(const Extension* extension,
118                                                     Browser* browser)
119    : SimpleMenuModel(this),
120      extension_id_(extension->id()),
121      browser_(browser),
122      profile_(browser->profile()),
123      delegate_(NULL),
124      action_type_(NO_ACTION),
125      extension_items_count_(0) {
126  InitMenu(extension);
127}
128
129bool ExtensionContextMenuModel::IsCommandIdChecked(int command_id) const {
130  if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
131      command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
132    return extension_items_->IsCommandIdChecked(command_id);
133  return false;
134}
135
136bool ExtensionContextMenuModel::IsCommandIdEnabled(int command_id) const {
137  const Extension* extension = GetExtension();
138  if (!extension)
139    return false;
140
141  if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
142      command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
143    return extension_items_->IsCommandIdEnabled(command_id);
144  } else if (command_id == CONFIGURE) {
145    return extensions::OptionsPageInfo::HasOptionsPage(extension);
146  } else if (command_id == NAME) {
147    // The NAME links to the Homepage URL. If the extension doesn't have a
148    // homepage, we just disable this menu item.
149    return extensions::ManifestURL::GetHomepageURL(extension).is_valid();
150  } else if (command_id == INSPECT_POPUP) {
151    WebContents* web_contents = GetActiveWebContents();
152    if (!web_contents)
153      return false;
154
155    return extension_action_ &&
156        extension_action_->HasPopup(SessionTabHelper::IdForTab(web_contents));
157  } else if (command_id == UNINSTALL) {
158    // Some extension types can not be uninstalled.
159    return extensions::ExtensionSystem::Get(
160        profile_)->management_policy()->UserMayModifySettings(extension, NULL);
161  }
162  return true;
163}
164
165bool ExtensionContextMenuModel::GetAcceleratorForCommandId(
166    int command_id, ui::Accelerator* accelerator) {
167  return false;
168}
169
170void ExtensionContextMenuModel::ExecuteCommand(int command_id,
171                                               int event_flags) {
172  const Extension* extension = GetExtension();
173  if (!extension)
174    return;
175
176  if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
177      command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
178    WebContents* web_contents =
179        browser_->tab_strip_model()->GetActiveWebContents();
180    DCHECK(extension_items_);
181    extension_items_->ExecuteCommand(
182        command_id, web_contents, content::ContextMenuParams());
183    return;
184  }
185
186  switch (command_id) {
187    case NAME: {
188      OpenURLParams params(extensions::ManifestURL::GetHomepageURL(extension),
189                           Referrer(), NEW_FOREGROUND_TAB,
190                           ui::PAGE_TRANSITION_LINK, false);
191      browser_->OpenURL(params);
192      break;
193    }
194    case ALWAYS_RUN: {
195      WebContents* web_contents = GetActiveWebContents();
196      if (web_contents) {
197        extensions::ActiveScriptController::GetForWebContents(web_contents)
198            ->AlwaysRunOnVisibleOrigin(extension);
199      }
200      break;
201    }
202    case CONFIGURE:
203      DCHECK(extensions::OptionsPageInfo::HasOptionsPage(extension));
204      extensions::ExtensionTabUtil::OpenOptionsPage(extension, browser_);
205      break;
206    case TOGGLE_VISIBILITY: {
207      ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
208      bool visible = ExtensionActionAPI::GetBrowserActionVisibility(
209                         prefs, extension->id());
210      ExtensionActionAPI::SetBrowserActionVisibility(
211          prefs, extension->id(), !visible);
212      break;
213    }
214    case UNINSTALL: {
215      AddRef();  // Balanced in Accepted() and Canceled()
216      extension_uninstall_dialog_.reset(
217          extensions::ExtensionUninstallDialog::Create(
218              profile_, browser_->window()->GetNativeWindow(), this));
219      extension_uninstall_dialog_->ConfirmUninstall(extension);
220      break;
221    }
222    case MANAGE: {
223      chrome::ShowExtensions(browser_, extension->id());
224      break;
225    }
226    case INSPECT_POPUP: {
227      delegate_->InspectPopup();
228      break;
229    }
230    default:
231     NOTREACHED() << "Unknown option";
232     break;
233  }
234}
235
236void ExtensionContextMenuModel::ExtensionUninstallAccepted() {
237  if (GetExtension()) {
238    extensions::ExtensionSystem::Get(profile_)
239        ->extension_service()
240        ->UninstallExtension(extension_id_,
241                             extensions::UNINSTALL_REASON_USER_INITIATED,
242                             base::Bind(&base::DoNothing),
243                             NULL);
244  }
245  Release();
246}
247
248void ExtensionContextMenuModel::ExtensionUninstallCanceled() {
249  Release();
250}
251
252ExtensionContextMenuModel::~ExtensionContextMenuModel() {}
253
254void ExtensionContextMenuModel::InitMenu(const Extension* extension) {
255  DCHECK(extension);
256
257  extensions::ExtensionActionManager* extension_action_manager =
258      extensions::ExtensionActionManager::Get(profile_);
259  extension_action_ = extension_action_manager->GetBrowserAction(*extension);
260  if (!extension_action_) {
261    extension_action_ = extension_action_manager->GetPageAction(*extension);
262    if (extension_action_)
263      action_type_ = PAGE_ACTION;
264  } else {
265    action_type_ = BROWSER_ACTION;
266  }
267
268  extension_items_.reset(new extensions::ContextMenuMatcher(
269      profile_, this, this, base::Bind(MenuItemMatchesAction, action_type_)));
270
271  std::string extension_name = extension->name();
272  // Ampersands need to be escaped to avoid being treated like
273  // mnemonics in the menu.
274  base::ReplaceChars(extension_name, "&", "&&", &extension_name);
275  AddItem(NAME, base::UTF8ToUTF16(extension_name));
276  AppendExtensionItems();
277  AddSeparator(ui::NORMAL_SEPARATOR);
278
279  // Add the "Always Allow" item for adding persisted permissions for script
280  // injections if there is an active action for this extension. Note that this
281  // will add it to *all* extension action context menus, not just the one
282  // attached to the script injection request icon, but that's okay.
283  WebContents* web_contents = GetActiveWebContents();
284  if (web_contents &&
285      extensions::ActiveScriptController::GetForWebContents(web_contents)
286          ->WantsToRun(extension)) {
287    AddItemWithStringId(ALWAYS_RUN, IDS_EXTENSIONS_ALWAYS_RUN);
288  }
289
290  AddItemWithStringId(CONFIGURE, IDS_EXTENSIONS_OPTIONS_MENU_ITEM);
291  AddItem(UNINSTALL, l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL));
292
293  // Add a toggle visibility (show/hide) if the extension icon is shown on the
294  // toolbar.
295  int visibility_string_id = GetVisibilityStringId(profile_, extension);
296  if (visibility_string_id != -1)
297    AddItemWithStringId(TOGGLE_VISIBILITY, visibility_string_id);
298
299  AddSeparator(ui::NORMAL_SEPARATOR);
300  AddItemWithStringId(MANAGE, IDS_MANAGE_EXTENSION);
301}
302
303const Extension* ExtensionContextMenuModel::GetExtension() const {
304  return extensions::ExtensionRegistry::Get(profile_)
305      ->enabled_extensions()
306      .GetByID(extension_id_);
307}
308
309void ExtensionContextMenuModel::AppendExtensionItems() {
310  extension_items_->Clear();
311
312  MenuManager* menu_manager = MenuManager::Get(profile_);
313  if (!menu_manager ||
314      !menu_manager->MenuItems(MenuItem::ExtensionKey(extension_id_)))
315    return;
316
317  AddSeparator(ui::NORMAL_SEPARATOR);
318
319  extension_items_count_ = 0;
320  extension_items_->AppendExtensionItems(MenuItem::ExtensionKey(extension_id_),
321                                         base::string16(),
322                                         &extension_items_count_,
323                                         true);  // is_action_menu
324}
325
326content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const {
327  return browser_->tab_strip_model()->GetActiveWebContents();
328}
329