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/tab_specific_content_settings.h"
16 #include "chrome/browser/infobars/infobar_service.h"
17 #include "chrome/browser/profiles/profile.h"
18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
19 #import "chrome/browser/ui/cocoa/content_settings/cookie_details_view_controller.h"
20 #import "chrome/browser/ui/cocoa/vertical_gradient_view.h"
21 #include "chrome/browser/ui/collected_cookies_infobar_delegate.h"
22 #include "chrome/common/pref_names.h"
23 #include "chrome/grit/generated_resources.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_source.h"
26 #include "content/public/browser/web_contents.h"
27 #include "grit/theme_resources.h"
28 #include "third_party/apple_sample_code/ImageAndTextCell.h"
29 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
30 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
31 #include "ui/base/l10n/l10n_util_mac.h"
32 #include "ui/base/resource/resource_bundle.h"
33 #include "ui/gfx/image/image.h"
34 #include "ui/gfx/image/image_skia.h"
35 #include "ui/gfx/image/image_skia_util_mac.h"
38 // Colors for the infobar.
39 const double kBannerGradientColorTop[3] =
40 {255.0 / 255.0, 242.0 / 255.0, 183.0 / 255.0};
41 const double kBannerGradientColorBottom[3] =
42 {250.0 / 255.0, 230.0 / 255.0, 145.0 / 255.0};
43 const double kBannerStrokeColor = 135.0 / 255.0;
45 enum TabViewItemIndices {
46 kAllowedCookiesTabIndex = 0,
47 kBlockedCookiesTabIndex
52 #pragma mark Constrained window delegate
54 CollectedCookiesMac::CollectedCookiesMac(content::WebContents* web_contents) {
55 TabSpecificContentSettings* content_settings =
56 TabSpecificContentSettings::FromWebContents(web_contents);
57 registrar_.Add(this, chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN,
58 content::Source<TabSpecificContentSettings>(content_settings));
60 sheet_controller_.reset([[CollectedCookiesWindowController alloc]
61 initWithWebContents:web_contents
62 collectedCookiesMac:this]);
64 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
65 [[CustomConstrainedWindowSheet alloc]
66 initWithCustomWindow:[sheet_controller_ window]]);
67 window_.reset(new ConstrainedWindowMac(
68 this, web_contents, sheet));
71 CollectedCookiesMac::~CollectedCookiesMac() {
74 void CollectedCookiesMac::Observe(int type,
75 const content::NotificationSource& source,
76 const content::NotificationDetails& details) {
77 DCHECK(type == chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN);
78 window_->CloseWebContentsModalDialog();
81 void CollectedCookiesMac::PerformClose() {
82 window_->CloseWebContentsModalDialog();
85 void CollectedCookiesMac::OnConstrainedWindowClosed(
86 ConstrainedWindowMac* window) {
87 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
90 #pragma mark Window Controller
92 @interface CollectedCookiesWindowController (Private)
93 - (void)showInfoBarForDomain:(const base::string16&)domain
94 setting:(ContentSetting)setting;
95 - (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting;
96 - (void)animateInfoBar;
99 @implementation CollectedCookiesWindowController
101 @synthesize allowedTreeController = allowedTreeController_;
102 @synthesize blockedTreeController = blockedTreeController_;
103 @synthesize allowedOutlineView = allowedOutlineView_;
104 @synthesize blockedOutlineView = blockedOutlineView_;
105 @synthesize infoBar = infoBar_;
106 @synthesize infoBarIcon = infoBarIcon_;
107 @synthesize infoBarText = infoBarText_;
108 @synthesize tabView = tabView_;
109 @synthesize blockedScrollView = blockedScrollView_;
110 @synthesize blockedCookiesText = blockedCookiesText_;
111 @synthesize cookieDetailsViewPlaceholder = cookieDetailsViewPlaceholder_;
113 @synthesize allowedCookiesButtonsEnabled =
114 allowedCookiesButtonsEnabled_;
115 @synthesize blockedCookiesButtonsEnabled =
116 blockedCookiesButtonsEnabled_;
117 @synthesize deleteCookiesButtonEnabled = deleteCookiesButtonEnabled_;
119 - (id)initWithWebContents:(content::WebContents*)webContents
120 collectedCookiesMac:(CollectedCookiesMac*)collectedCookiesMac {
124 [base::mac::FrameworkBundle() pathForResource:@"CollectedCookies"
126 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
127 webContents_ = webContents;
128 collectedCookiesMac_ = collectedCookiesMac;
129 [self loadTreeModelFromWebContents];
131 animation_.reset([[NSViewAnimation alloc] init]);
132 [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
137 - (void)awakeFromNib {
138 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
139 NSImage* infoIcon = rb.GetNativeImageNamed(IDR_INFO).ToNSImage();
140 [infoBarIcon_ setImage:infoIcon];
142 // Initialize the banner gradient and stroke color.
143 NSColor* bannerStartingColor =
144 [NSColor colorWithCalibratedRed:kBannerGradientColorTop[0]
145 green:kBannerGradientColorTop[1]
146 blue:kBannerGradientColorTop[2]
148 NSColor* bannerEndingColor =
149 [NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0]
150 green:kBannerGradientColorBottom[1]
151 blue:kBannerGradientColorBottom[2]
153 base::scoped_nsobject<NSGradient> bannerGradient(
154 [[NSGradient alloc] initWithStartingColor:bannerStartingColor
155 endingColor:bannerEndingColor]);
156 [infoBar_ setGradient:bannerGradient];
158 NSColor* bannerStrokeColor =
159 [NSColor colorWithCalibratedWhite:kBannerStrokeColor
161 [infoBar_ setStrokeColor:bannerStrokeColor];
163 // Change the label of the blocked cookies part if necessary.
165 Profile::FromBrowserContext(webContents_->GetBrowserContext());
166 if (profile->GetPrefs()->GetBoolean(prefs::kBlockThirdPartyCookies)) {
167 [blockedCookiesText_ setStringValue:l10n_util::GetNSString(
168 IDS_COLLECTED_COOKIES_BLOCKED_THIRD_PARTY_BLOCKING_ENABLED)];
169 CGFloat textDeltaY = [GTMUILocalizerAndLayoutTweaker
170 sizeToFitFixedWidthTextField:blockedCookiesText_];
172 // Shrink the blocked cookies outline view.
173 NSRect frame = [blockedScrollView_ frame];
174 frame.size.height -= textDeltaY;
175 [blockedScrollView_ setFrame:frame];
177 // Move the label down so it actually fits.
178 frame = [blockedCookiesText_ frame];
179 frame.origin.y -= textDeltaY;
180 [blockedCookiesText_ setFrame:frame];
183 detailsViewController_.reset([[CookieDetailsViewController alloc] init]);
185 NSView* detailView = [detailsViewController_.get() view];
186 NSRect viewFrameRect = [cookieDetailsViewPlaceholder_ frame];
187 [[detailsViewController_.get() view] setFrame:viewFrameRect];
188 [[cookieDetailsViewPlaceholder_ superview]
189 replaceSubview:cookieDetailsViewPlaceholder_
192 [self tabView:tabView_ didSelectTabViewItem:[tabView_ selectedTabViewItem]];
195 - (void)windowWillClose:(NSNotification*)notif {
196 // If the user closes our parent tab while we're still open, this method will
197 // (eventually) be called in response to a WebContentsDestroyed() call from
198 // the WebContentsImpl to its observers. But since the InfoBarService is also
199 // torn down in response to WebContentsDestroyed(), it may already be null.
200 // Since the tab is going away anyway, we can just omit showing an infobar,
201 // which prevents any attempt to access a null InfoBarService.
202 if (contentSettingsChanged_ && !webContents_->IsBeingDestroyed()) {
203 CollectedCookiesInfoBarDelegate::Create(
204 InfoBarService::FromWebContents(webContents_));
206 [allowedOutlineView_ setDelegate:nil];
207 [blockedOutlineView_ setDelegate:nil];
208 [animation_ stopAnimation];
211 - (IBAction)closeSheet:(id)sender {
212 collectedCookiesMac_->PerformClose();
215 - (void)addException:(ContentSetting)setting
216 forTreeController:(NSTreeController*)controller {
217 NSArray* nodes = [controller selectedNodes];
218 BOOL multipleDomainsChanged = NO;
219 base::string16 lastDomain;
220 for (NSTreeNode* treeNode in nodes) {
221 CocoaCookieTreeNode* node = [treeNode representedObject];
222 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
223 if (cookie->GetDetailedInfo().node_type !=
224 CookieTreeNode::DetailedInfo::TYPE_HOST) {
228 Profile::FromBrowserContext(webContents_->GetBrowserContext());
229 CookieTreeHostNode* host_node =
230 static_cast<CookieTreeHostNode*>(cookie);
231 host_node->CreateContentException(
232 CookieSettings::Factory::GetForProfile(profile).get(), setting);
233 if (!lastDomain.empty())
234 multipleDomainsChanged = YES;
235 lastDomain = host_node->GetTitle();
237 if (multipleDomainsChanged)
238 [self showInfoBarForMultipleDomainsAndSetting:setting];
240 [self showInfoBarForDomain:lastDomain setting:setting];
241 contentSettingsChanged_ = YES;
244 - (IBAction)allowOrigin:(id)sender {
245 [self addException:CONTENT_SETTING_ALLOW
246 forTreeController:blockedTreeController_];
249 - (IBAction)allowForSessionFromOrigin:(id)sender {
250 [self addException:CONTENT_SETTING_SESSION_ONLY
251 forTreeController:blockedTreeController_];
254 - (IBAction)blockOrigin:(id)sender {
255 [self addException:CONTENT_SETTING_BLOCK
256 forTreeController:allowedTreeController_];
259 - (IBAction)deleteSelected:(id)sender {
260 NSArray* nodes = [[self class] normalizeNodeSelection:
261 [allowedTreeController_ selectedNodes]];
262 for (NSTreeNode* cocoaTreeNode in nodes) {
263 CookieTreeNode* cookieNode = static_cast<CookieTreeNode*>(
264 [[cocoaTreeNode representedObject] treeNode]);
265 allowedTreeModel_->DeleteCookieNode(cookieNode);
269 - (CocoaCookieTreeNode*)cocoaAllowedTreeModel {
270 return allowedControllerBridge_->cocoa_model();
273 - (CookiesTreeModel*)allowedTreeModel {
274 return allowedTreeModel_.get();
277 - (CocoaCookieTreeNode*)cocoaBlockedTreeModel {
278 return blockedControllerBridge_->cocoa_model();
281 - (CookiesTreeModel*)blockedTreeModel {
282 return blockedTreeModel_.get();
285 - (void)outlineView:(NSOutlineView*)outlineView
286 willDisplayCell:(id)cell
287 forTableColumn:(NSTableColumn*)tableColumn
289 CocoaCookieTreeNode* node = [item representedObject];
291 if (outlineView == allowedOutlineView_)
292 index = allowedTreeModel_->GetIconIndex([node treeNode]);
294 index = blockedTreeModel_->GetIconIndex([node treeNode]);
297 icon = [icons_ objectAtIndex:index];
299 icon = [icons_ lastObject];
300 DCHECK([cell isKindOfClass:[ImageAndTextCell class]]);
301 [static_cast<ImageAndTextCell*>(cell) setImage:icon];
304 - (void)outlineViewSelectionDidChange:(NSNotification*)notif {
305 BOOL isAllowedOutlineView;
306 if ([notif object] == allowedOutlineView_) {
307 isAllowedOutlineView = YES;
308 } else if ([notif object] == blockedOutlineView_) {
309 isAllowedOutlineView = NO;
315 NSTreeController* controller =
316 isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_;
317 NSArray* nodes = [controller selectedNodes];
319 if (isAllowedOutlineView)
320 [self setDeleteCookiesButtonEnabled:([nodes count] > 0)];
322 [self setDeleteCookiesButtonEnabled:NO];
324 for (NSTreeNode* treeNode in nodes) {
325 CocoaCookieTreeNode* node = [treeNode representedObject];
326 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
327 if (cookie->GetDetailedInfo().node_type !=
328 CookieTreeNode::DetailedInfo::TYPE_HOST) {
331 CookieTreeHostNode* host_node =
332 static_cast<CookieTreeHostNode*>(cookie);
333 if (host_node->CanCreateContentException()) {
334 if (isAllowedOutlineView) {
335 [self setAllowedCookiesButtonsEnabled:YES];
337 [self setBlockedCookiesButtonsEnabled:YES];
342 if (isAllowedOutlineView) {
343 [self setAllowedCookiesButtonsEnabled:NO];
345 [self setBlockedCookiesButtonsEnabled:NO];
349 // Initializes the |allowedTreeModel_| and |blockedTreeModel_|, and builds
350 // the |cocoaAllowedTreeModel_| and |cocoaBlockedTreeModel_|.
351 - (void)loadTreeModelFromWebContents {
352 TabSpecificContentSettings* content_settings =
353 TabSpecificContentSettings::FromWebContents(webContents_);
355 allowedTreeModel_ = content_settings->CreateAllowedCookiesTreeModel();
356 blockedTreeModel_ = content_settings->CreateBlockedCookiesTreeModel();
358 // Convert the model's icons from Skia to Cocoa.
359 std::vector<gfx::ImageSkia> skiaIcons;
360 allowedTreeModel_->GetIcons(&skiaIcons);
361 icons_.reset([[NSMutableArray alloc] init]);
362 for (std::vector<gfx::ImageSkia>::iterator it = skiaIcons.begin();
363 it != skiaIcons.end(); ++it) {
364 [icons_ addObject:gfx::NSImageFromImageSkia(*it)];
367 // Default icon will be the last item in the array.
368 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
369 // TODO(rsesek): Rename this resource now that it's in multiple places.
371 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).ToNSImage()];
373 // Create the Cocoa model.
374 allowedControllerBridge_.reset(
375 new CookiesTreeControllerBridge(allowedTreeModel_.get()));
376 blockedControllerBridge_.reset(
377 new CookiesTreeControllerBridge(blockedTreeModel_.get()));
380 - (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting {
383 case CONTENT_SETTING_BLOCK:
384 label = l10n_util::GetNSString(
385 IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED);
388 case CONTENT_SETTING_ALLOW:
389 label = l10n_util::GetNSString(
390 IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED);
393 case CONTENT_SETTING_SESSION_ONLY:
394 label = l10n_util::GetNSString(
395 IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED);
400 label = [[[NSString alloc] init] autorelease];
402 [infoBarText_ setStringValue:label];
403 [self animateInfoBar];
406 + (NSArray*)normalizeNodeSelection:(NSArray*)selection {
407 NSMutableArray* normalized = [NSMutableArray arrayWithArray:selection];
408 for (NSTreeNode* node in selection) {
409 NSTreeNode* parent = node;
410 while ((parent = [parent parentNode])) {
411 if ([normalized containsObject:parent]) {
412 [normalized removeObject:node];
420 - (void)showInfoBarForDomain:(const base::string16&)domain
421 setting:(ContentSetting)setting {
424 case CONTENT_SETTING_BLOCK:
425 label = l10n_util::GetNSStringF(
426 IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
430 case CONTENT_SETTING_ALLOW:
431 label = l10n_util::GetNSStringF(
432 IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
436 case CONTENT_SETTING_SESSION_ONLY:
437 label = l10n_util::GetNSStringF(
438 IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
444 label = [[[NSString alloc] init] autorelease];
446 [infoBarText_ setStringValue:label];
447 [self animateInfoBar];
450 - (void)animateInfoBar {
454 infoBarVisible_ = YES;
456 NSWindow* sheet = [self window];
457 NSRect sheetFrame = [sheet frame];
458 NSRect infoBarFrame = [infoBar_ frame];
459 NSRect tabViewFrame = [tabView_ frame];
461 // Calculate the end position of the info bar and set it to its start
463 infoBarFrame.origin.y = NSHeight(sheetFrame);
464 infoBarFrame.size.width = NSWidth(sheetFrame);
465 [infoBar_ setFrame:infoBarFrame];
466 [[[self window] contentView] addSubview:infoBar_];
468 // Calculate the new position of the sheet.
469 sheetFrame.origin.y -= NSHeight(infoBarFrame);
470 sheetFrame.size.height += NSHeight(infoBarFrame);
472 NSArray* animations = @[
473 // Slide the infobar in.
475 NSViewAnimationTargetKey : infoBar_,
476 NSViewAnimationEndFrameKey : [NSValue valueWithRect:infoBarFrame]
478 // Make sure the tab view ends up in the right position.
480 NSViewAnimationTargetKey : tabView_,
481 NSViewAnimationEndFrameKey : [NSValue valueWithRect:tabViewFrame]
485 NSViewAnimationTargetKey : sheet,
486 NSViewAnimationEndFrameKey : [NSValue valueWithRect:sheetFrame]
490 [animation_ setViewAnimations:animations];
491 // The default duration is 0.5s, which actually feels slow in here, so speed
493 [animation_ gtm_setDuration:0.2 eventMask:NSLeftMouseUpMask];
494 [animation_ startAnimation];
497 - (void)tabView:(NSTabView*)tabView
498 didSelectTabViewItem:(NSTabViewItem*)tabViewItem {
499 NSTreeController* treeController = nil;
500 switch ([tabView indexOfTabViewItem:tabViewItem]) {
501 case kAllowedCookiesTabIndex:
502 treeController = allowedTreeController_;
504 case kBlockedCookiesTabIndex:
505 treeController = blockedTreeController_;
511 [detailsViewController_ configureBindingsForTreeController:treeController];