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/content_settings/collected_cookies_mac.h" 6 7 #include <vector> 8 9 #import "base/mac/mac_util.h" 10 #include "base/sys_string_conversions.h" 11 #include "chrome/browser/profiles/profile.h" 12 #import "chrome/browser/ui/cocoa/content_settings/cookie_details_view_controller.h" 13 #import "chrome/browser/ui/cocoa/vertical_gradient_view.h" 14 #include "chrome/browser/ui/collected_cookies_infobar_delegate.h" 15 #include "content/browser/tab_contents/tab_contents.h" 16 #include "content/common/notification_details.h" 17 #include "content/common/notification_source.h" 18 #include "grit/generated_resources.h" 19 #include "grit/theme_resources.h" 20 #include "skia/ext/skia_utils_mac.h" 21 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" 22 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" 23 #include "third_party/apple/ImageAndTextCell.h" 24 #include "third_party/skia/include/core/SkBitmap.h" 25 #include "ui/base/l10n/l10n_util_mac.h" 26 #include "ui/base/resource/resource_bundle.h" 27 #include "ui/gfx/image.h" 28 29 namespace { 30 // Colors for the infobar. 31 const double kBannerGradientColorTop[3] = 32 {255.0 / 255.0, 242.0 / 255.0, 183.0 / 255.0}; 33 const double kBannerGradientColorBottom[3] = 34 {250.0 / 255.0, 230.0 / 255.0, 145.0 / 255.0}; 35 const double kBannerStrokeColor = 135.0 / 255.0; 36 37 enum TabViewItemIndices { 38 kAllowedCookiesTabIndex = 0, 39 kBlockedCookiesTabIndex 40 }; 41 42 } // namespace 43 44 #pragma mark Bridge between the constrained window delegate and the sheet 45 46 // The delegate used to forward the events from the sheet to the constrained 47 // window delegate. 48 @interface CollectedCookiesSheetBridge : NSObject { 49 CollectedCookiesMac* collectedCookies_; // weak 50 } 51 - (id)initWithCollectedCookiesMac:(CollectedCookiesMac*)collectedCookies; 52 - (void)sheetDidEnd:(NSWindow*)sheet 53 returnCode:(int)returnCode 54 contextInfo:(void*)contextInfo; 55 @end 56 57 @implementation CollectedCookiesSheetBridge 58 - (id)initWithCollectedCookiesMac:(CollectedCookiesMac*)collectedCookies { 59 if ((self = [super init])) { 60 collectedCookies_ = collectedCookies; 61 } 62 return self; 63 } 64 65 - (void)sheetDidEnd:(NSWindow*)sheet 66 returnCode:(int)returnCode 67 contextInfo:(void*)contextInfo { 68 collectedCookies_->OnSheetDidEnd(sheet); 69 } 70 @end 71 72 #pragma mark Constrained window delegate 73 74 CollectedCookiesMac::CollectedCookiesMac(NSWindow* parent, 75 TabContents* tab_contents) 76 : ConstrainedWindowMacDelegateCustomSheet( 77 [[[CollectedCookiesSheetBridge alloc] 78 initWithCollectedCookiesMac:this] autorelease], 79 @selector(sheetDidEnd:returnCode:contextInfo:)), 80 tab_contents_(tab_contents) { 81 TabSpecificContentSettings* content_settings = 82 tab_contents->GetTabSpecificContentSettings(); 83 registrar_.Add(this, NotificationType::COLLECTED_COOKIES_SHOWN, 84 Source<TabSpecificContentSettings>(content_settings)); 85 86 sheet_controller_ = [[CollectedCookiesWindowController alloc] 87 initWithTabContents:tab_contents]; 88 89 set_sheet([sheet_controller_ window]); 90 91 window_ = tab_contents->CreateConstrainedDialog(this); 92 } 93 94 CollectedCookiesMac::~CollectedCookiesMac() { 95 NSWindow* window = [sheet_controller_ window]; 96 if (window_ && window && is_sheet_open()) { 97 window_ = NULL; 98 [NSApp endSheet:window]; 99 } 100 } 101 102 void CollectedCookiesMac::DeleteDelegate() { 103 delete this; 104 } 105 106 void CollectedCookiesMac::Observe(NotificationType type, 107 const NotificationSource& source, 108 const NotificationDetails& details) { 109 DCHECK(type == NotificationType::COLLECTED_COOKIES_SHOWN); 110 DCHECK_EQ(Source<TabSpecificContentSettings>(source).ptr(), 111 tab_contents_->GetTabSpecificContentSettings()); 112 window_->CloseConstrainedWindow(); 113 } 114 115 void CollectedCookiesMac::OnSheetDidEnd(NSWindow* sheet) { 116 [sheet orderOut:sheet_controller_]; 117 if (window_) 118 window_->CloseConstrainedWindow(); 119 } 120 121 #pragma mark Window Controller 122 123 @interface CollectedCookiesWindowController(Private) 124 -(void)showInfoBarForDomain:(const string16&)domain 125 setting:(ContentSetting)setting; 126 -(void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting; 127 -(void)animateInfoBar; 128 @end 129 130 @implementation CollectedCookiesWindowController 131 132 @synthesize allowedCookiesButtonsEnabled = 133 allowedCookiesButtonsEnabled_; 134 @synthesize blockedCookiesButtonsEnabled = 135 blockedCookiesButtonsEnabled_; 136 137 @synthesize allowedTreeController = allowedTreeController_; 138 @synthesize blockedTreeController = blockedTreeController_; 139 140 - (id)initWithTabContents:(TabContents*)tabContents { 141 DCHECK(tabContents); 142 143 NSString* nibpath = 144 [base::mac::MainAppBundle() pathForResource:@"CollectedCookies" 145 ofType:@"nib"]; 146 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { 147 tabContents_ = tabContents; 148 [self loadTreeModelFromTabContents]; 149 150 animation_.reset([[NSViewAnimation alloc] init]); 151 [animation_ setAnimationBlockingMode:NSAnimationNonblocking]; 152 } 153 return self; 154 } 155 156 - (void)awakeFromNib { 157 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 158 NSImage* infoIcon = rb.GetNativeImageNamed(IDR_INFO); 159 DCHECK(infoIcon); 160 [infoBarIcon_ setImage:infoIcon]; 161 162 // Initialize the banner gradient and stroke color. 163 NSColor* bannerStartingColor = 164 [NSColor colorWithCalibratedRed:kBannerGradientColorTop[0] 165 green:kBannerGradientColorTop[1] 166 blue:kBannerGradientColorTop[2] 167 alpha:1.0]; 168 NSColor* bannerEndingColor = 169 [NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0] 170 green:kBannerGradientColorBottom[1] 171 blue:kBannerGradientColorBottom[2] 172 alpha:1.0]; 173 scoped_nsobject<NSGradient> bannerGradient( 174 [[NSGradient alloc] initWithStartingColor:bannerStartingColor 175 endingColor:bannerEndingColor]); 176 [infoBar_ setGradient:bannerGradient]; 177 178 NSColor* bannerStrokeColor = 179 [NSColor colorWithCalibratedWhite:kBannerStrokeColor 180 alpha:1.0]; 181 [infoBar_ setStrokeColor:bannerStrokeColor]; 182 183 // Change the label of the blocked cookies part if necessary. 184 if (tabContents_->profile()->GetHostContentSettingsMap()-> 185 BlockThirdPartyCookies()) { 186 [blockedCookiesText_ setStringValue:l10n_util::GetNSString( 187 IDS_COLLECTED_COOKIES_BLOCKED_THIRD_PARTY_BLOCKING_ENABLED)]; 188 CGFloat textDeltaY = [GTMUILocalizerAndLayoutTweaker 189 sizeToFitFixedWidthTextField:blockedCookiesText_]; 190 191 // Shrink the blocked cookies outline view. 192 NSRect frame = [blockedScrollView_ frame]; 193 frame.size.height -= textDeltaY; 194 [blockedScrollView_ setFrame:frame]; 195 196 // Move the label down so it actually fits. 197 frame = [blockedCookiesText_ frame]; 198 frame.origin.y -= textDeltaY; 199 [blockedCookiesText_ setFrame:frame]; 200 } 201 202 detailsViewController_.reset([[CookieDetailsViewController alloc] init]); 203 204 NSView* detailView = [detailsViewController_.get() view]; 205 NSRect viewFrameRect = [cookieDetailsViewPlaceholder_ frame]; 206 [[detailsViewController_.get() view] setFrame:viewFrameRect]; 207 [[cookieDetailsViewPlaceholder_ superview] 208 replaceSubview:cookieDetailsViewPlaceholder_ 209 with:detailView]; 210 211 [self tabView:tabView_ didSelectTabViewItem:[tabView_ selectedTabViewItem]]; 212 } 213 214 - (void)windowWillClose:(NSNotification*)notif { 215 if (contentSettingsChanged_) { 216 tabContents_->AddInfoBar( 217 new CollectedCookiesInfoBarDelegate(tabContents_)); 218 } 219 [allowedOutlineView_ setDelegate:nil]; 220 [blockedOutlineView_ setDelegate:nil]; 221 [animation_ stopAnimation]; 222 [self autorelease]; 223 } 224 225 - (IBAction)closeSheet:(id)sender { 226 [NSApp endSheet:[self window]]; 227 } 228 229 - (void)addException:(ContentSetting)setting 230 forTreeController:(NSTreeController*)controller { 231 NSArray* nodes = [controller selectedNodes]; 232 BOOL multipleDomainsChanged = NO; 233 string16 lastDomain; 234 for (NSTreeNode* treeNode in nodes) { 235 CocoaCookieTreeNode* node = [treeNode representedObject]; 236 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]); 237 if (cookie->GetDetailedInfo().node_type != 238 CookieTreeNode::DetailedInfo::TYPE_ORIGIN) { 239 continue; 240 } 241 CookieTreeOriginNode* origin_node = 242 static_cast<CookieTreeOriginNode*>(cookie); 243 origin_node->CreateContentException( 244 tabContents_->profile()->GetHostContentSettingsMap(), 245 setting); 246 if (!lastDomain.empty()) 247 multipleDomainsChanged = YES; 248 lastDomain = origin_node->GetTitle(); 249 } 250 if (multipleDomainsChanged) 251 [self showInfoBarForMultipleDomainsAndSetting:setting]; 252 else 253 [self showInfoBarForDomain:lastDomain setting:setting]; 254 contentSettingsChanged_ = YES; 255 } 256 257 - (IBAction)allowOrigin:(id)sender { 258 [self addException:CONTENT_SETTING_ALLOW 259 forTreeController:blockedTreeController_]; 260 } 261 262 - (IBAction)allowForSessionFromOrigin:(id)sender { 263 [self addException:CONTENT_SETTING_SESSION_ONLY 264 forTreeController:blockedTreeController_]; 265 } 266 267 - (IBAction)blockOrigin:(id)sender { 268 [self addException:CONTENT_SETTING_BLOCK 269 forTreeController:allowedTreeController_]; 270 } 271 272 - (CocoaCookieTreeNode*)cocoaAllowedTreeModel { 273 return cocoaAllowedTreeModel_.get(); 274 } 275 - (void)setCocoaAllowedTreeModel:(CocoaCookieTreeNode*)model { 276 cocoaAllowedTreeModel_.reset([model retain]); 277 } 278 279 - (CookiesTreeModel*)allowedTreeModel { 280 return allowedTreeModel_.get(); 281 } 282 283 - (CocoaCookieTreeNode*)cocoaBlockedTreeModel { 284 return cocoaBlockedTreeModel_.get(); 285 } 286 - (void)setCocoaBlockedTreeModel:(CocoaCookieTreeNode*)model { 287 cocoaBlockedTreeModel_.reset([model retain]); 288 } 289 290 - (CookiesTreeModel*)blockedTreeModel { 291 return blockedTreeModel_.get(); 292 } 293 294 - (void)outlineView:(NSOutlineView*)outlineView 295 willDisplayCell:(id)cell 296 forTableColumn:(NSTableColumn*)tableColumn 297 item:(id)item { 298 CocoaCookieTreeNode* node = [item representedObject]; 299 int index; 300 if (outlineView == allowedOutlineView_) 301 index = allowedTreeModel_->GetIconIndex([node treeNode]); 302 else 303 index = blockedTreeModel_->GetIconIndex([node treeNode]); 304 NSImage* icon = nil; 305 if (index >= 0) 306 icon = [icons_ objectAtIndex:index]; 307 else 308 icon = [icons_ lastObject]; 309 DCHECK([cell isKindOfClass:[ImageAndTextCell class]]); 310 [static_cast<ImageAndTextCell*>(cell) setImage:icon]; 311 } 312 313 - (void)outlineViewSelectionDidChange:(NSNotification*)notif { 314 BOOL isAllowedOutlineView; 315 if ([notif object] == allowedOutlineView_) { 316 isAllowedOutlineView = YES; 317 } else if ([notif object] == blockedOutlineView_) { 318 isAllowedOutlineView = NO; 319 } else { 320 NOTREACHED(); 321 return; 322 } 323 NSTreeController* controller = 324 isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_; 325 326 NSArray* nodes = [controller selectedNodes]; 327 for (NSTreeNode* treeNode in nodes) { 328 CocoaCookieTreeNode* node = [treeNode representedObject]; 329 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]); 330 if (cookie->GetDetailedInfo().node_type != 331 CookieTreeNode::DetailedInfo::TYPE_ORIGIN) { 332 continue; 333 } 334 CookieTreeOriginNode* origin_node = 335 static_cast<CookieTreeOriginNode*>(cookie); 336 if (origin_node->CanCreateContentException()) { 337 if (isAllowedOutlineView) { 338 [self setAllowedCookiesButtonsEnabled:YES]; 339 } else { 340 [self setBlockedCookiesButtonsEnabled:YES]; 341 } 342 return; 343 } 344 } 345 if (isAllowedOutlineView) { 346 [self setAllowedCookiesButtonsEnabled:NO]; 347 } else { 348 [self setBlockedCookiesButtonsEnabled:NO]; 349 } 350 } 351 352 // Initializes the |allowedTreeModel_| and |blockedTreeModel_|, and builds 353 // the |cocoaAllowedTreeModel_| and |cocoaBlockedTreeModel_|. 354 - (void)loadTreeModelFromTabContents { 355 TabSpecificContentSettings* content_settings = 356 tabContents_->GetTabSpecificContentSettings(); 357 allowedTreeModel_.reset(content_settings->GetAllowedCookiesTreeModel()); 358 blockedTreeModel_.reset(content_settings->GetBlockedCookiesTreeModel()); 359 360 // Convert the model's icons from Skia to Cocoa. 361 std::vector<SkBitmap> skiaIcons; 362 allowedTreeModel_->GetIcons(&skiaIcons); 363 icons_.reset([[NSMutableArray alloc] init]); 364 for (std::vector<SkBitmap>::iterator it = skiaIcons.begin(); 365 it != skiaIcons.end(); ++it) { 366 [icons_ addObject:gfx::SkBitmapToNSImage(*it)]; 367 } 368 369 // Default icon will be the last item in the array. 370 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 371 // TODO(rsesek): Rename this resource now that it's in multiple places. 372 [icons_ addObject:rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER)]; 373 374 // Create the Cocoa model. 375 CookieTreeNode* root = 376 static_cast<CookieTreeNode*>(allowedTreeModel_->GetRoot()); 377 scoped_nsobject<CocoaCookieTreeNode> model( 378 [[CocoaCookieTreeNode alloc] initWithNode:root]); 379 [self setCocoaAllowedTreeModel:model.get()]; // Takes ownership. 380 root = static_cast<CookieTreeNode*>(blockedTreeModel_->GetRoot()); 381 model.reset( 382 [[CocoaCookieTreeNode alloc] initWithNode:root]); 383 [self setCocoaBlockedTreeModel:model.get()]; // Takes ownership. 384 } 385 386 -(void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting { 387 NSString* label; 388 switch (setting) { 389 case CONTENT_SETTING_BLOCK: 390 label = l10n_util::GetNSString( 391 IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED); 392 break; 393 394 case CONTENT_SETTING_ALLOW: 395 label = l10n_util::GetNSString( 396 IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED); 397 break; 398 399 case CONTENT_SETTING_SESSION_ONLY: 400 label = l10n_util::GetNSString( 401 IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED); 402 break; 403 404 default: 405 NOTREACHED(); 406 label = [[[NSString alloc] init] autorelease]; 407 } 408 [infoBarText_ setStringValue:label]; 409 [self animateInfoBar]; 410 } 411 412 -(void)showInfoBarForDomain:(const string16&)domain 413 setting:(ContentSetting)setting { 414 NSString* label; 415 switch (setting) { 416 case CONTENT_SETTING_BLOCK: 417 label = l10n_util::GetNSStringF( 418 IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED, 419 domain); 420 break; 421 422 case CONTENT_SETTING_ALLOW: 423 label = l10n_util::GetNSStringF( 424 IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED, 425 domain); 426 break; 427 428 case CONTENT_SETTING_SESSION_ONLY: 429 label = l10n_util::GetNSStringF( 430 IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED, 431 domain); 432 break; 433 434 default: 435 NOTREACHED(); 436 label = [[[NSString alloc] init] autorelease]; 437 } 438 [infoBarText_ setStringValue:label]; 439 [self animateInfoBar]; 440 } 441 442 -(void)animateInfoBar { 443 if (infoBarVisible_) 444 return; 445 446 infoBarVisible_ = YES; 447 448 NSMutableArray* animations = [NSMutableArray arrayWithCapacity:3]; 449 450 NSWindow* sheet = [self window]; 451 NSRect sheetFrame = [sheet frame]; 452 NSRect infoBarFrame = [infoBar_ frame]; 453 NSRect tabViewFrame = [tabView_ frame]; 454 455 // Calculate the end position of the info bar and set it to its start 456 // position. 457 infoBarFrame.origin.y = NSHeight(sheetFrame); 458 infoBarFrame.size.width = NSWidth(sheetFrame); 459 [infoBar_ setFrame:infoBarFrame]; 460 [[[self window] contentView] addSubview:infoBar_]; 461 462 // Calculate the new position of the sheet. 463 sheetFrame.origin.y -= NSHeight(infoBarFrame); 464 sheetFrame.size.height += NSHeight(infoBarFrame); 465 466 // Slide the infobar in. 467 [animations addObject: 468 [NSDictionary dictionaryWithObjectsAndKeys: 469 infoBar_, NSViewAnimationTargetKey, 470 [NSValue valueWithRect:infoBarFrame], 471 NSViewAnimationEndFrameKey, 472 nil]]; 473 // Make sure the tab view ends up in the right position. 474 [animations addObject: 475 [NSDictionary dictionaryWithObjectsAndKeys: 476 tabView_, NSViewAnimationTargetKey, 477 [NSValue valueWithRect:tabViewFrame], 478 NSViewAnimationEndFrameKey, 479 nil]]; 480 481 // Grow the sheet. 482 [animations addObject: 483 [NSDictionary dictionaryWithObjectsAndKeys: 484 sheet, NSViewAnimationTargetKey, 485 [NSValue valueWithRect:sheetFrame], 486 NSViewAnimationEndFrameKey, 487 nil]]; 488 [animation_ setViewAnimations:animations]; 489 // The default duration is 0.5s, which actually feels slow in here, so speed 490 // it up a bit. 491 [animation_ gtm_setDuration:0.2 492 eventMask:NSLeftMouseUpMask]; 493 [animation_ startAnimation]; 494 } 495 496 - (void) tabView:(NSTabView*)tabView 497 didSelectTabViewItem:(NSTabViewItem*)tabViewItem { 498 NSTreeController* treeController = nil; 499 switch ([tabView indexOfTabViewItem:tabViewItem]) { 500 case kAllowedCookiesTabIndex: 501 treeController = allowedTreeController_; 502 break; 503 case kBlockedCookiesTabIndex: 504 treeController = blockedTreeController_; 505 break; 506 default: 507 NOTREACHED(); 508 return; 509 } 510 [detailsViewController_ configureBindingsForTreeController:treeController]; 511 } 512 513 @end 514