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_install_view_controller.h"
7 #include "base/auto_reset.h"
8 #include "base/i18n/rtl.h"
9 #include "base/mac/bundle_locations.h"
10 #include "base/mac/mac_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/extensions/bundle_installer.h"
15 #import "chrome/browser/ui/chrome_style.h"
16 #include "chrome/common/extensions/extension_constants.h"
17 #include "content/public/browser/page_navigator.h"
18 #include "extensions/common/extension.h"
19 #include "grit/generated_resources.h"
20 #include "skia/ext/skia_utils_mac.h"
21 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
22 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/l10n/l10n_util_mac.h"
25 #include "ui/gfx/image/image_skia_util_mac.h"
27 using content::OpenURLParams;
28 using content::Referrer;
29 using extensions::BundleInstaller;
33 // A collection of attributes (bitmask) for how to draw a cell, the expand
34 // marker and the text in the cell.
35 enum CellAttributesMask {
37 kNoExpandMarker = 1 << 1,
39 kAutoExpandCell = 1 << 3,
40 kUseCustomLinkCell = 1 << 4,
44 typedef NSUInteger CellAttributes;
48 @interface ExtensionInstallViewController ()
49 - (BOOL)isBundleInstall;
50 - (BOOL)hasWebstoreData;
51 - (void)appendRatingStar:(const gfx::ImageSkia*)skiaImage;
52 - (void)onOutlineViewRowCountDidChange;
53 - (NSDictionary*)buildItemWithTitle:(NSString*)title
54 cellAttributes:(CellAttributes)cellAttributes
55 children:(NSArray*)children;
56 - (NSDictionary*)buildDetailToggleItem:(size_t)type
57 permissionsDetailIndex:(size_t)index;
58 - (NSArray*)buildWarnings:(const ExtensionInstallPrompt::Prompt&)prompt;
59 - (void)updateViewFrame:(NSRect)frame;
62 @interface DetailToggleHyperlinkButtonCell : HyperlinkButtonCell {
63 NSUInteger permissionsDetailIndex_;
64 ExtensionInstallPrompt::DetailsType permissionsDetailType_;
65 SEL linkClickedAction_;
68 @property(assign, nonatomic) NSUInteger permissionsDetailIndex;
69 @property(assign, nonatomic)
70 ExtensionInstallPrompt::DetailsType permissionsDetailType;
71 @property(assign, nonatomic) SEL linkClickedAction;
77 // Padding above the warnings separator, we must also subtract this when hiding
79 const CGFloat kWarningsSeparatorPadding = 14;
81 // The left padding for the link cell.
82 const CGFloat kLinkCellPaddingLeft = 3;
84 // Maximum height we will adjust controls to when trying to accomodate their
86 const CGFloat kMaxControlHeight = 250;
88 NSString* const kTitleKey = @"title";
89 NSString* const kChildrenKey = @"children";
90 NSString* const kCellAttributesKey = @"cellAttributes";
91 NSString* const kPermissionsDetailIndex = @"permissionsDetailIndex";
92 NSString* const kPermissionsDetailType = @"permissionsDetailType";
94 // Adjust the |control|'s height so that its content is not clipped.
95 // This also adds the change in height to the |totalOffset| and shifts the
96 // control down by that amount.
97 void OffsetControlVerticallyToFitContent(NSControl* control,
98 CGFloat* totalOffset) {
99 // Adjust the control's height so that its content is not clipped.
100 NSRect currentRect = [control frame];
101 NSRect fitRect = currentRect;
102 fitRect.size.height = kMaxControlHeight;
103 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height;
104 CGFloat offset = desiredHeight - NSHeight(currentRect);
106 [control setFrameSize:NSMakeSize(NSWidth(currentRect),
107 NSHeight(currentRect) + offset)];
109 *totalOffset += offset;
111 // Move the control vertically by the new total offset.
112 NSPoint origin = [control frame].origin;
113 origin.y -= *totalOffset;
114 [control setFrameOrigin:origin];
117 // Gets the desired height of |outlineView|. Simply using the view's frame
118 // doesn't work if an animation is pending.
119 CGFloat GetDesiredOutlineViewHeight(NSOutlineView* outlineView) {
121 for (NSInteger i = 0; i < [outlineView numberOfRows]; ++i)
122 height += NSHeight([outlineView rectOfRow:i]);
126 void OffsetOutlineViewVerticallyToFitContent(NSOutlineView* outlineView,
127 CGFloat* totalOffset) {
128 NSScrollView* scrollView = [outlineView enclosingScrollView];
129 NSRect frame = [scrollView frame];
130 CGFloat desiredHeight = GetDesiredOutlineViewHeight(outlineView);
131 if (desiredHeight > kMaxControlHeight)
132 desiredHeight = kMaxControlHeight;
133 CGFloat offset = desiredHeight - NSHeight(frame);
134 frame.size.height += offset;
136 *totalOffset += offset;
138 // Move the control vertically by the new total offset.
139 frame.origin.y -= *totalOffset;
140 [scrollView setFrame:frame];
143 void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) {
144 ExtensionInstallViewController* controller =
145 static_cast<ExtensionInstallViewController*>(data);
146 [controller appendRatingStar:skiaImage];
149 void DrawBulletInFrame(NSRect frame) {
151 rect.size.width = std::min(NSWidth(frame), NSHeight(frame)) * 0.25;
152 rect.size.height = NSWidth(rect);
153 rect.origin.x = frame.origin.x + (NSWidth(frame) - NSWidth(rect)) / 2.0;
154 rect.origin.y = frame.origin.y + (NSHeight(frame) - NSHeight(rect)) / 2.0;
155 rect = NSIntegralRect(rect);
157 [[NSColor colorWithCalibratedWhite:0.0 alpha:0.42] set];
158 [[NSBezierPath bezierPathWithOvalInRect:rect] fill];
161 bool HasAttribute(id item, CellAttributesMask attributeMask) {
162 return [[item objectForKey:kCellAttributesKey] intValue] & attributeMask;
167 @implementation ExtensionInstallViewController
169 @synthesize iconView = iconView_;
170 @synthesize titleField = titleField_;
171 @synthesize itemsField = itemsField_;
172 @synthesize cancelButton = cancelButton_;
173 @synthesize okButton = okButton_;
174 @synthesize outlineView = outlineView_;
175 @synthesize warningsSeparator = warningsSeparator_;
176 @synthesize ratingStars = ratingStars_;
177 @synthesize ratingCountField = ratingCountField_;
178 @synthesize userCountField = userCountField_;
179 @synthesize storeLinkButton = storeLinkButton_;
181 - (id)initWithNavigator:(content::PageNavigator*)navigator
182 delegate:(ExtensionInstallPrompt::Delegate*)delegate
183 prompt:(const ExtensionInstallPrompt::Prompt&)prompt {
184 // We use a different XIB in the case of bundle installs, installs with
185 // webstore data, or no permission warnings. These are laid out nicely for
186 // the data they display.
187 NSString* nibName = nil;
188 if (prompt.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT) {
189 nibName = @"ExtensionInstallPromptBundle";
190 } else if (prompt.has_webstore_data()) {
191 nibName = @"ExtensionInstallPromptWebstoreData";
192 } else if (!prompt.ShouldShowPermissions() &&
193 prompt.GetOAuthIssueCount() == 0 &&
194 prompt.GetRetainedFileCount() == 0) {
195 nibName = @"ExtensionInstallPromptNoWarnings";
197 nibName = @"ExtensionInstallPrompt";
200 if ((self = [super initWithNibName:nibName
201 bundle:base::mac::FrameworkBundle()])) {
202 navigator_ = navigator;
203 delegate_ = delegate;
204 prompt_.reset(new ExtensionInstallPrompt::Prompt(prompt));
205 warnings_.reset([[self buildWarnings:prompt] retain]);
210 - (IBAction)storeLinkClicked:(id)sender {
211 GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
212 prompt_->extension()->id());
213 navigator_->OpenURL(OpenURLParams(
214 store_url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK,
217 delegate_->InstallUIAbort(/*user_initiated=*/true);
220 - (IBAction)cancel:(id)sender {
221 delegate_->InstallUIAbort(/*user_initiated=*/true);
224 - (IBAction)ok:(id)sender {
225 delegate_->InstallUIProceed();
228 - (void)awakeFromNib {
229 // Set control labels.
230 [titleField_ setStringValue:base::SysUTF16ToNSString(prompt_->GetHeading())];
232 if (prompt_->HasAcceptButtonLabel()) {
233 [okButton_ setTitle:base::SysUTF16ToNSString(
234 prompt_->GetAcceptButtonLabel())];
236 [okButton_ removeFromSuperview];
237 okButtonRect = [okButton_ frame];
240 [cancelButton_ setTitle:prompt_->HasAbortButtonLabel() ?
241 base::SysUTF16ToNSString(prompt_->GetAbortButtonLabel()) :
242 l10n_util::GetNSString(IDS_CANCEL)];
243 if ([self hasWebstoreData]) {
244 prompt_->AppendRatingStars(AppendRatingStarsShim, self);
245 [ratingCountField_ setStringValue:base::SysUTF16ToNSString(
246 prompt_->GetRatingCount())];
247 [userCountField_ setStringValue:base::SysUTF16ToNSString(
248 prompt_->GetUserCount())];
249 [[storeLinkButton_ cell] setUnderlineOnHover:YES];
250 [[storeLinkButton_ cell] setTextColor:
251 gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor())];
254 // The bundle install dialog has no icon.
255 if (![self isBundleInstall])
256 [iconView_ setImage:prompt_->icon().ToNSImage()];
258 // The dialog is laid out in the NIB exactly how we want it assuming that
259 // each label fits on one line. However, for each label, we want to allow
260 // wrapping onto multiple lines. So we accumulate an offset by measuring how
261 // big each label wants to be, and comparing it to how big it actually is.
262 // Then we shift each label down and resize by the appropriate amount, then
263 // finally resize the window.
264 CGFloat totalOffset = 0.0;
266 OffsetControlVerticallyToFitContent(titleField_, &totalOffset);
268 // Resize |okButton_| and |cancelButton_| to fit the button labels, but keep
269 // them right-aligned.
272 buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:okButton_];
273 if (buttonDelta.width) {
274 [okButton_ setFrame:NSOffsetRect([okButton_ frame],
275 -buttonDelta.width, 0)];
276 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame],
277 -buttonDelta.width, 0)];
280 // Make |cancelButton_| right-aligned in the absence of |okButton_|.
281 NSRect cancelButtonRect = [cancelButton_ frame];
282 cancelButtonRect.origin.x =
283 NSMaxX(okButtonRect) - NSWidth(cancelButtonRect);
284 [cancelButton_ setFrame:cancelButtonRect];
286 buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_];
287 if (buttonDelta.width) {
288 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame],
289 -buttonDelta.width, 0)];
292 if ([self isBundleInstall]) {
293 // We display the list of extension names as a simple text string, seperated
295 BundleInstaller::ItemList items = prompt_->bundle()->GetItemsWithState(
296 BundleInstaller::Item::STATE_PENDING);
298 NSMutableString* joinedItems = [NSMutableString string];
299 for (size_t i = 0; i < items.size(); ++i) {
301 [joinedItems appendString:@"\n"];
302 [joinedItems appendString:base::SysUTF16ToNSString(
303 items[i].GetNameForDisplay())];
305 [itemsField_ setStringValue:joinedItems];
307 // Adjust the controls to fit the list of extensions.
308 OffsetControlVerticallyToFitContent(itemsField_, &totalOffset);
311 // If there are any warnings or OAuth issues, then we have to do some special
313 if (prompt_->ShouldShowPermissions() || prompt_->GetOAuthIssueCount() > 0 ||
314 prompt_->GetRetainedFileCount() > 0) {
315 NSSize spacing = [outlineView_ intercellSpacing];
318 [outlineView_ setIntercellSpacing:spacing];
319 [[[[outlineView_ tableColumns] objectAtIndex:0] dataCell] setWraps:YES];
320 for (id item in warnings_.get())
321 [self expandItemAndChildren:item];
323 // Adjust the outline view to fit the warnings.
324 OffsetOutlineViewVerticallyToFitContent(outlineView_, &totalOffset);
325 } else if ([self hasWebstoreData] || [self isBundleInstall]) {
326 // Installs with webstore data and bundle installs that don't have a
327 // permissions section need to hide controls related to that and shrink the
328 // window by the space they take up.
329 NSRect hiddenRect = NSUnionRect([warningsSeparator_ frame],
330 [[outlineView_ enclosingScrollView] frame]);
331 [warningsSeparator_ setHidden:YES];
332 [[outlineView_ enclosingScrollView] setHidden:YES];
333 totalOffset -= NSHeight(hiddenRect) + kWarningsSeparatorPadding;
336 // If necessary, adjust the window size.
338 NSRect currentRect = [[self view] bounds];
339 currentRect.size.height += totalOffset;
340 [self updateViewFrame:currentRect];
344 - (BOOL)isBundleInstall {
345 return prompt_->type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
348 - (BOOL)hasWebstoreData {
349 return prompt_->has_webstore_data();
352 - (void)appendRatingStar:(const gfx::ImageSkia*)skiaImage {
353 NSImage* image = gfx::NSImageFromImageSkiaWithColorSpace(
354 *skiaImage, base::mac::GetSystemColorSpace());
355 NSRect frame = NSMakeRect(0, 0, skiaImage->width(), skiaImage->height());
356 base::scoped_nsobject<NSImageView> view(
357 [[NSImageView alloc] initWithFrame:frame]);
358 [view setImage:image];
360 // Add this star after all the other ones
361 CGFloat maxStarRight = 0;
362 if ([[ratingStars_ subviews] count]) {
363 maxStarRight = NSMaxX([[[ratingStars_ subviews] lastObject] frame]);
365 NSRect starBounds = NSMakeRect(maxStarRight, 0,
366 skiaImage->width(), skiaImage->height());
367 [view setFrame:starBounds];
368 [ratingStars_ addSubview:view];
371 - (void)onOutlineViewRowCountDidChange {
372 // Force the outline view to update.
373 [outlineView_ reloadData];
375 CGFloat totalOffset = 0.0;
376 OffsetOutlineViewVerticallyToFitContent(outlineView_, &totalOffset);
378 NSRect currentRect = [[self view] bounds];
379 currentRect.size.height += totalOffset;
380 [self updateViewFrame:currentRect];
384 - (id)outlineView:(NSOutlineView*)outlineView
385 child:(NSInteger)index
388 return [warnings_ objectAtIndex:index];
389 if ([item isKindOfClass:[NSDictionary class]])
390 return [[item objectForKey:kChildrenKey] objectAtIndex:index];
395 - (BOOL)outlineView:(NSOutlineView*)outlineView
396 isItemExpandable:(id)item {
397 return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
400 - (NSInteger)outlineView:(NSOutlineView*)outlineView
401 numberOfChildrenOfItem:(id)item {
403 return [warnings_ count];
405 if ([item isKindOfClass:[NSDictionary class]])
406 return [[item objectForKey:kChildrenKey] count];
412 - (id)outlineView:(NSOutlineView*)outlineView
413 objectValueForTableColumn:(NSTableColumn *)tableColumn
415 return [item objectForKey:kTitleKey];
418 - (BOOL)outlineView:(NSOutlineView *)outlineView
419 shouldExpandItem:(id)item {
420 return HasAttribute(item, kCanExpand);
423 - (void)outlineViewItemDidExpand:sender {
424 // Call via run loop to avoid animation glitches.
425 [self performSelector:@selector(onOutlineViewRowCountDidChange)
430 - (void)outlineViewItemDidCollapse:sender {
431 // Call via run loop to avoid animation glitches.
432 [self performSelector:@selector(onOutlineViewRowCountDidChange)
437 - (CGFloat)outlineView:(NSOutlineView *)outlineView
438 heightOfRowByItem:(id)item {
439 // Prevent reentrancy due to the frameOfCellAtColumn:row: call below.
440 if (isComputingRowHeight_)
442 base::AutoReset<BOOL> reset(&isComputingRowHeight_, YES);
444 NSCell* cell = [[[outlineView_ tableColumns] objectAtIndex:0] dataCell];
445 [cell setStringValue:[item objectForKey:kTitleKey]];
446 NSRect bounds = NSZeroRect;
447 NSInteger row = [outlineView_ rowForItem:item];
448 bounds.size.width = NSWidth([outlineView_ frameOfCellAtColumn:0 row:row]);
449 bounds.size.height = kMaxControlHeight;
451 return [cell cellSizeForBounds:bounds].height;
454 - (BOOL)outlineView:(NSOutlineView*)outlineView
455 shouldShowOutlineCellForItem:(id)item {
456 return !HasAttribute(item, kNoExpandMarker);
459 - (BOOL)outlineView:(NSOutlineView*)outlineView
460 shouldTrackCell:(NSCell*)cell
461 forTableColumn:(NSTableColumn*)tableColumn
463 return HasAttribute(item, kUseCustomLinkCell);
466 - (void)outlineView:(NSOutlineView*)outlineView
467 willDisplayCell:(id)cell
468 forTableColumn:(NSTableColumn *)tableColumn
470 if (HasAttribute(item, kBoldText))
471 [cell setFont:[NSFont boldSystemFontOfSize:12.0]];
473 [cell setFont:[NSFont systemFontOfSize:12.0]];
476 - (void)outlineView:(NSOutlineView *)outlineView
477 willDisplayOutlineCell:(id)cell
478 forTableColumn:(NSTableColumn *)tableColumn
480 if (HasAttribute(item, kNoExpandMarker)) {
481 [cell setImagePosition:NSNoImage];
485 if (HasAttribute(item, kUseBullet)) {
486 // Replace disclosure triangles with bullet lists for leaf nodes.
487 [cell setImagePosition:NSNoImage];
488 DrawBulletInFrame([outlineView_ frameOfOutlineCellAtRow:
489 [outlineView_ rowForItem:item]]);
493 // Reset image to default value.
494 [cell setImagePosition:NSImageOverlaps];
497 - (BOOL)outlineView:(NSOutlineView *)outlineView
498 shouldSelectItem:(id)item {
502 - (NSCell*)outlineView:(NSOutlineView*)outlineView
503 dataCellForTableColumn:(NSTableColumn*)tableColumn
505 if (HasAttribute(item, kUseCustomLinkCell)) {
506 base::scoped_nsobject<DetailToggleHyperlinkButtonCell> cell(
507 [[DetailToggleHyperlinkButtonCell alloc] initTextCell:@""]);
508 [cell setTarget:self];
509 [cell setLinkClickedAction:@selector(onToggleDetailsLinkClicked:)];
510 [cell setAlignment:NSLeftTextAlignment];
511 [cell setUnderlineOnHover:YES];
513 gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor())];
515 size_t detailsIndex =
516 [[item objectForKey:kPermissionsDetailIndex] unsignedIntegerValue];
517 [cell setPermissionsDetailIndex:detailsIndex];
519 ExtensionInstallPrompt::DetailsType detailsType =
520 static_cast<ExtensionInstallPrompt::DetailsType>(
521 [[item objectForKey:kPermissionsDetailType] unsignedIntegerValue]);
522 [cell setPermissionsDetailType:detailsType];
524 if (prompt_->GetIsShowingDetails(detailsType, detailsIndex)) {
526 l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_HIDE_DETAILS)];
529 l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_SHOW_DETAILS)];
532 return cell.autorelease();
534 return [tableColumn dataCell];
538 - (void)expandItemAndChildren:(id)item {
539 if (HasAttribute(item, kAutoExpandCell))
540 [outlineView_ expandItem:item expandChildren:NO];
542 for (id child in [item objectForKey:kChildrenKey])
543 [self expandItemAndChildren:child];
546 - (void)onToggleDetailsLinkClicked:(id)sender {
547 size_t index = [sender permissionsDetailIndex];
548 ExtensionInstallPrompt::DetailsType type = [sender permissionsDetailType];
549 prompt_->SetIsShowingDetails(
550 type, index, !prompt_->GetIsShowingDetails(type, index));
552 warnings_.reset([[self buildWarnings:*prompt_] retain]);
553 [outlineView_ reloadData];
555 for (id item in warnings_.get())
556 [self expandItemAndChildren:item];
559 - (NSDictionary*)buildItemWithTitle:(NSString*)title
560 cellAttributes:(CellAttributes)cellAttributes
561 children:(NSArray*)children {
562 if (!children || ([children count] == 0 && cellAttributes & kUseBullet)) {
563 // Add a dummy child even though this is a leaf node. This will cause
564 // the outline view to show a disclosure triangle for this item.
565 // This is later overriden in willDisplayOutlineCell: to draw a bullet
566 // instead. (The bullet could be placed in the title instead but then
567 // the bullet wouldn't line up with disclosure triangles of sibling nodes.)
568 children = [NSArray arrayWithObject:[NSDictionary dictionary]];
570 cellAttributes = cellAttributes | kCanExpand;
575 kChildrenKey : children,
576 kCellAttributesKey : [NSNumber numberWithInt:cellAttributes],
577 kPermissionsDetailIndex : @0ul,
578 kPermissionsDetailType : @0ul,
582 - (NSDictionary*)buildDetailToggleItem:(size_t)type
583 permissionsDetailIndex:(size_t)index {
586 kChildrenKey : @[ @{} ],
587 kCellAttributesKey : [NSNumber numberWithInt:kUseCustomLinkCell |
589 kPermissionsDetailIndex : [NSNumber numberWithUnsignedInteger:index],
590 kPermissionsDetailType : [NSNumber numberWithUnsignedInteger:type],
594 - (NSArray*)buildWarnings:(const ExtensionInstallPrompt::Prompt&)prompt {
595 NSMutableArray* warnings = [NSMutableArray array];
596 NSString* heading = nil;
598 ExtensionInstallPrompt::DetailsType type =
599 ExtensionInstallPrompt::PERMISSIONS_DETAILS;
600 if (prompt.ShouldShowPermissions()) {
601 NSMutableArray* children = [NSMutableArray array];
602 if (prompt.GetPermissionCount() > 0) {
603 for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
605 [self buildItemWithTitle:SysUTF16ToNSString(prompt.GetPermission(i))
606 cellAttributes:kUseBullet
609 // If there are additional details, add them below this item.
610 if (!prompt.GetPermissionsDetails(i).empty()) {
611 if (prompt.GetIsShowingDetails(
612 ExtensionInstallPrompt::PERMISSIONS_DETAILS, i)) {
614 [self buildItemWithTitle:SysUTF16ToNSString(
615 prompt.GetPermissionsDetails(i))
616 cellAttributes:kNoExpandMarker
620 // Add a row for the link.
622 [self buildDetailToggleItem:type permissionsDetailIndex:i]];
626 heading = SysUTF16ToNSString(prompt.GetPermissionsHeading());
629 [self buildItemWithTitle:
630 l10n_util::GetNSString(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS)
631 cellAttributes:kUseBullet
636 [warnings addObject:[self
637 buildItemWithTitle:heading
638 cellAttributes:kBoldText | kAutoExpandCell | kNoExpandMarker
642 if (prompt.GetOAuthIssueCount() > 0) {
643 type = ExtensionInstallPrompt::OAUTH_DETAILS;
645 NSMutableArray* children = [NSMutableArray array];
647 for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
648 NSMutableArray* details = [NSMutableArray array];
649 const IssueAdviceInfoEntry& issue = prompt.GetOAuthIssue(i);
650 if (!issue.details.empty() && prompt.GetIsShowingDetails(type, i)) {
651 for (size_t j = 0; j < issue.details.size(); ++j) {
653 [self buildItemWithTitle:SysUTF16ToNSString(issue.details[j])
654 cellAttributes:kNoExpandMarker
660 [self buildItemWithTitle:SysUTF16ToNSString(issue.description)
661 cellAttributes:kUseBullet | kAutoExpandCell
664 if (!issue.details.empty()) {
665 // Add a row for the link.
667 [self buildDetailToggleItem:type permissionsDetailIndex:i]];
672 [self buildItemWithTitle:SysUTF16ToNSString(prompt.GetOAuthHeading())
673 cellAttributes:kBoldText | kAutoExpandCell| kNoExpandMarker
677 if (prompt.GetRetainedFileCount() > 0) {
678 type = ExtensionInstallPrompt::RETAINED_FILES_DETAILS;
680 NSMutableArray* children = [NSMutableArray array];
682 if (prompt.GetIsShowingDetails(type, 0)) {
683 for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i) {
685 [self buildItemWithTitle:SysUTF16ToNSString(
686 prompt.GetRetainedFile(i))
687 cellAttributes:kUseBullet
693 [self buildItemWithTitle:SysUTF16ToNSString(
694 prompt.GetRetainedFilesHeading())
695 cellAttributes:kBoldText | kAutoExpandCell | kNoExpandMarker
698 // Add a row for the link.
700 [self buildDetailToggleItem:type permissionsDetailIndex:0]];
706 - (void)updateViewFrame:(NSRect)frame {
707 NSWindow* window = [[self view] window];
708 [window setFrame:[window frameRectForContentRect:frame] display:YES];
709 [[self view] setFrame:frame];
715 @implementation DetailToggleHyperlinkButtonCell
717 @synthesize permissionsDetailIndex = permissionsDetailIndex_;
718 @synthesize permissionsDetailType = permissionsDetailType_;
719 @synthesize linkClickedAction = linkClickedAction_;
721 + (BOOL)prefersTrackingUntilMouseUp {
725 - (NSRect)drawingRectForBounds:(NSRect)rect {
726 NSRect rectInset = NSMakeRect(rect.origin.x + kLinkCellPaddingLeft,
728 rect.size.width - kLinkCellPaddingLeft,
730 return [super drawingRectForBounds:rectInset];
733 - (NSUInteger)hitTestForEvent:(NSEvent*)event
734 inRect:(NSRect)cellFrame
735 ofView:(NSView*)controlView {
736 NSUInteger hitTestResult =
737 [super hitTestForEvent:event inRect:cellFrame ofView:controlView];
738 if ((hitTestResult & NSCellHitContentArea) != 0)
739 hitTestResult |= NSCellHitTrackableArea;
740 return hitTestResult;
743 - (void)handleLinkClicked {
744 [NSApp sendAction:linkClickedAction_ to:[self target] from:self];
747 - (BOOL)trackMouse:(NSEvent*)event
748 inRect:(NSRect)cellFrame
749 ofView:(NSView*)controlView
750 untilMouseUp:(BOOL)flag {
752 NSUInteger hitTestResult =
753 [self hitTestForEvent:event inRect:cellFrame ofView:controlView];
754 if ((hitTestResult & NSCellHitContentArea) != 0) {
755 result = [super trackMouse:event
759 event = [NSApp currentEvent];
761 [self hitTestForEvent:event inRect:cellFrame ofView:controlView];
762 if ((hitTestResult & NSCellHitContentArea) != 0)
763 [self handleLinkClicked];
768 - (NSArray*)accessibilityActionNames {
769 return [[super accessibilityActionNames]
770 arrayByAddingObject:NSAccessibilityPressAction];
773 - (void)accessibilityPerformAction:(NSString*)action {
774 if ([action isEqualToString:NSAccessibilityPressAction])
775 [self handleLinkClicked];
777 [super accessibilityPerformAction:action];