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/content_settings/collected_cookies_mac.h"
9 #include "base/mac/bundle_locations.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/content_settings/cookie_settings.h"
15 #include "chrome/browser/content_settings/local_shared_objects_container.h"
16 #include "chrome/browser/content_settings/tab_specific_content_settings.h"
17 #include "chrome/browser/infobars/infobar_service.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/browser_dialogs.h"
20 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
21 #import "chrome/browser/ui/cocoa/content_settings/cookie_details_view_controller.h"
22 #import "chrome/browser/ui/cocoa/vertical_gradient_view.h"
23 #include "chrome/browser/ui/collected_cookies_infobar_delegate.h"
24 #include "chrome/common/pref_names.h"
25 #include "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/web_contents.h"
28 #include "grit/generated_resources.h"
29 #include "grit/theme_resources.h"
30 #include "third_party/apple_sample_code/ImageAndTextCell.h"
31 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
32 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
33 #include "ui/base/l10n/l10n_util_mac.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/image/image.h"
36 #include "ui/gfx/image/image_skia.h"
37 #include "ui/gfx/image/image_skia_util_mac.h"
40 // Colors for the infobar.
41 const double kBannerGradientColorTop[3] =
42 {255.0 / 255.0, 242.0 / 255.0, 183.0 / 255.0};
43 const double kBannerGradientColorBottom[3] =
44 {250.0 / 255.0, 230.0 / 255.0, 145.0 / 255.0};
45 const double kBannerStrokeColor = 135.0 / 255.0;
47 enum TabViewItemIndices {
48 kAllowedCookiesTabIndex = 0,
49 kBlockedCookiesTabIndex
56 // Declared in browser_dialogs.h so others don't have to depend on our header.
57 void ShowCollectedCookiesDialog(content::WebContents* web_contents) {
58 // Deletes itself on close.
59 new CollectedCookiesMac(web_contents);
64 #pragma mark Constrained window delegate
66 CollectedCookiesMac::CollectedCookiesMac(content::WebContents* web_contents) {
67 TabSpecificContentSettings* content_settings =
68 TabSpecificContentSettings::FromWebContents(web_contents);
69 registrar_.Add(this, chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN,
70 content::Source<TabSpecificContentSettings>(content_settings));
72 sheet_controller_.reset([[CollectedCookiesWindowController alloc]
73 initWithWebContents:web_contents
74 collectedCookiesMac:this]);
76 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
77 [[CustomConstrainedWindowSheet alloc]
78 initWithCustomWindow:[sheet_controller_ window]]);
79 window_.reset(new ConstrainedWindowMac(
80 this, web_contents, sheet));
83 CollectedCookiesMac::~CollectedCookiesMac() {
86 void CollectedCookiesMac::Observe(int type,
87 const content::NotificationSource& source,
88 const content::NotificationDetails& details) {
89 DCHECK(type == chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN);
90 window_->CloseWebContentsModalDialog();
93 void CollectedCookiesMac::PerformClose() {
94 window_->CloseWebContentsModalDialog();
97 void CollectedCookiesMac::OnConstrainedWindowClosed(
98 ConstrainedWindowMac* window) {
99 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
102 #pragma mark Window Controller
104 @interface CollectedCookiesWindowController (Private)
105 - (void)showInfoBarForDomain:(const base::string16&)domain
106 setting:(ContentSetting)setting;
107 - (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting;
108 - (void)animateInfoBar;
111 @implementation CollectedCookiesWindowController
113 @synthesize allowedTreeController = allowedTreeController_;
114 @synthesize blockedTreeController = blockedTreeController_;
115 @synthesize allowedOutlineView = allowedOutlineView_;
116 @synthesize blockedOutlineView = blockedOutlineView_;
117 @synthesize infoBar = infoBar_;
118 @synthesize infoBarIcon = infoBarIcon_;
119 @synthesize infoBarText = infoBarText_;
120 @synthesize tabView = tabView_;
121 @synthesize blockedScrollView = blockedScrollView_;
122 @synthesize blockedCookiesText = blockedCookiesText_;
123 @synthesize cookieDetailsViewPlaceholder = cookieDetailsViewPlaceholder_;
125 @synthesize allowedCookiesButtonsEnabled =
126 allowedCookiesButtonsEnabled_;
127 @synthesize blockedCookiesButtonsEnabled =
128 blockedCookiesButtonsEnabled_;
129 @synthesize deleteCookiesButtonEnabled = deleteCookiesButtonEnabled_;
131 - (id)initWithWebContents:(content::WebContents*)webContents
132 collectedCookiesMac:(CollectedCookiesMac*)collectedCookiesMac {
136 [base::mac::FrameworkBundle() pathForResource:@"CollectedCookies"
138 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
139 webContents_ = webContents;
140 collectedCookiesMac_ = collectedCookiesMac;
141 [self loadTreeModelFromWebContents];
143 animation_.reset([[NSViewAnimation alloc] init]);
144 [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
149 - (void)awakeFromNib {
150 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
151 NSImage* infoIcon = rb.GetNativeImageNamed(IDR_INFO).ToNSImage();
152 [infoBarIcon_ setImage:infoIcon];
154 // Initialize the banner gradient and stroke color.
155 NSColor* bannerStartingColor =
156 [NSColor colorWithCalibratedRed:kBannerGradientColorTop[0]
157 green:kBannerGradientColorTop[1]
158 blue:kBannerGradientColorTop[2]
160 NSColor* bannerEndingColor =
161 [NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0]
162 green:kBannerGradientColorBottom[1]
163 blue:kBannerGradientColorBottom[2]
165 base::scoped_nsobject<NSGradient> bannerGradient(
166 [[NSGradient alloc] initWithStartingColor:bannerStartingColor
167 endingColor:bannerEndingColor]);
168 [infoBar_ setGradient:bannerGradient];
170 NSColor* bannerStrokeColor =
171 [NSColor colorWithCalibratedWhite:kBannerStrokeColor
173 [infoBar_ setStrokeColor:bannerStrokeColor];
175 // Change the label of the blocked cookies part if necessary.
177 Profile::FromBrowserContext(webContents_->GetBrowserContext());
178 if (profile->GetPrefs()->GetBoolean(prefs::kBlockThirdPartyCookies)) {
179 [blockedCookiesText_ setStringValue:l10n_util::GetNSString(
180 IDS_COLLECTED_COOKIES_BLOCKED_THIRD_PARTY_BLOCKING_ENABLED)];
181 CGFloat textDeltaY = [GTMUILocalizerAndLayoutTweaker
182 sizeToFitFixedWidthTextField:blockedCookiesText_];
184 // Shrink the blocked cookies outline view.
185 NSRect frame = [blockedScrollView_ frame];
186 frame.size.height -= textDeltaY;
187 [blockedScrollView_ setFrame:frame];
189 // Move the label down so it actually fits.
190 frame = [blockedCookiesText_ frame];
191 frame.origin.y -= textDeltaY;
192 [blockedCookiesText_ setFrame:frame];
195 detailsViewController_.reset([[CookieDetailsViewController alloc] init]);
197 NSView* detailView = [detailsViewController_.get() view];
198 NSRect viewFrameRect = [cookieDetailsViewPlaceholder_ frame];
199 [[detailsViewController_.get() view] setFrame:viewFrameRect];
200 [[cookieDetailsViewPlaceholder_ superview]
201 replaceSubview:cookieDetailsViewPlaceholder_
204 [self tabView:tabView_ didSelectTabViewItem:[tabView_ selectedTabViewItem]];
207 - (void)windowWillClose:(NSNotification*)notif {
208 if (contentSettingsChanged_) {
209 CollectedCookiesInfoBarDelegate::Create(
210 InfoBarService::FromWebContents(webContents_));
212 [allowedOutlineView_ setDelegate:nil];
213 [blockedOutlineView_ setDelegate:nil];
214 [animation_ stopAnimation];
217 - (IBAction)closeSheet:(id)sender {
218 collectedCookiesMac_->PerformClose();
221 - (void)addException:(ContentSetting)setting
222 forTreeController:(NSTreeController*)controller {
223 NSArray* nodes = [controller selectedNodes];
224 BOOL multipleDomainsChanged = NO;
225 base::string16 lastDomain;
226 for (NSTreeNode* treeNode in nodes) {
227 CocoaCookieTreeNode* node = [treeNode representedObject];
228 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
229 if (cookie->GetDetailedInfo().node_type !=
230 CookieTreeNode::DetailedInfo::TYPE_HOST) {
234 Profile::FromBrowserContext(webContents_->GetBrowserContext());
235 CookieTreeHostNode* host_node =
236 static_cast<CookieTreeHostNode*>(cookie);
237 host_node->CreateContentException(
238 CookieSettings::Factory::GetForProfile(profile).get(), setting);
239 if (!lastDomain.empty())
240 multipleDomainsChanged = YES;
241 lastDomain = host_node->GetTitle();
243 if (multipleDomainsChanged)
244 [self showInfoBarForMultipleDomainsAndSetting:setting];
246 [self showInfoBarForDomain:lastDomain setting:setting];
247 contentSettingsChanged_ = YES;
250 - (IBAction)allowOrigin:(id)sender {
251 [self addException:CONTENT_SETTING_ALLOW
252 forTreeController:blockedTreeController_];
255 - (IBAction)allowForSessionFromOrigin:(id)sender {
256 [self addException:CONTENT_SETTING_SESSION_ONLY
257 forTreeController:blockedTreeController_];
260 - (IBAction)blockOrigin:(id)sender {
261 [self addException:CONTENT_SETTING_BLOCK
262 forTreeController:allowedTreeController_];
265 - (IBAction)deleteSelected:(id)sender {
266 NSArray* nodes = [[self class] normalizeNodeSelection:
267 [allowedTreeController_ selectedNodes]];
268 for (NSTreeNode* cocoaTreeNode in nodes) {
269 CookieTreeNode* cookieNode = static_cast<CookieTreeNode*>(
270 [[cocoaTreeNode representedObject] treeNode]);
271 allowedTreeModel_->DeleteCookieNode(cookieNode);
275 - (CocoaCookieTreeNode*)cocoaAllowedTreeModel {
276 return allowedControllerBridge_->cocoa_model();
279 - (CookiesTreeModel*)allowedTreeModel {
280 return allowedTreeModel_.get();
283 - (CocoaCookieTreeNode*)cocoaBlockedTreeModel {
284 return blockedControllerBridge_->cocoa_model();
287 - (CookiesTreeModel*)blockedTreeModel {
288 return blockedTreeModel_.get();
291 - (void)outlineView:(NSOutlineView*)outlineView
292 willDisplayCell:(id)cell
293 forTableColumn:(NSTableColumn*)tableColumn
295 CocoaCookieTreeNode* node = [item representedObject];
297 if (outlineView == allowedOutlineView_)
298 index = allowedTreeModel_->GetIconIndex([node treeNode]);
300 index = blockedTreeModel_->GetIconIndex([node treeNode]);
303 icon = [icons_ objectAtIndex:index];
305 icon = [icons_ lastObject];
306 DCHECK([cell isKindOfClass:[ImageAndTextCell class]]);
307 [static_cast<ImageAndTextCell*>(cell) setImage:icon];
310 - (void)outlineViewSelectionDidChange:(NSNotification*)notif {
311 BOOL isAllowedOutlineView;
312 if ([notif object] == allowedOutlineView_) {
313 isAllowedOutlineView = YES;
314 [self setDeleteCookiesButtonEnabled:YES];
315 } else if ([notif object] == blockedOutlineView_) {
316 isAllowedOutlineView = NO;
317 [self setDeleteCookiesButtonEnabled:NO];
322 NSTreeController* controller =
323 isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_;
325 NSArray* nodes = [controller selectedNodes];
326 for (NSTreeNode* treeNode in nodes) {
327 CocoaCookieTreeNode* node = [treeNode representedObject];
328 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
329 if (cookie->GetDetailedInfo().node_type !=
330 CookieTreeNode::DetailedInfo::TYPE_HOST) {
333 CookieTreeHostNode* host_node =
334 static_cast<CookieTreeHostNode*>(cookie);
335 if (host_node->CanCreateContentException()) {
336 if (isAllowedOutlineView) {
337 [self setAllowedCookiesButtonsEnabled:YES];
339 [self setBlockedCookiesButtonsEnabled:YES];
344 if (isAllowedOutlineView) {
345 [self setAllowedCookiesButtonsEnabled:NO];
347 [self setBlockedCookiesButtonsEnabled:NO];
351 // Initializes the |allowedTreeModel_| and |blockedTreeModel_|, and builds
352 // the |cocoaAllowedTreeModel_| and |cocoaBlockedTreeModel_|.
353 - (void)loadTreeModelFromWebContents {
354 TabSpecificContentSettings* content_settings =
355 TabSpecificContentSettings::FromWebContents(webContents_);
357 const LocalSharedObjectsContainer& allowed_data =
358 content_settings->allowed_local_shared_objects();
359 allowedTreeModel_ = allowed_data.CreateCookiesTreeModel();
361 const LocalSharedObjectsContainer& blocked_data =
362 content_settings->blocked_local_shared_objects();
363 blockedTreeModel_ = blocked_data.CreateCookiesTreeModel();
365 // Convert the model's icons from Skia to Cocoa.
366 std::vector<gfx::ImageSkia> skiaIcons;
367 allowedTreeModel_->GetIcons(&skiaIcons);
368 icons_.reset([[NSMutableArray alloc] init]);
369 for (std::vector<gfx::ImageSkia>::iterator it = skiaIcons.begin();
370 it != skiaIcons.end(); ++it) {
371 [icons_ addObject:gfx::NSImageFromImageSkia(*it)];
374 // Default icon will be the last item in the array.
375 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
376 // TODO(rsesek): Rename this resource now that it's in multiple places.
378 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).ToNSImage()];
380 // Create the Cocoa model.
381 allowedControllerBridge_.reset(
382 new CookiesTreeControllerBridge(allowedTreeModel_.get()));
383 blockedControllerBridge_.reset(
384 new CookiesTreeControllerBridge(blockedTreeModel_.get()));
387 - (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting {
390 case CONTENT_SETTING_BLOCK:
391 label = l10n_util::GetNSString(
392 IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED);
395 case CONTENT_SETTING_ALLOW:
396 label = l10n_util::GetNSString(
397 IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED);
400 case CONTENT_SETTING_SESSION_ONLY:
401 label = l10n_util::GetNSString(
402 IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED);
407 label = [[[NSString alloc] init] autorelease];
409 [infoBarText_ setStringValue:label];
410 [self animateInfoBar];
413 + (NSArray*)normalizeNodeSelection:(NSArray*)selection {
414 NSMutableArray* normalized = [NSMutableArray arrayWithArray:selection];
415 for (NSTreeNode* node in selection) {
416 NSTreeNode* parent = node;
417 while ((parent = [parent parentNode])) {
418 if ([normalized containsObject:parent]) {
419 [normalized removeObject:node];
427 - (void)showInfoBarForDomain:(const base::string16&)domain
428 setting:(ContentSetting)setting {
431 case CONTENT_SETTING_BLOCK:
432 label = l10n_util::GetNSStringF(
433 IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
437 case CONTENT_SETTING_ALLOW:
438 label = l10n_util::GetNSStringF(
439 IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
443 case CONTENT_SETTING_SESSION_ONLY:
444 label = l10n_util::GetNSStringF(
445 IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
451 label = [[[NSString alloc] init] autorelease];
453 [infoBarText_ setStringValue:label];
454 [self animateInfoBar];
457 - (void)animateInfoBar {
461 infoBarVisible_ = YES;
463 NSWindow* sheet = [self window];
464 NSRect sheetFrame = [sheet frame];
465 NSRect infoBarFrame = [infoBar_ frame];
466 NSRect tabViewFrame = [tabView_ frame];
468 // Calculate the end position of the info bar and set it to its start
470 infoBarFrame.origin.y = NSHeight(sheetFrame);
471 infoBarFrame.size.width = NSWidth(sheetFrame);
472 [infoBar_ setFrame:infoBarFrame];
473 [[[self window] contentView] addSubview:infoBar_];
475 // Calculate the new position of the sheet.
476 sheetFrame.origin.y -= NSHeight(infoBarFrame);
477 sheetFrame.size.height += NSHeight(infoBarFrame);
479 NSArray* animations = @[
480 // Slide the infobar in.
482 NSViewAnimationTargetKey : infoBar_,
483 NSViewAnimationEndFrameKey : [NSValue valueWithRect:infoBarFrame]
485 // Make sure the tab view ends up in the right position.
487 NSViewAnimationTargetKey : tabView_,
488 NSViewAnimationEndFrameKey : [NSValue valueWithRect:tabViewFrame]
492 NSViewAnimationTargetKey : sheet,
493 NSViewAnimationEndFrameKey : [NSValue valueWithRect:sheetFrame]
497 [animation_ setViewAnimations:animations];
498 // The default duration is 0.5s, which actually feels slow in here, so speed
500 [animation_ gtm_setDuration:0.2 eventMask:NSLeftMouseUpMask];
501 [animation_ startAnimation];
504 - (void)tabView:(NSTabView*)tabView
505 didSelectTabViewItem:(NSTabViewItem*)tabViewItem {
506 NSTreeController* treeController = nil;
507 switch ([tabView indexOfTabViewItem:tabViewItem]) {
508 case kAllowedCookiesTabIndex:
509 treeController = allowedTreeController_;
511 case kBlockedCookiesTabIndex:
512 treeController = blockedTreeController_;
518 [detailsViewController_ configureBindingsForTreeController:treeController];