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 } else if ([notif object] == blockedOutlineView_) {
315 isAllowedOutlineView = NO;
321 NSTreeController* controller =
322 isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_;
323 NSArray* nodes = [controller selectedNodes];
325 if (isAllowedOutlineView)
326 [self setDeleteCookiesButtonEnabled:([nodes count] > 0)];
328 [self setDeleteCookiesButtonEnabled:NO];
330 for (NSTreeNode* treeNode in nodes) {
331 CocoaCookieTreeNode* node = [treeNode representedObject];
332 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
333 if (cookie->GetDetailedInfo().node_type !=
334 CookieTreeNode::DetailedInfo::TYPE_HOST) {
337 CookieTreeHostNode* host_node =
338 static_cast<CookieTreeHostNode*>(cookie);
339 if (host_node->CanCreateContentException()) {
340 if (isAllowedOutlineView) {
341 [self setAllowedCookiesButtonsEnabled:YES];
343 [self setBlockedCookiesButtonsEnabled:YES];
348 if (isAllowedOutlineView) {
349 [self setAllowedCookiesButtonsEnabled:NO];
351 [self setBlockedCookiesButtonsEnabled:NO];
355 // Initializes the |allowedTreeModel_| and |blockedTreeModel_|, and builds
356 // the |cocoaAllowedTreeModel_| and |cocoaBlockedTreeModel_|.
357 - (void)loadTreeModelFromWebContents {
358 TabSpecificContentSettings* content_settings =
359 TabSpecificContentSettings::FromWebContents(webContents_);
361 const LocalSharedObjectsContainer& allowed_data =
362 content_settings->allowed_local_shared_objects();
363 allowedTreeModel_ = allowed_data.CreateCookiesTreeModel();
365 const LocalSharedObjectsContainer& blocked_data =
366 content_settings->blocked_local_shared_objects();
367 blockedTreeModel_ = blocked_data.CreateCookiesTreeModel();
369 // Convert the model's icons from Skia to Cocoa.
370 std::vector<gfx::ImageSkia> skiaIcons;
371 allowedTreeModel_->GetIcons(&skiaIcons);
372 icons_.reset([[NSMutableArray alloc] init]);
373 for (std::vector<gfx::ImageSkia>::iterator it = skiaIcons.begin();
374 it != skiaIcons.end(); ++it) {
375 [icons_ addObject:gfx::NSImageFromImageSkia(*it)];
378 // Default icon will be the last item in the array.
379 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
380 // TODO(rsesek): Rename this resource now that it's in multiple places.
382 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).ToNSImage()];
384 // Create the Cocoa model.
385 allowedControllerBridge_.reset(
386 new CookiesTreeControllerBridge(allowedTreeModel_.get()));
387 blockedControllerBridge_.reset(
388 new CookiesTreeControllerBridge(blockedTreeModel_.get()));
391 - (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting {
394 case CONTENT_SETTING_BLOCK:
395 label = l10n_util::GetNSString(
396 IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED);
399 case CONTENT_SETTING_ALLOW:
400 label = l10n_util::GetNSString(
401 IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED);
404 case CONTENT_SETTING_SESSION_ONLY:
405 label = l10n_util::GetNSString(
406 IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED);
411 label = [[[NSString alloc] init] autorelease];
413 [infoBarText_ setStringValue:label];
414 [self animateInfoBar];
417 + (NSArray*)normalizeNodeSelection:(NSArray*)selection {
418 NSMutableArray* normalized = [NSMutableArray arrayWithArray:selection];
419 for (NSTreeNode* node in selection) {
420 NSTreeNode* parent = node;
421 while ((parent = [parent parentNode])) {
422 if ([normalized containsObject:parent]) {
423 [normalized removeObject:node];
431 - (void)showInfoBarForDomain:(const base::string16&)domain
432 setting:(ContentSetting)setting {
435 case CONTENT_SETTING_BLOCK:
436 label = l10n_util::GetNSStringF(
437 IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
441 case CONTENT_SETTING_ALLOW:
442 label = l10n_util::GetNSStringF(
443 IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
447 case CONTENT_SETTING_SESSION_ONLY:
448 label = l10n_util::GetNSStringF(
449 IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
455 label = [[[NSString alloc] init] autorelease];
457 [infoBarText_ setStringValue:label];
458 [self animateInfoBar];
461 - (void)animateInfoBar {
465 infoBarVisible_ = YES;
467 NSWindow* sheet = [self window];
468 NSRect sheetFrame = [sheet frame];
469 NSRect infoBarFrame = [infoBar_ frame];
470 NSRect tabViewFrame = [tabView_ frame];
472 // Calculate the end position of the info bar and set it to its start
474 infoBarFrame.origin.y = NSHeight(sheetFrame);
475 infoBarFrame.size.width = NSWidth(sheetFrame);
476 [infoBar_ setFrame:infoBarFrame];
477 [[[self window] contentView] addSubview:infoBar_];
479 // Calculate the new position of the sheet.
480 sheetFrame.origin.y -= NSHeight(infoBarFrame);
481 sheetFrame.size.height += NSHeight(infoBarFrame);
483 NSArray* animations = @[
484 // Slide the infobar in.
486 NSViewAnimationTargetKey : infoBar_,
487 NSViewAnimationEndFrameKey : [NSValue valueWithRect:infoBarFrame]
489 // Make sure the tab view ends up in the right position.
491 NSViewAnimationTargetKey : tabView_,
492 NSViewAnimationEndFrameKey : [NSValue valueWithRect:tabViewFrame]
496 NSViewAnimationTargetKey : sheet,
497 NSViewAnimationEndFrameKey : [NSValue valueWithRect:sheetFrame]
501 [animation_ setViewAnimations:animations];
502 // The default duration is 0.5s, which actually feels slow in here, so speed
504 [animation_ gtm_setDuration:0.2 eventMask:NSLeftMouseUpMask];
505 [animation_ startAnimation];
508 - (void)tabView:(NSTabView*)tabView
509 didSelectTabViewItem:(NSTabViewItem*)tabViewItem {
510 NSTreeController* treeController = nil;
511 switch ([tabView indexOfTabViewItem:tabViewItem]) {
512 case kAllowedCookiesTabIndex:
513 treeController = allowedTreeController_;
515 case kBlockedCookiesTabIndex:
516 treeController = blockedTreeController_;
522 [detailsViewController_ configureBindingsForTreeController:treeController];