• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2011 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#import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu.h"
6
7#include "base/sys_string_conversions.h"
8#include "base/task.h"
9#include "chrome/browser/extensions/extension_service.h"
10#include "chrome/browser/extensions/extension_tabs_module.h"
11#include "chrome/browser/extensions/extension_uninstall_dialog.h"
12#include "chrome/browser/prefs/pref_change_registrar.h"
13#include "chrome/browser/prefs/pref_service.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/ui/browser_list.h"
16#include "chrome/browser/ui/cocoa/browser_window_cocoa.h"
17#include "chrome/browser/ui/cocoa/browser_window_controller.h"
18#include "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h"
19#include "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h"
20#include "chrome/browser/ui/cocoa/info_bubble_view.h"
21#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
22#include "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
23#include "chrome/common/extensions/extension.h"
24#include "chrome/common/extensions/extension_action.h"
25#include "chrome/common/extensions/extension_constants.h"
26#include "chrome/common/pref_names.h"
27#include "chrome/common/url_constants.h"
28#include "content/common/notification_details.h"
29#include "content/common/notification_observer.h"
30#include "content/common/notification_source.h"
31#include "content/common/notification_type.h"
32#include "grit/generated_resources.h"
33#include "ui/base/l10n/l10n_util_mac.h"
34
35// A class that loads the extension icon on the I/O thread before showing the
36// confirmation dialog to uninstall the given extension.
37// Also acts as the extension's UI delegate in order to display the dialog.
38class AsyncUninstaller : public ExtensionUninstallDialog::Delegate {
39 public:
40  AsyncUninstaller(const Extension* extension, Profile* profile)
41      : extension_(extension),
42        profile_(profile) {
43    extension_uninstall_dialog_.reset(new ExtensionUninstallDialog(profile));
44    extension_uninstall_dialog_->ConfirmUninstall(this, extension_);
45  }
46
47  ~AsyncUninstaller() {}
48
49  // ExtensionUninstallDialog::Delegate:
50  virtual void ExtensionDialogAccepted() {
51    profile_->GetExtensionService()->
52        UninstallExtension(extension_->id(), false, NULL);
53  }
54  virtual void ExtensionDialogCanceled() {}
55
56 private:
57  // The extension that we're loading the icon for. Weak.
58  const Extension* extension_;
59
60  // The current profile. Weak.
61  Profile* profile_;
62
63  scoped_ptr<ExtensionUninstallDialog> extension_uninstall_dialog_;
64
65  DISALLOW_COPY_AND_ASSIGN(AsyncUninstaller);
66};
67
68namespace extension_action_context_menu {
69
70class DevmodeObserver : public NotificationObserver {
71 public:
72  DevmodeObserver(ExtensionActionContextMenu* menu,
73                             PrefService* service)
74      : menu_(menu), pref_service_(service) {
75    registrar_.Init(pref_service_);
76    registrar_.Add(prefs::kExtensionsUIDeveloperMode, this);
77  }
78  virtual ~DevmodeObserver() {}
79
80  void Observe(NotificationType type,
81               const NotificationSource& source,
82               const NotificationDetails& details) {
83    if (type == NotificationType::PREF_CHANGED)
84      [menu_ updateInspectorItem];
85    else
86      NOTREACHED();
87  }
88
89 private:
90  ExtensionActionContextMenu* menu_;
91  PrefService* pref_service_;
92  PrefChangeRegistrar registrar_;
93};
94
95class ProfileObserverBridge : public NotificationObserver {
96 public:
97  ProfileObserverBridge(ExtensionActionContextMenu* owner,
98                        const Profile* profile)
99      : owner_(owner),
100        profile_(profile) {
101    registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
102                   Source<Profile>(profile));
103  }
104
105  ~ProfileObserverBridge() {}
106
107  // Overridden from NotificationObserver
108  void Observe(NotificationType type,
109               const NotificationSource& source,
110               const NotificationDetails& details) {
111    if (type == NotificationType::PROFILE_DESTROYED &&
112        source == Source<Profile>(profile_)) {
113      [owner_ invalidateProfile];
114    }
115  }
116
117 private:
118  ExtensionActionContextMenu* owner_;
119  const Profile* profile_;
120  NotificationRegistrar registrar_;
121};
122
123}  // namespace extension_action_context_menu
124
125@interface ExtensionActionContextMenu(Private)
126// Callback for the context menu items.
127- (void)dispatch:(id)menuItem;
128@end
129
130@implementation ExtensionActionContextMenu
131
132namespace {
133// Enum of menu item choices to their respective indices.
134// NOTE: You MUST keep this in sync with the |menuItems| NSArray below.
135enum {
136  kExtensionContextName = 0,
137  kExtensionContextOptions = 2,
138  kExtensionContextDisable = 3,
139  kExtensionContextUninstall = 4,
140  kExtensionContextHide = 5,
141  kExtensionContextManage = 7,
142  kExtensionContextInspect = 8
143};
144
145int CurrentTabId() {
146  Browser* browser = BrowserList::GetLastActive();
147  if(!browser)
148    return -1;
149  TabContents* contents = browser->GetSelectedTabContents();
150  if (!contents)
151    return -1;
152  return ExtensionTabUtil::GetTabId(contents);
153}
154
155}  // namespace
156
157- (id)initWithExtension:(const Extension*)extension
158                profile:(Profile*)profile
159        extensionAction:(ExtensionAction*)action{
160  if ((self = [super initWithTitle:@""])) {
161    action_ = action;
162    extension_ = extension;
163    profile_ = profile;
164
165    NSArray* menuItems = [NSArray arrayWithObjects:
166        base::SysUTF8ToNSString(extension->name()),
167        [NSMenuItem separatorItem],
168        l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_OPTIONS),
169        l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_DISABLE),
170        l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_UNINSTALL),
171        l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_HIDE_BUTTON),
172        [NSMenuItem separatorItem],
173        l10n_util::GetNSStringWithFixup(IDS_MANAGE_EXTENSIONS),
174        nil];
175
176    for (id item in menuItems) {
177      if ([item isKindOfClass:[NSMenuItem class]]) {
178        [self addItem:item];
179      } else if ([item isKindOfClass:[NSString class]]) {
180        NSMenuItem* itemObj = [self addItemWithTitle:item
181                                              action:@selector(dispatch:)
182                                       keyEquivalent:@""];
183        // The tag should correspond to the enum above.
184        // NOTE: The enum and the order of the menu items MUST be in sync.
185        [itemObj setTag:[self indexOfItem:itemObj]];
186
187        // Disable the 'Options' item if there are no options to set.
188        if ([itemObj tag] == kExtensionContextOptions &&
189            extension_->options_url().spec().length() <= 0) {
190          // Setting the target to nil will disable the item. For some reason
191          // setEnabled:NO does not work.
192          [itemObj setTarget:nil];
193        } else {
194          [itemObj setTarget:self];
195        }
196
197        // Only browser actions can have their button hidden. Page actions
198        // should never show the "Hide" menu item.
199        if ([itemObj tag] == kExtensionContextHide &&
200            !extension->browser_action()) {
201          [itemObj setTarget:nil];  // Item is disabled.
202          [itemObj setHidden:YES];  // Item is hidden.
203        } else {
204          [itemObj setTarget:self];
205        }
206      }
207    }
208
209    NSString* inspectorTitle =
210        l10n_util::GetNSStringWithFixup(IDS_EXTENSION_ACTION_INSPECT_POPUP);
211    inspectorItem_.reset([[NSMenuItem alloc] initWithTitle:inspectorTitle
212                                                    action:@selector(dispatch:)
213                                             keyEquivalent:@""]);
214    [inspectorItem_.get() setTarget:self];
215    [inspectorItem_.get() setTag:kExtensionContextInspect];
216
217    PrefService* service = profile_->GetPrefs();
218    observer_.reset(
219        new extension_action_context_menu::DevmodeObserver(self, service));
220    profile_observer_.reset(
221        new extension_action_context_menu::ProfileObserverBridge(self,
222                                                                 profile));
223
224    [self updateInspectorItem];
225    return self;
226  }
227  return nil;
228}
229
230- (void)updateInspectorItem {
231  PrefService* service = profile_->GetPrefs();
232  bool devmode = service->GetBoolean(prefs::kExtensionsUIDeveloperMode);
233  if (devmode) {
234    if ([self indexOfItem:inspectorItem_.get()] == -1)
235      [self addItem:inspectorItem_.get()];
236  } else {
237    if ([self indexOfItem:inspectorItem_.get()] != -1)
238      [self removeItem:inspectorItem_.get()];
239  }
240}
241
242- (void)dispatch:(id)menuItem {
243  Browser* browser = BrowserList::FindBrowserWithProfile(profile_);
244  if (!browser)
245    return;
246
247  NSMenuItem* item = (NSMenuItem*)menuItem;
248  switch ([item tag]) {
249    case kExtensionContextName: {
250      GURL url(std::string(extension_urls::kGalleryBrowsePrefix) +
251               std::string("/detail/") + extension_->id());
252      browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK);
253      break;
254    }
255    case kExtensionContextOptions: {
256      DCHECK(!extension_->options_url().is_empty());
257      profile_->GetExtensionProcessManager()->OpenOptionsPage(extension_,
258                                                              browser);
259      break;
260    }
261    case kExtensionContextDisable: {
262      ExtensionService* extensionService = profile_->GetExtensionService();
263      if (!extensionService)
264        return; // Incognito mode.
265      extensionService->DisableExtension(extension_->id());
266      break;
267    }
268    case kExtensionContextUninstall: {
269      uninstaller_.reset(new AsyncUninstaller(extension_, profile_));
270      break;
271    }
272    case kExtensionContextHide: {
273      ExtensionService* extension_service = profile_->GetExtensionService();
274      extension_service->SetBrowserActionVisibility(extension_, false);
275      break;
276    }
277    case kExtensionContextManage: {
278      browser->OpenURL(GURL(chrome::kChromeUIExtensionsURL), GURL(),
279                       NEW_FOREGROUND_TAB, PageTransition::LINK);
280      break;
281    }
282    case kExtensionContextInspect: {
283      BrowserWindowCocoa* window =
284          static_cast<BrowserWindowCocoa*>(browser->window());
285      ToolbarController* toolbarController =
286          [window->cocoa_controller() toolbarController];
287      LocationBarViewMac* locationBarView =
288          [toolbarController locationBarBridge];
289
290      NSPoint popupPoint = NSZeroPoint;
291      if (extension_->page_action() == action_) {
292        popupPoint = locationBarView->GetPageActionBubblePoint(action_);
293
294      } else if (extension_->browser_action() == action_) {
295        BrowserActionsController* controller =
296            [toolbarController browserActionsController];
297        popupPoint = [controller popupPointForBrowserAction:extension_];
298
299      } else {
300        NOTREACHED() << "action_ is not a page action or browser action?";
301      }
302
303      int tabId = CurrentTabId();
304      GURL url = action_->GetPopupUrl(tabId);
305      DCHECK(url.is_valid());
306      [ExtensionPopupController showURL:url
307                              inBrowser:BrowserList::GetLastActive()
308                             anchoredAt:popupPoint
309                          arrowLocation:info_bubble::kTopRight
310                                devMode:YES];
311      break;
312    }
313    default:
314      NOTREACHED();
315      break;
316  }
317}
318
319- (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
320  if([menuItem isEqualTo:inspectorItem_.get()]) {
321    return action_ && action_->HasPopup(CurrentTabId());
322  }
323  return YES;
324}
325
326- (void)invalidateProfile {
327  observer_.reset();
328  profile_ = NULL;
329}
330
331@end
332