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.
5 #import "chrome/browser/ui/cocoa/extensions/extension_installed_bubble_controller.h"
7 #include "base/i18n/rtl.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/extensions/bundle_installer.h"
12 #include "chrome/browser/extensions/extension_action.h"
13 #include "chrome/browser/extensions/extension_action_manager.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_navigator.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #include "chrome/browser/ui/chrome_pages.h"
18 #include "chrome/browser/ui/chrome_style.h"
19 #include "chrome/browser/ui/cocoa/browser_window_cocoa.h"
20 #include "chrome/browser/ui/cocoa/browser_window_controller.h"
21 #include "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h"
22 #include "chrome/browser/ui/cocoa/hover_close_button.h"
23 #include "chrome/browser/ui/cocoa/info_bubble_view.h"
24 #include "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
25 #include "chrome/browser/ui/cocoa/new_tab_button.h"
26 #include "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
27 #include "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
28 #include "chrome/browser/ui/extensions/extension_install_ui_factory.h"
29 #include "chrome/browser/ui/extensions/extension_installed_bubble.h"
30 #include "chrome/browser/ui/singleton_tabs.h"
31 #include "chrome/browser/ui/sync/sync_promo_ui.h"
32 #include "chrome/common/extensions/api/extension_action/action_info.h"
33 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
34 #include "chrome/common/extensions/sync_helper.h"
35 #include "chrome/common/url_constants.h"
36 #include "chrome/grit/chromium_strings.h"
37 #include "chrome/grit/generated_resources.h"
38 #include "components/signin/core/browser/signin_metrics.h"
39 #include "extensions/browser/install/extension_install_ui.h"
40 #include "extensions/common/extension.h"
41 #include "extensions/common/feature_switch.h"
42 #import "skia/ext/skia_utils_mac.h"
43 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
44 #import "ui/base/cocoa/controls/hyperlink_text_view.h"
45 #include "ui/base/l10n/l10n_util.h"
47 using content::BrowserThread;
48 using extensions::BundleInstaller;
49 using extensions::Extension;
51 class ExtensionInstalledBubbleBridge
52 : public ExtensionInstalledBubble::Delegate {
54 explicit ExtensionInstalledBubbleBridge(
55 ExtensionInstalledBubbleController* controller);
56 ~ExtensionInstalledBubbleBridge() override;
59 // ExtensionInstalledBubble::Delegate:
60 bool MaybeShowNow() override;
62 // The (owning) installed bubble controller.
63 ExtensionInstalledBubbleController* controller_;
65 DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleBridge);
68 ExtensionInstalledBubbleBridge::ExtensionInstalledBubbleBridge(
69 ExtensionInstalledBubbleController* controller)
70 : controller_(controller) {
73 ExtensionInstalledBubbleBridge::~ExtensionInstalledBubbleBridge() {
76 bool ExtensionInstalledBubbleBridge::MaybeShowNow() {
77 [controller_ showWindow:controller_];
81 @implementation ExtensionInstalledBubbleController
83 @synthesize bundle = bundle_;
84 // Exposed for unit test.
85 @synthesize pageActionPreviewShowing = pageActionPreviewShowing_;
87 - (id)initWithParentWindow:(NSWindow*)parentWindow
88 extension:(const Extension*)extension
89 bundle:(const BundleInstaller*)bundle
90 browser:(Browser*)browser
92 NSString* nibName = bundle ? @"ExtensionInstalledBubbleBundle" :
93 @"ExtensionInstalledBubble";
94 if ((self = [super initWithWindowNibPath:nibName
95 parentWindow:parentWindow
96 anchoredAt:NSZeroPoint])) {
100 icon_.reset([gfx::SkBitmapToNSImage(icon) retain]);
101 pageActionPreviewShowing_ = NO;
104 type_ = extension_installed_bubble::kBundle;
105 } else if (extension->is_app()) {
106 type_ = extension_installed_bubble::kApp;
107 } else if (!extensions::OmniboxInfo::GetKeyword(extension).empty()) {
108 type_ = extension_installed_bubble::kOmniboxKeyword;
109 } else if (extensions::ActionInfo::GetBrowserActionInfo(extension)) {
110 type_ = extension_installed_bubble::kBrowserAction;
111 } else if (extensions::ActionInfo::GetPageActionInfo(extension) &&
112 extensions::ActionInfo::IsVerboseInstallMessage(extension)) {
113 type_ = extension_installed_bubble::kPageAction;
115 type_ = extension_installed_bubble::kGeneric;
118 if (type_ == extension_installed_bubble::kBundle) {
119 [self showWindow:self];
121 // Start showing window only after extension has fully loaded.
122 installedBubbleBridge_.reset(new ExtensionInstalledBubbleBridge(self));
123 installedBubble_.reset(new ExtensionInstalledBubble(
124 installedBubbleBridge_.get(),
128 installedBubble_->IgnoreBrowserClosing();
134 - (const Extension*)extension {
135 if (type_ == extension_installed_bubble::kBundle)
137 return installedBubble_->extension();
140 // Sets |promo_| based on |promoPlaceholder_|, sets |promoPlaceholder_| to nil.
141 - (void)initializeLabel {
142 // Replace the promo placeholder NSTextField with the real label NSTextView.
143 // The former doesn't show links in a nice way, but the latter can't be added
144 // in IB without a containing scroll view, so create the NSTextView
146 promo_.reset([[HyperlinkTextView alloc]
147 initWithFrame:[promoPlaceholder_ frame]]);
148 [promo_.get() setAutoresizingMask:[promoPlaceholder_ autoresizingMask]];
149 [[promoPlaceholder_ superview]
150 replaceSubview:promoPlaceholder_ with:promo_.get()];
151 promoPlaceholder_ = nil; // Now released.
152 [promo_.get() setDelegate:self];
155 // Returns YES if the sync promo should be shown in the bubble.
156 - (BOOL)showSyncPromo {
157 if (type_ == extension_installed_bubble::kBundle)
159 return extensions::sync_helper::IsSyncableExtension([self extension]) &&
160 SyncPromoUI::ShouldShowSyncPromo(browser_->profile());
163 - (void)windowWillClose:(NSNotification*)notification {
164 // Turn off page action icon preview when the window closes, unless we
165 // already removed it when the window resigned key status.
166 [self removePageActionPreviewIfNecessary];
168 [closeButton_ setTrackingEnabled:NO];
169 [super windowWillClose:notification];
172 // The controller is the delegate of the window, so it receives "did resign
173 // key" notifications. When key is resigned, close the window.
174 - (void)windowDidResignKey:(NSNotification*)notification {
175 // If the browser window is closing, we need to remove the page action
176 // immediately, otherwise the closing animation may overlap with
177 // browser destruction.
178 [self removePageActionPreviewIfNecessary];
179 [super windowDidResignKey:notification];
182 - (IBAction)closeWindow:(id)sender {
183 DCHECK([[self window] isVisible]);
187 - (BOOL)textView:(NSTextView*)aTextView
188 clickedOnLink:(id)link
189 atIndex:(NSUInteger)charIndex {
190 DCHECK_EQ(promo_.get(), aTextView);
191 chrome::ShowBrowserSignin(browser_,
192 signin_metrics::SOURCE_EXTENSION_INSTALL_BUBBLE);
196 // Extracted to a function here so that it can be overridden for unit testing.
197 - (void)removePageActionPreviewIfNecessary {
198 if (![self extension] || !pageActionPreviewShowing_)
200 ExtensionAction* page_action =
201 extensions::ExtensionActionManager::Get(browser_->profile())->
202 GetPageAction(*[self extension]);
205 pageActionPreviewShowing_ = NO;
207 BrowserWindowCocoa* window =
208 static_cast<BrowserWindowCocoa*>(browser_->window());
209 LocationBarViewMac* locationBarView =
210 [window->cocoa_controller() locationBarBridge];
211 locationBarView->SetPreviewEnabledPageAction(page_action,
212 false); // disables preview.
215 // The extension installed bubble points at the browser action icon or the
216 // page action icon (shown as a preview), depending on the extension type.
217 // We need to calculate the location of these icons and the size of the
218 // message itself (which varies with the title of the extension) in order
219 // to figure out the origin point for the extension installed bubble.
220 // TODO(mirandac): add framework to easily test extension UI components!
221 - (NSPoint)calculateArrowPoint {
222 BrowserWindowCocoa* window =
223 static_cast<BrowserWindowCocoa*>(browser_->window());
224 NSPoint arrowPoint = NSZeroPoint;
226 if (type_ == extension_installed_bubble::kApp) {
227 TabStripView* view = [window->cocoa_controller() tabStripView];
228 NewTabButton* button = [view getNewTabButton];
229 NSRect bounds = [button bounds];
230 NSPoint anchor = NSMakePoint(
232 NSMaxY(bounds) - extension_installed_bubble::kAppsBubbleArrowOffset);
233 arrowPoint = [button convertPoint:anchor toView:nil];
234 } else if (type_ == extension_installed_bubble::kBrowserAction ||
235 extensions::FeatureSwitch::extension_action_redesign()->
237 // If the toolbar redesign is enabled, all bubbles for extensions point to
238 // their toolbar action.
239 BrowserActionsController* controller =
240 [[window->cocoa_controller() toolbarController]
241 browserActionsController];
242 arrowPoint = [controller popupPointForId:[self extension]->id()];
243 } else if (type_ == extension_installed_bubble::kPageAction) {
244 LocationBarViewMac* locationBarView =
245 [window->cocoa_controller() locationBarBridge];
247 ExtensionAction* page_action =
248 extensions::ExtensionActionManager::Get(browser_->profile())->
249 GetPageAction(*[self extension]);
251 // Tell the location bar to show a preview of the page action icon,
252 // which would ordinarily only be displayed on a page of the appropriate
253 // type. We remove this preview when the extension installed bubble
255 locationBarView->SetPreviewEnabledPageAction(page_action, true);
256 pageActionPreviewShowing_ = YES;
258 // Find the center of the bottom of the page action icon.
259 arrowPoint = locationBarView->GetPageActionBubblePoint(page_action);
260 } else if (type_ == extension_installed_bubble::kOmniboxKeyword) {
261 LocationBarViewMac* locationBarView =
262 [window->cocoa_controller() locationBarBridge];
263 arrowPoint = locationBarView->GetPageInfoBubblePoint();
265 DCHECK(type_ == extension_installed_bubble::kBundle ||
266 type_ == extension_installed_bubble::kGeneric);
267 // Point at the bottom of the wrench menu.
268 NSView* wrenchButton =
269 [[window->cocoa_controller() toolbarController] wrenchButton];
270 const NSRect bounds = [wrenchButton bounds];
271 NSPoint anchor = NSMakePoint(NSMidX(bounds), NSMaxY(bounds));
272 arrowPoint = [wrenchButton convertPoint:anchor toView:nil];
277 // Override -[BaseBubbleController showWindow:] to tweak bubble location and
278 // set up UI elements.
279 - (void)showWindow:(id)sender {
280 DCHECK_CURRENTLY_ON(BrowserThread::UI);
282 // Load nib and calculate height based on messages to be shown.
283 NSWindow* window = [self initializeWindow];
284 int newWindowHeight = [self calculateWindowHeight];
285 [self.bubble setFrameSize:NSMakeSize(
286 NSWidth([[window contentView] bounds]), newWindowHeight)];
287 NSSize windowDelta = NSMakeSize(
288 0, newWindowHeight - NSHeight([[window contentView] bounds]));
289 windowDelta = [[window contentView] convertSize:windowDelta toView:nil];
290 NSRect newFrame = [window frame];
291 newFrame.size.height += windowDelta.height;
292 [window setFrame:newFrame display:NO];
294 // Now that we have resized the window, adjust y pos of the messages.
295 [self setMessageFrames:newWindowHeight];
297 // Find window origin, taking into account bubble size and arrow location.
299 [self.parentWindow convertBaseToScreen:[self calculateArrowPoint]];
300 [super showWindow:sender];
303 // Finish nib loading, set arrow location and load icon into window. This
304 // function is exposed for unit testing.
305 - (NSWindow*)initializeWindow {
306 NSWindow* window = [self window]; // completes nib load
308 if (type_ == extension_installed_bubble::kOmniboxKeyword) {
309 [self.bubble setArrowLocation:info_bubble::kTopLeft];
311 [self.bubble setArrowLocation:info_bubble::kTopRight];
314 if (type_ == extension_installed_bubble::kBundle)
317 // Set appropriate icon, resizing if necessary.
318 if ([icon_ size].width > extension_installed_bubble::kIconSize) {
319 [icon_ setSize:NSMakeSize(extension_installed_bubble::kIconSize,
320 extension_installed_bubble::kIconSize)];
322 [iconImage_ setImage:icon_];
323 [iconImage_ setNeedsDisplay:YES];
327 // Calculate the height of each install message, resizing messages in their
328 // frames to fit window width. Return the new window height, based on the
329 // total of all message heights.
330 - (int)calculateWindowHeight {
331 // Adjust the window height to reflect the sum height of all messages
332 // and vertical padding.
333 int newWindowHeight = 2 * extension_installed_bubble::kOuterVerticalMargin;
335 // If type is bundle, list the extensions that were installed and those that
337 if (type_ == extension_installed_bubble::kBundle) {
338 NSInteger installedListHeight =
339 [self addExtensionList:installedHeadingMsg_
340 itemsMsg:installedItemsMsg_
341 state:BundleInstaller::Item::STATE_INSTALLED];
343 NSInteger failedListHeight =
344 [self addExtensionList:failedHeadingMsg_
345 itemsMsg:failedItemsMsg_
346 state:BundleInstaller::Item::STATE_FAILED];
348 newWindowHeight += installedListHeight + failedListHeight;
350 // Put some space between the lists if both are present.
351 if (installedListHeight > 0 && failedListHeight > 0)
352 newWindowHeight += extension_installed_bubble::kInnerVerticalMargin;
354 return newWindowHeight;
357 int sync_promo_height = 0;
358 if ([self showSyncPromo]) {
359 // First calculate the height of the sign-in promo.
360 NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
362 NSString* link(l10n_util::GetNSStringWithFixup(
363 IDS_EXTENSION_INSTALLED_SIGNIN_PROMO_LINK));
364 NSString* message(l10n_util::GetNSStringWithFixup(
365 IDS_EXTENSION_INSTALLED_SIGNIN_PROMO));
366 message = [link stringByAppendingString:message];
368 HyperlinkTextView* view = promo_.get();
369 [view setMessage:message withFont:font messageColor:[NSColor blackColor]];
370 [view addLinkRange:NSMakeRange(0, [link length])
372 linkColor:gfx::SkColorToCalibratedNSColor(
373 chrome_style::GetLinkColor())];
375 // HACK! The TextView does not report correct height even after you stuff
376 // it with text (it tells you it is single-line even if it is multiline), so
377 // here the hidden howToUse_ TextField is temporarily repurposed to
378 // calculate the correct height for the TextView.
379 [[howToUse_ cell] setAttributedStringValue:[promo_ attributedString]];
380 [GTMUILocalizerAndLayoutTweaker
381 sizeToFitFixedWidthTextField:howToUse_];
382 sync_promo_height = NSHeight([howToUse_ frame]);
385 // First part of extension installed message, the heading.
386 base::string16 extension_name =
387 base::UTF8ToUTF16([self extension]->name().c_str());
388 base::i18n::AdjustStringForLocaleDirection(&extension_name);
389 [heading_ setStringValue:l10n_util::GetNSStringF(
390 IDS_EXTENSION_INSTALLED_HEADING, extension_name)];
391 [GTMUILocalizerAndLayoutTweaker
392 sizeToFitFixedWidthTextField:heading_];
393 newWindowHeight += NSHeight([heading_ frame]) +
394 extension_installed_bubble::kInnerVerticalMargin;
396 // If type is browser/page action, include a special message about them.
397 if (type_ == extension_installed_bubble::kBrowserAction ||
398 type_ == extension_installed_bubble::kPageAction) {
399 [howToUse_ setStringValue:base::SysUTF16ToNSString(
400 installedBubble_->GetHowToUseDescription())];
401 [howToUse_ setHidden:NO];
403 setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
404 [GTMUILocalizerAndLayoutTweaker
405 sizeToFitFixedWidthTextField:howToUse_];
406 newWindowHeight += NSHeight([howToUse_ frame]) +
407 extension_installed_bubble::kInnerVerticalMargin;
410 // If type is omnibox keyword, include a special message about the keyword.
411 if (type_ == extension_installed_bubble::kOmniboxKeyword) {
412 [howToUse_ setStringValue:base::SysUTF16ToNSString(
413 installedBubble_->GetHowToUseDescription())];
414 [howToUse_ setHidden:NO];
416 setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
417 [GTMUILocalizerAndLayoutTweaker
418 sizeToFitFixedWidthTextField:howToUse_];
419 newWindowHeight += NSHeight([howToUse_ frame]) +
420 extension_installed_bubble::kInnerVerticalMargin;
423 // If type is app, hide howToManage_, and include a "show me" link in the
425 if (type_ == extension_installed_bubble::kApp) {
426 [howToManage_ setHidden:YES];
427 [appShortcutLink_ setHidden:NO];
428 newWindowHeight += 2 * extension_installed_bubble::kInnerVerticalMargin;
429 newWindowHeight += NSHeight([appShortcutLink_ frame]);
431 // Second part of extension installed message.
433 setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
434 [GTMUILocalizerAndLayoutTweaker
435 sizeToFitFixedWidthTextField:howToManage_];
436 newWindowHeight += NSHeight([howToManage_ frame]);
439 // Sync sign-in promo, if any.
440 if (sync_promo_height > 0) {
441 NSRect promo_frame = [promo_.get() frame];
442 promo_frame.size.height = sync_promo_height;
443 [promo_.get() setFrame:promo_frame];
444 newWindowHeight += extension_installed_bubble::kInnerVerticalMargin;
445 newWindowHeight += sync_promo_height;
448 if (type_ != extension_installed_bubble::kBundle &&
449 installedBubble_->has_command_keybinding()) {
450 [manageShortcutLink_ setHidden:NO];
451 [[manageShortcutLink_ cell]
452 setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
453 [[manageShortcutLink_ cell]
454 setTextColor:gfx::SkColorToCalibratedNSColor(
455 chrome_style::GetLinkColor())];
456 [GTMUILocalizerAndLayoutTweaker sizeToFitView:manageShortcutLink_];
457 newWindowHeight += extension_installed_bubble::kInnerVerticalMargin;
458 newWindowHeight += NSHeight([manageShortcutLink_ frame]);
461 return newWindowHeight;
464 - (NSInteger)addExtensionList:(NSTextField*)headingMsg
465 itemsMsg:(NSTextField*)itemsMsg
466 state:(BundleInstaller::Item::State)state {
467 base::string16 heading = bundle_->GetHeadingTextFor(state);
468 bool hidden = heading.empty();
469 [headingMsg setHidden:hidden];
470 [itemsMsg setHidden:hidden];
474 [headingMsg setStringValue:base::SysUTF16ToNSString(heading)];
475 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:headingMsg];
477 NSMutableString* joinedItems = [NSMutableString string];
478 BundleInstaller::ItemList items = bundle_->GetItemsWithState(state);
479 for (size_t i = 0; i < items.size(); ++i) {
481 [joinedItems appendString:@"\n"];
482 [joinedItems appendString:base::SysUTF16ToNSString(
483 items[i].GetNameForDisplay())];
486 [itemsMsg setStringValue:joinedItems];
487 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:itemsMsg];
489 return NSHeight([headingMsg frame]) +
490 extension_installed_bubble::kInnerVerticalMargin +
491 NSHeight([itemsMsg frame]);
494 // Adjust y-position of messages to sit properly in new window height.
495 - (void)setMessageFrames:(int)newWindowHeight {
496 if (type_ == extension_installed_bubble::kBundle) {
497 // Layout the messages from the bottom up.
498 NSTextField* msgs[] = { failedItemsMsg_, failedHeadingMsg_,
499 installedItemsMsg_, installedHeadingMsg_ };
500 NSInteger offsetFromBottom = 0;
501 BOOL isFirstVisible = YES;
502 for (size_t i = 0; i < arraysize(msgs); ++i) {
503 if ([msgs[i] isHidden])
506 NSRect frame = [msgs[i] frame];
507 NSInteger margin = isFirstVisible ?
508 extension_installed_bubble::kOuterVerticalMargin :
509 extension_installed_bubble::kInnerVerticalMargin;
511 frame.origin.y = offsetFromBottom + margin;
512 [msgs[i] setFrame:frame];
513 offsetFromBottom += NSHeight(frame) + margin;
518 // Move the close button a bit to vertically align it with the heading.
519 NSInteger closeButtonFudge = 1;
520 NSRect frame = [closeButton_ frame];
521 frame.origin.y = newWindowHeight - (NSHeight(frame) + closeButtonFudge +
522 extension_installed_bubble::kOuterVerticalMargin);
523 [closeButton_ setFrame:frame];
528 NSRect headingFrame = [heading_ frame];
529 headingFrame.origin.y = newWindowHeight - (
530 NSHeight(headingFrame) +
531 extension_installed_bubble::kOuterVerticalMargin);
532 [heading_ setFrame:headingFrame];
534 NSRect howToManageFrame = [howToManage_ frame];
535 if (!extensions::OmniboxInfo::GetKeyword([self extension]).empty() ||
536 extensions::ActionInfo::GetBrowserActionInfo([self extension]) ||
537 extensions::ActionInfo::IsVerboseInstallMessage([self extension])) {
538 // For browser actions, page actions and omnibox keyword show the
539 // 'how to use' message before the 'how to manage' message.
540 NSRect howToUseFrame = [howToUse_ frame];
541 howToUseFrame.origin.y = headingFrame.origin.y - (
542 NSHeight(howToUseFrame) +
543 extension_installed_bubble::kInnerVerticalMargin);
544 [howToUse_ setFrame:howToUseFrame];
546 howToManageFrame.origin.y = howToUseFrame.origin.y - (
547 NSHeight(howToManageFrame) +
548 extension_installed_bubble::kInnerVerticalMargin);
550 howToManageFrame.origin.y = NSMinY(headingFrame) - (
551 NSHeight(howToManageFrame) +
552 extension_installed_bubble::kInnerVerticalMargin);
554 [howToManage_ setFrame:howToManageFrame];
556 NSRect frame = howToManageFrame;
557 if ([self showSyncPromo]) {
558 frame = [promo_.get() frame];
559 frame.origin.y = NSMinY(howToManageFrame) -
560 (NSHeight(frame) + extension_installed_bubble::kInnerVerticalMargin);
561 [promo_.get() setFrame:frame];
564 if (![manageShortcutLink_ isHidden]) {
565 NSRect manageShortcutFrame = [manageShortcutLink_ frame];
566 manageShortcutFrame.origin.y = NSMinY(frame) - (
567 NSHeight(manageShortcutFrame) +
568 extension_installed_bubble::kInnerVerticalMargin);
569 // Right-align the link.
570 manageShortcutFrame.origin.x = NSMaxX(frame) -
571 NSWidth(manageShortcutFrame);
572 [manageShortcutLink_ setFrame:manageShortcutFrame];
576 // Exposed for unit testing.
577 - (NSRect)headingFrame {
578 return [heading_ frame];
581 - (NSRect)frameOfHowToUse {
582 return [howToUse_ frame];
585 - (NSRect)frameOfHowToManage {
586 return [howToManage_ frame];
589 - (NSRect)frameOfSigninPromo {
590 return [promo_ frame];
593 - (NSButton*)appInstalledShortcutLink {
594 return appShortcutLink_;
597 - (IBAction)onManageShortcutClicked:(id)sender {
599 std::string configure_url = chrome::kChromeUIExtensionsURL;
600 configure_url += chrome::kExtensionConfigureCommandsSubPage;
601 chrome::NavigateParams params(chrome::GetSingletonTabNavigateParams(
602 browser_, GURL(configure_url)));
603 chrome::Navigate(¶ms);
606 - (IBAction)onAppShortcutClicked:(id)sender {
607 scoped_ptr<extensions::ExtensionInstallUI> install_ui(
608 extensions::CreateExtensionInstallUI(browser_->profile()));
609 install_ui->OpenAppInstalledUI([self extension]->id());
612 - (void)awakeFromNib {
615 [self initializeLabel];