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_factory.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 "components/content_settings/core/browser/cookie_settings.h"
25 #include "components/content_settings/core/common/pref_names.h"
26 #include "content/public/browser/notification_details.h"
27 #include "content/public/browser/notification_source.h"
28 #include "content/public/browser/web_contents.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
54 #pragma mark Constrained window delegate
56 CollectedCookiesMac::CollectedCookiesMac(content::WebContents* web_contents) {
57 TabSpecificContentSettings* content_settings =
58 TabSpecificContentSettings::FromWebContents(web_contents);
59 registrar_.Add(this, chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN,
60 content::Source<TabSpecificContentSettings>(content_settings));
62 sheet_controller_.reset([[CollectedCookiesWindowController alloc]
63 initWithWebContents:web_contents
64 collectedCookiesMac:this]);
66 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
67 [[CustomConstrainedWindowSheet alloc]
68 initWithCustomWindow:[sheet_controller_ window]]);
69 window_.reset(new ConstrainedWindowMac(
70 this, web_contents, sheet));
73 CollectedCookiesMac::~CollectedCookiesMac() {
76 void CollectedCookiesMac::Observe(int type,
77 const content::NotificationSource& source,
78 const content::NotificationDetails& details) {
79 DCHECK(type == chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN);
80 window_->CloseWebContentsModalDialog();
83 void CollectedCookiesMac::PerformClose() {
84 window_->CloseWebContentsModalDialog();
87 void CollectedCookiesMac::OnConstrainedWindowClosed(
88 ConstrainedWindowMac* window) {
89 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
92 #pragma mark Window Controller
94 @interface CollectedCookiesWindowController (Private)
95 - (void)showInfoBarForDomain:(const base::string16&)domain
96 setting:(ContentSetting)setting;
97 - (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting;
98 - (void)animateInfoBar;
101 @implementation CollectedCookiesWindowController
103 @synthesize allowedTreeController = allowedTreeController_;
104 @synthesize blockedTreeController = blockedTreeController_;
105 @synthesize allowedOutlineView = allowedOutlineView_;
106 @synthesize blockedOutlineView = blockedOutlineView_;
107 @synthesize infoBar = infoBar_;
108 @synthesize infoBarIcon = infoBarIcon_;
109 @synthesize infoBarText = infoBarText_;
110 @synthesize tabView = tabView_;
111 @synthesize blockedScrollView = blockedScrollView_;
112 @synthesize blockedCookiesText = blockedCookiesText_;
113 @synthesize cookieDetailsViewPlaceholder = cookieDetailsViewPlaceholder_;
115 @synthesize allowedCookiesButtonsEnabled =
116 allowedCookiesButtonsEnabled_;
117 @synthesize blockedCookiesButtonsEnabled =
118 blockedCookiesButtonsEnabled_;
119 @synthesize deleteCookiesButtonEnabled = deleteCookiesButtonEnabled_;
121 - (id)initWithWebContents:(content::WebContents*)webContents
122 collectedCookiesMac:(CollectedCookiesMac*)collectedCookiesMac {
126 [base::mac::FrameworkBundle() pathForResource:@"CollectedCookies"
128 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
129 webContents_ = webContents;
130 collectedCookiesMac_ = collectedCookiesMac;
131 [self loadTreeModelFromWebContents];
133 animation_.reset([[NSViewAnimation alloc] init]);
134 [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
139 - (void)awakeFromNib {
140 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
141 NSImage* infoIcon = rb.GetNativeImageNamed(IDR_INFO).ToNSImage();
142 [infoBarIcon_ setImage:infoIcon];
144 // Initialize the banner gradient and stroke color.
145 NSColor* bannerStartingColor =
146 [NSColor colorWithCalibratedRed:kBannerGradientColorTop[0]
147 green:kBannerGradientColorTop[1]
148 blue:kBannerGradientColorTop[2]
150 NSColor* bannerEndingColor =
151 [NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0]
152 green:kBannerGradientColorBottom[1]
153 blue:kBannerGradientColorBottom[2]
155 base::scoped_nsobject<NSGradient> bannerGradient(
156 [[NSGradient alloc] initWithStartingColor:bannerStartingColor
157 endingColor:bannerEndingColor]);
158 [infoBar_ setGradient:bannerGradient];
160 NSColor* bannerStrokeColor =
161 [NSColor colorWithCalibratedWhite:kBannerStrokeColor
163 [infoBar_ setStrokeColor:bannerStrokeColor];
165 // Change the label of the blocked cookies part if necessary.
167 Profile::FromBrowserContext(webContents_->GetBrowserContext());
168 if (profile->GetPrefs()->GetBoolean(prefs::kBlockThirdPartyCookies)) {
169 [blockedCookiesText_ setStringValue:l10n_util::GetNSString(
170 IDS_COLLECTED_COOKIES_BLOCKED_THIRD_PARTY_BLOCKING_ENABLED)];
171 CGFloat textDeltaY = [GTMUILocalizerAndLayoutTweaker
172 sizeToFitFixedWidthTextField:blockedCookiesText_];
174 // Shrink the blocked cookies outline view.
175 NSRect frame = [blockedScrollView_ frame];
176 frame.size.height -= textDeltaY;
177 [blockedScrollView_ setFrame:frame];
179 // Move the label down so it actually fits.
180 frame = [blockedCookiesText_ frame];
181 frame.origin.y -= textDeltaY;
182 [blockedCookiesText_ setFrame:frame];
185 detailsViewController_.reset([[CookieDetailsViewController alloc] init]);
187 NSView* detailView = [detailsViewController_.get() view];
188 NSRect viewFrameRect = [cookieDetailsViewPlaceholder_ frame];
189 [[detailsViewController_.get() view] setFrame:viewFrameRect];
190 [[cookieDetailsViewPlaceholder_ superview]
191 replaceSubview:cookieDetailsViewPlaceholder_
194 [self tabView:tabView_ didSelectTabViewItem:[tabView_ selectedTabViewItem]];
197 - (void)windowWillClose:(NSNotification*)notif {
198 // If the user closes our parent tab while we're still open, this method will
199 // (eventually) be called in response to a WebContentsDestroyed() call from
200 // the WebContentsImpl to its observers. But since the InfoBarService is also
201 // torn down in response to WebContentsDestroyed(), it may already be null.
202 // Since the tab is going away anyway, we can just omit showing an infobar,
203 // which prevents any attempt to access a null InfoBarService.
204 if (contentSettingsChanged_ && !webContents_->IsBeingDestroyed()) {
205 CollectedCookiesInfoBarDelegate::Create(
206 InfoBarService::FromWebContents(webContents_));
208 [allowedOutlineView_ setDelegate:nil];
209 [blockedOutlineView_ setDelegate:nil];
210 [animation_ stopAnimation];
213 - (IBAction)closeSheet:(id)sender {
214 collectedCookiesMac_->PerformClose();
217 - (void)addException:(ContentSetting)setting
218 forTreeController:(NSTreeController*)controller {
219 NSArray* nodes = [controller selectedNodes];
220 BOOL multipleDomainsChanged = NO;
221 base::string16 lastDomain;
222 for (NSTreeNode* treeNode in nodes) {
223 CocoaCookieTreeNode* node = [treeNode representedObject];
224 CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
225 if (cookie->GetDetailedInfo().node_type !=
226 CookieTreeNode::DetailedInfo::TYPE_HOST) {
230 Profile::FromBrowserContext(webContents_->GetBrowserContext());
231 CookieTreeHostNode* host_node =
232 static_cast<CookieTreeHostNode*>(cookie);
233 host_node->CreateContentException(
234 CookieSettingsFactory::GetForProfile(profile).get(), setting);
235 if (!lastDomain.empty())
236 multipleDomainsChanged = YES;
237 lastDomain = host_node->GetTitle();
239 if (multipleDomainsChanged)
240 [self showInfoBarForMultipleDomainsAndSetting:setting];
242 [self showInfoBarForDomain:lastDomain setting:setting];
243 contentSettingsChanged_ = YES;
246 - (IBAction)allowOrigin:(id)sender {
247 [self addException:CONTENT_SETTING_ALLOW
248 forTreeController:blockedTreeController_];
251 - (IBAction)allowForSessionFromOrigin:(id)sender {
252 [self addException:CONTENT_SETTING_SESSION_ONLY
253 forTreeController:blockedTreeController_];
256 - (IBAction)blockOrigin:(id)sender {
257 [self addException:CONTENT_SETTING_BLOCK
258 forTreeController:allowedTreeController_];
261 - (IBAction)deleteSelected:(id)sender {
262 NSArray* nodes = [[self class] normalizeNodeSelection:
263 [allowedTreeController_ selectedNodes]];
264 for (NSTreeNode* cocoaTreeNode in nodes) {
265 CookieTreeNode* cookieNode = static_cast<CookieTreeNode*>(
266 [[cocoaTreeNode representedObject] treeNode]);
267 allowedTreeModel_->DeleteCookieNode(cookieNode);
271 - (CocoaCookieTreeNode*)cocoaAllowedTreeModel {
272 return allowedControllerBridge_->cocoa_model();
275 - (CookiesTreeModel*)allowedTreeModel {
276 return allowedTreeModel_.get();
279 - (CocoaCookieTreeNode*)cocoaBlockedTreeModel {
280 return blockedControllerBridge_->cocoa_model();
283 - (CookiesTreeModel*)blockedTreeModel {
284 return blockedTreeModel_.get();
287 - (void)outlineView:(NSOutlineView*)outlineView
288 willDisplayCell:(id)cell
289 forTableColumn:(NSTableColumn*)tableColumn
291 CocoaCookieTreeNode* node = [item representedObject];
293 if (outlineView == allowedOutlineView_)
294 index = allowedTreeModel_->GetIconIndex([node treeNode]);
296 index = blockedTreeModel_->GetIconIndex([node treeNode]);
299 icon = [icons_ objectAtIndex:index];
301 icon = [icons_ lastObject];
302 DCHECK([cell isKindOfClass:[ImageAndTextCell class]]);
303 [static_cast<ImageAndTextCell*>(cell) setImage:icon];
306 - (void)outlineViewSelectionDidChange:(NSNotification*)notif {
307 BOOL isAllowedOutlineView;
308 if ([notif object] == allowedOutlineView_) {
309 isAllowedOutlineView = YES;
310 } else if ([notif object] == blockedOutlineView_) {
311 isAllowedOutlineView = NO;
317 NSTreeController* controller =
318 isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_;
319 NSArray* nodes = [controller selectedNodes];
321 if (isAllowedOutlineView)
322 [self setDeleteCookiesButtonEnabled:([nodes count] > 0)];
324 [self setDeleteCookiesButtonEnabled:NO];
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 allowedTreeModel_ = content_settings->CreateAllowedCookiesTreeModel();
358 blockedTreeModel_ = content_settings->CreateBlockedCookiesTreeModel();
360 // Convert the model's icons from Skia to Cocoa.
361 std::vector<gfx::ImageSkia> skiaIcons;
362 allowedTreeModel_->GetIcons(&skiaIcons);
363 icons_.reset([[NSMutableArray alloc] init]);
364 for (std::vector<gfx::ImageSkia>::iterator it = skiaIcons.begin();
365 it != skiaIcons.end(); ++it) {
366 [icons_ addObject:gfx::NSImageFromImageSkia(*it)];
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.
373 rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).ToNSImage()];
375 // Create the Cocoa model.
376 allowedControllerBridge_.reset(
377 new CookiesTreeControllerBridge(allowedTreeModel_.get()));
378 blockedControllerBridge_.reset(
379 new CookiesTreeControllerBridge(blockedTreeModel_.get()));
382 - (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting {
385 case CONTENT_SETTING_BLOCK:
386 label = l10n_util::GetNSString(
387 IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED);
390 case CONTENT_SETTING_ALLOW:
391 label = l10n_util::GetNSString(
392 IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED);
395 case CONTENT_SETTING_SESSION_ONLY:
396 label = l10n_util::GetNSString(
397 IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED);
402 label = [[[NSString alloc] init] autorelease];
404 [infoBarText_ setStringValue:label];
405 [self animateInfoBar];
408 + (NSArray*)normalizeNodeSelection:(NSArray*)selection {
409 NSMutableArray* normalized = [NSMutableArray arrayWithArray:selection];
410 for (NSTreeNode* node in selection) {
411 NSTreeNode* parent = node;
412 while ((parent = [parent parentNode])) {
413 if ([normalized containsObject:parent]) {
414 [normalized removeObject:node];
422 - (void)showInfoBarForDomain:(const base::string16&)domain
423 setting:(ContentSetting)setting {
426 case CONTENT_SETTING_BLOCK:
427 label = l10n_util::GetNSStringF(
428 IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
432 case CONTENT_SETTING_ALLOW:
433 label = l10n_util::GetNSStringF(
434 IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
438 case CONTENT_SETTING_SESSION_ONLY:
439 label = l10n_util::GetNSStringF(
440 IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
446 label = [[[NSString alloc] init] autorelease];
448 [infoBarText_ setStringValue:label];
449 [self animateInfoBar];
452 - (void)animateInfoBar {
456 infoBarVisible_ = YES;
458 NSWindow* sheet = [self window];
459 NSRect sheetFrame = [sheet frame];
460 NSRect infoBarFrame = [infoBar_ frame];
461 NSRect tabViewFrame = [tabView_ frame];
463 // Calculate the end position of the info bar and set it to its start
465 infoBarFrame.origin.y = NSHeight(sheetFrame);
466 infoBarFrame.size.width = NSWidth(sheetFrame);
467 [infoBar_ setFrame:infoBarFrame];
468 [[[self window] contentView] addSubview:infoBar_];
470 // Calculate the new position of the sheet.
471 sheetFrame.origin.y -= NSHeight(infoBarFrame);
472 sheetFrame.size.height += NSHeight(infoBarFrame);
474 NSArray* animations = @[
475 // Slide the infobar in.
477 NSViewAnimationTargetKey : infoBar_,
478 NSViewAnimationEndFrameKey : [NSValue valueWithRect:infoBarFrame]
480 // Make sure the tab view ends up in the right position.
482 NSViewAnimationTargetKey : tabView_,
483 NSViewAnimationEndFrameKey : [NSValue valueWithRect:tabViewFrame]
487 NSViewAnimationTargetKey : sheet,
488 NSViewAnimationEndFrameKey : [NSValue valueWithRect:sheetFrame]
492 [animation_ setViewAnimations:animations];
493 // The default duration is 0.5s, which actually feels slow in here, so speed
495 [animation_ gtm_setDuration:0.2 eventMask:NSLeftMouseUpMask];
496 [animation_ startAnimation];
499 - (void)tabView:(NSTabView*)tabView
500 didSelectTabViewItem:(NSTabViewItem*)tabViewItem {
501 NSTreeController* treeController = nil;
502 switch ([tabView indexOfTabViewItem:tabViewItem]) {
503 case kAllowedCookiesTabIndex:
504 treeController = allowedTreeController_;
506 case kBlockedCookiesTabIndex:
507 treeController = blockedTreeController_;
513 [detailsViewController_ configureBindingsForTreeController:treeController];