Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / content_settings / collected_cookies_mac.mm
blob5cc1a6b644ca2a6495bc785d09568c06e2bc6dda
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"
7 #include <vector>
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"
39 namespace {
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
52 } // namespace
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;
99 @end
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 {
123   DCHECK(webContents);
125   NSString* nibpath =
126       [base::mac::FrameworkBundle() pathForResource:@"CollectedCookies"
127                                              ofType:@"nib"];
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];
135   }
136   return self;
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]
149                                 alpha:1.0];
150   NSColor* bannerEndingColor =
151       [NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0]
152                                 green:kBannerGradientColorBottom[1]
153                                  blue:kBannerGradientColorBottom[2]
154                                 alpha:1.0];
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
162                                   alpha:1.0];
163   [infoBar_ setStrokeColor:bannerStrokeColor];
165   // Change the label of the blocked cookies part if necessary.
166   Profile* profile =
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];
183   }
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_
192                 with:detailView];
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_));
207   }
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) {
227       continue;
228     }
229     Profile* profile =
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();
238   }
239   if (multipleDomainsChanged)
240     [self showInfoBarForMultipleDomainsAndSetting:setting];
241   else
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);
268   }
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
290                item:(id)item {
291   CocoaCookieTreeNode* node = [item representedObject];
292   int index;
293   if (outlineView == allowedOutlineView_)
294     index = allowedTreeModel_->GetIconIndex([node treeNode]);
295   else
296     index = blockedTreeModel_->GetIconIndex([node treeNode]);
297   NSImage* icon = nil;
298   if (index >= 0)
299     icon = [icons_ objectAtIndex:index];
300   else
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;
312   } else {
313     NOTREACHED();
314     return;
315   }
317   NSTreeController* controller =
318       isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_;
319   NSArray* nodes = [controller selectedNodes];
321   if (isAllowedOutlineView)
322     [self setDeleteCookiesButtonEnabled:([nodes count] > 0)];
323   else
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) {
331       continue;
332     }
333    CookieTreeHostNode* host_node =
334        static_cast<CookieTreeHostNode*>(cookie);
335    if (host_node->CanCreateContentException()) {
336       if (isAllowedOutlineView) {
337         [self setAllowedCookiesButtonsEnabled:YES];
338       } else {
339         [self setBlockedCookiesButtonsEnabled:YES];
340       }
341       return;
342     }
343   }
344   if (isAllowedOutlineView) {
345     [self setAllowedCookiesButtonsEnabled:NO];
346   } else {
347     [self setBlockedCookiesButtonsEnabled:NO];
348   }
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)];
367   }
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:
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 {
383   NSString* label;
384   switch (setting) {
385     case CONTENT_SETTING_BLOCK:
386       label = l10n_util::GetNSString(
387           IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED);
388       break;
390     case CONTENT_SETTING_ALLOW:
391       label = l10n_util::GetNSString(
392           IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED);
393       break;
395     case CONTENT_SETTING_SESSION_ONLY:
396       label = l10n_util::GetNSString(
397           IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED);
398       break;
400     default:
401       NOTREACHED();
402       label = [[[NSString alloc] init] autorelease];
403   }
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];
415         break;
416       }
417     }
418   }
419   return normalized;
422 - (void)showInfoBarForDomain:(const base::string16&)domain
423                      setting:(ContentSetting)setting {
424   NSString* label;
425   switch (setting) {
426     case CONTENT_SETTING_BLOCK:
427       label = l10n_util::GetNSStringF(
428           IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
429           domain);
430       break;
432     case CONTENT_SETTING_ALLOW:
433       label = l10n_util::GetNSStringF(
434           IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
435           domain);
436       break;
438     case CONTENT_SETTING_SESSION_ONLY:
439       label = l10n_util::GetNSStringF(
440           IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
441           domain);
442       break;
444     default:
445       NOTREACHED();
446       label = [[[NSString alloc] init] autorelease];
447   }
448   [infoBarText_ setStringValue:label];
449   [self animateInfoBar];
452 - (void)animateInfoBar {
453   if (infoBarVisible_)
454     return;
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
464   // position.
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.
476     @{
477       NSViewAnimationTargetKey : infoBar_,
478       NSViewAnimationEndFrameKey : [NSValue valueWithRect:infoBarFrame]
479     },
480     // Make sure the tab view ends up in the right position.
481     @{
482       NSViewAnimationTargetKey : tabView_,
483       NSViewAnimationEndFrameKey : [NSValue valueWithRect:tabViewFrame]
484     },
485     // Grow the sheet.
486     @{
487       NSViewAnimationTargetKey : sheet,
488       NSViewAnimationEndFrameKey : [NSValue valueWithRect:sheetFrame]
489     }
490   ];
492   [animation_ setViewAnimations:animations];
493   // The default duration is 0.5s, which actually feels slow in here, so speed
494   // it up a bit.
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_;
505       break;
506     case kBlockedCookiesTabIndex:
507       treeController = blockedTreeController_;
508       break;
509     default:
510       NOTREACHED();
511       return;
512   }
513   [detailsViewController_ configureBindingsForTreeController:treeController];
516 @end