collected_cookies_mac.mm revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 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.
4
5#import "chrome/browser/ui/cocoa/content_settings/collected_cookies_mac.h"
6
7#include <vector>
8
9#import "base/mac/mac_util.h"
10#include "base/sys_string_conversions.h"
11#include "chrome/browser/profiles/profile.h"
12#import "chrome/browser/ui/cocoa/content_settings/cookie_details_view_controller.h"
13#import "chrome/browser/ui/cocoa/vertical_gradient_view.h"
14#include "chrome/browser/ui/collected_cookies_infobar_delegate.h"
15#include "content/browser/tab_contents/tab_contents.h"
16#include "content/common/notification_details.h"
17#include "content/common/notification_source.h"
18#include "grit/generated_resources.h"
19#include "grit/theme_resources.h"
20#include "skia/ext/skia_utils_mac.h"
21#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
22#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23#include "third_party/apple/ImageAndTextCell.h"
24#include "third_party/skia/include/core/SkBitmap.h"
25#include "ui/base/l10n/l10n_util_mac.h"
26#include "ui/base/resource/resource_bundle.h"
27#include "ui/gfx/image.h"
28
29namespace {
30// Colors for the infobar.
31const double kBannerGradientColorTop[3] =
32    {255.0 / 255.0, 242.0 / 255.0, 183.0 / 255.0};
33const double kBannerGradientColorBottom[3] =
34    {250.0 / 255.0, 230.0 / 255.0, 145.0 / 255.0};
35const double kBannerStrokeColor = 135.0 / 255.0;
36
37enum TabViewItemIndices {
38  kAllowedCookiesTabIndex = 0,
39  kBlockedCookiesTabIndex
40};
41
42} // namespace
43
44#pragma mark Bridge between the constrained window delegate and the sheet
45
46// The delegate used to forward the events from the sheet to the constrained
47// window delegate.
48@interface CollectedCookiesSheetBridge : NSObject {
49  CollectedCookiesMac* collectedCookies_;  // weak
50}
51- (id)initWithCollectedCookiesMac:(CollectedCookiesMac*)collectedCookies;
52- (void)sheetDidEnd:(NSWindow*)sheet
53         returnCode:(int)returnCode
54        contextInfo:(void*)contextInfo;
55@end
56
57@implementation CollectedCookiesSheetBridge
58- (id)initWithCollectedCookiesMac:(CollectedCookiesMac*)collectedCookies {
59  if ((self = [super init])) {
60    collectedCookies_ = collectedCookies;
61  }
62  return self;
63}
64
65- (void)sheetDidEnd:(NSWindow*)sheet
66         returnCode:(int)returnCode
67        contextInfo:(void*)contextInfo {
68  collectedCookies_->OnSheetDidEnd(sheet);
69}
70@end
71
72#pragma mark Constrained window delegate
73
74CollectedCookiesMac::CollectedCookiesMac(NSWindow* parent,
75                                         TabContents* tab_contents)
76    : ConstrainedWindowMacDelegateCustomSheet(
77        [[[CollectedCookiesSheetBridge alloc]
78            initWithCollectedCookiesMac:this] autorelease],
79        @selector(sheetDidEnd:returnCode:contextInfo:)),
80      tab_contents_(tab_contents) {
81  TabSpecificContentSettings* content_settings =
82      tab_contents->GetTabSpecificContentSettings();
83  registrar_.Add(this, NotificationType::COLLECTED_COOKIES_SHOWN,
84                 Source<TabSpecificContentSettings>(content_settings));
85
86  sheet_controller_ = [[CollectedCookiesWindowController alloc]
87      initWithTabContents:tab_contents];
88
89  set_sheet([sheet_controller_ window]);
90
91  window_ = tab_contents->CreateConstrainedDialog(this);
92}
93
94CollectedCookiesMac::~CollectedCookiesMac() {
95  NSWindow* window = [sheet_controller_ window];
96  if (window_ && window && is_sheet_open()) {
97    window_ = NULL;
98    [NSApp endSheet:window];
99  }
100}
101
102void CollectedCookiesMac::DeleteDelegate() {
103  delete this;
104}
105
106void CollectedCookiesMac::Observe(NotificationType type,
107                                  const NotificationSource& source,
108                                  const NotificationDetails& details) {
109  DCHECK(type == NotificationType::COLLECTED_COOKIES_SHOWN);
110  DCHECK_EQ(Source<TabSpecificContentSettings>(source).ptr(),
111            tab_contents_->GetTabSpecificContentSettings());
112  window_->CloseConstrainedWindow();
113}
114
115void CollectedCookiesMac::OnSheetDidEnd(NSWindow* sheet) {
116  [sheet orderOut:sheet_controller_];
117  if (window_)
118    window_->CloseConstrainedWindow();
119}
120
121#pragma mark Window Controller
122
123@interface CollectedCookiesWindowController(Private)
124-(void)showInfoBarForDomain:(const string16&)domain
125                    setting:(ContentSetting)setting;
126-(void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting;
127-(void)animateInfoBar;
128@end
129
130@implementation CollectedCookiesWindowController
131
132@synthesize allowedCookiesButtonsEnabled =
133    allowedCookiesButtonsEnabled_;
134@synthesize blockedCookiesButtonsEnabled =
135    blockedCookiesButtonsEnabled_;
136
137@synthesize allowedTreeController = allowedTreeController_;
138@synthesize blockedTreeController = blockedTreeController_;
139
140- (id)initWithTabContents:(TabContents*)tabContents {
141  DCHECK(tabContents);
142
143  NSString* nibpath =
144      [base::mac::MainAppBundle() pathForResource:@"CollectedCookies"
145                                          ofType:@"nib"];
146  if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
147    tabContents_ = tabContents;
148    [self loadTreeModelFromTabContents];
149
150    animation_.reset([[NSViewAnimation alloc] init]);
151    [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
152  }
153  return self;
154}
155
156- (void)awakeFromNib {
157  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
158  NSImage* infoIcon = rb.GetNativeImageNamed(IDR_INFO);
159  DCHECK(infoIcon);
160  [infoBarIcon_ setImage:infoIcon];
161
162  // Initialize the banner gradient and stroke color.
163  NSColor* bannerStartingColor =
164      [NSColor colorWithCalibratedRed:kBannerGradientColorTop[0]
165                                green:kBannerGradientColorTop[1]
166                                 blue:kBannerGradientColorTop[2]
167                                alpha:1.0];
168  NSColor* bannerEndingColor =
169      [NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0]
170                                green:kBannerGradientColorBottom[1]
171                                 blue:kBannerGradientColorBottom[2]
172                                alpha:1.0];
173  scoped_nsobject<NSGradient> bannerGradient(
174      [[NSGradient alloc] initWithStartingColor:bannerStartingColor
175                                    endingColor:bannerEndingColor]);
176  [infoBar_ setGradient:bannerGradient];
177
178  NSColor* bannerStrokeColor =
179      [NSColor colorWithCalibratedWhite:kBannerStrokeColor
180                                  alpha:1.0];
181  [infoBar_ setStrokeColor:bannerStrokeColor];
182
183  // Change the label of the blocked cookies part if necessary.
184  if (tabContents_->profile()->GetHostContentSettingsMap()->
185      BlockThirdPartyCookies()) {
186    [blockedCookiesText_ setStringValue:l10n_util::GetNSString(
187        IDS_COLLECTED_COOKIES_BLOCKED_THIRD_PARTY_BLOCKING_ENABLED)];
188    CGFloat textDeltaY = [GTMUILocalizerAndLayoutTweaker
189        sizeToFitFixedWidthTextField:blockedCookiesText_];
190
191    // Shrink the blocked cookies outline view.
192    NSRect frame = [blockedScrollView_ frame];
193    frame.size.height -= textDeltaY;
194    [blockedScrollView_ setFrame:frame];
195
196    // Move the label down so it actually fits.
197    frame = [blockedCookiesText_ frame];
198    frame.origin.y -= textDeltaY;
199    [blockedCookiesText_ setFrame:frame];
200  }
201
202  detailsViewController_.reset([[CookieDetailsViewController alloc] init]);
203
204  NSView* detailView = [detailsViewController_.get() view];
205  NSRect viewFrameRect = [cookieDetailsViewPlaceholder_ frame];
206  [[detailsViewController_.get() view] setFrame:viewFrameRect];
207  [[cookieDetailsViewPlaceholder_ superview]
208      replaceSubview:cookieDetailsViewPlaceholder_
209                with:detailView];
210
211  [self tabView:tabView_ didSelectTabViewItem:[tabView_ selectedTabViewItem]];
212}
213
214- (void)windowWillClose:(NSNotification*)notif {
215  if (contentSettingsChanged_) {
216    tabContents_->AddInfoBar(
217        new CollectedCookiesInfoBarDelegate(tabContents_));
218  }
219  [allowedOutlineView_ setDelegate:nil];
220  [blockedOutlineView_ setDelegate:nil];
221  [animation_ stopAnimation];
222  [self autorelease];
223}
224
225- (IBAction)closeSheet:(id)sender {
226  [NSApp endSheet:[self window]];
227}
228
229- (void)addException:(ContentSetting)setting
230   forTreeController:(NSTreeController*)controller {
231  NSArray* nodes = [controller selectedNodes];
232  BOOL multipleDomainsChanged = NO;
233  string16 lastDomain;
234  for (NSTreeNode* treeNode in nodes) {
235    CocoaCookieTreeNode* node = [treeNode representedObject];
236    CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
237    if (cookie->GetDetailedInfo().node_type !=
238        CookieTreeNode::DetailedInfo::TYPE_ORIGIN) {
239      continue;
240    }
241    CookieTreeOriginNode* origin_node =
242        static_cast<CookieTreeOriginNode*>(cookie);
243    origin_node->CreateContentException(
244        tabContents_->profile()->GetHostContentSettingsMap(),
245        setting);
246    if (!lastDomain.empty())
247      multipleDomainsChanged = YES;
248    lastDomain = origin_node->GetTitle();
249  }
250  if (multipleDomainsChanged)
251    [self showInfoBarForMultipleDomainsAndSetting:setting];
252  else
253    [self showInfoBarForDomain:lastDomain setting:setting];
254  contentSettingsChanged_ = YES;
255}
256
257- (IBAction)allowOrigin:(id)sender {
258  [self    addException:CONTENT_SETTING_ALLOW
259      forTreeController:blockedTreeController_];
260}
261
262- (IBAction)allowForSessionFromOrigin:(id)sender {
263  [self    addException:CONTENT_SETTING_SESSION_ONLY
264      forTreeController:blockedTreeController_];
265}
266
267- (IBAction)blockOrigin:(id)sender {
268  [self    addException:CONTENT_SETTING_BLOCK
269      forTreeController:allowedTreeController_];
270}
271
272- (CocoaCookieTreeNode*)cocoaAllowedTreeModel {
273  return cocoaAllowedTreeModel_.get();
274}
275- (void)setCocoaAllowedTreeModel:(CocoaCookieTreeNode*)model {
276  cocoaAllowedTreeModel_.reset([model retain]);
277}
278
279- (CookiesTreeModel*)allowedTreeModel {
280  return allowedTreeModel_.get();
281}
282
283- (CocoaCookieTreeNode*)cocoaBlockedTreeModel {
284  return cocoaBlockedTreeModel_.get();
285}
286- (void)setCocoaBlockedTreeModel:(CocoaCookieTreeNode*)model {
287  cocoaBlockedTreeModel_.reset([model retain]);
288}
289
290- (CookiesTreeModel*)blockedTreeModel {
291  return blockedTreeModel_.get();
292}
293
294- (void)outlineView:(NSOutlineView*)outlineView
295    willDisplayCell:(id)cell
296     forTableColumn:(NSTableColumn*)tableColumn
297               item:(id)item {
298  CocoaCookieTreeNode* node = [item representedObject];
299  int index;
300  if (outlineView == allowedOutlineView_)
301    index = allowedTreeModel_->GetIconIndex([node treeNode]);
302  else
303    index = blockedTreeModel_->GetIconIndex([node treeNode]);
304  NSImage* icon = nil;
305  if (index >= 0)
306    icon = [icons_ objectAtIndex:index];
307  else
308    icon = [icons_ lastObject];
309  DCHECK([cell isKindOfClass:[ImageAndTextCell class]]);
310  [static_cast<ImageAndTextCell*>(cell) setImage:icon];
311}
312
313- (void)outlineViewSelectionDidChange:(NSNotification*)notif {
314  BOOL isAllowedOutlineView;
315  if ([notif object] == allowedOutlineView_) {
316    isAllowedOutlineView = YES;
317  } else if ([notif object] == blockedOutlineView_) {
318    isAllowedOutlineView = NO;
319  } else {
320    NOTREACHED();
321    return;
322  }
323  NSTreeController* controller =
324      isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_;
325
326  NSArray* nodes = [controller selectedNodes];
327  for (NSTreeNode* treeNode in nodes) {
328    CocoaCookieTreeNode* node = [treeNode representedObject];
329    CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
330    if (cookie->GetDetailedInfo().node_type !=
331        CookieTreeNode::DetailedInfo::TYPE_ORIGIN) {
332      continue;
333    }
334   CookieTreeOriginNode* origin_node =
335       static_cast<CookieTreeOriginNode*>(cookie);
336   if (origin_node->CanCreateContentException()) {
337      if (isAllowedOutlineView) {
338        [self setAllowedCookiesButtonsEnabled:YES];
339      } else {
340        [self setBlockedCookiesButtonsEnabled:YES];
341      }
342      return;
343    }
344  }
345  if (isAllowedOutlineView) {
346    [self setAllowedCookiesButtonsEnabled:NO];
347  } else {
348    [self setBlockedCookiesButtonsEnabled:NO];
349  }
350}
351
352// Initializes the |allowedTreeModel_| and |blockedTreeModel_|, and builds
353// the |cocoaAllowedTreeModel_| and |cocoaBlockedTreeModel_|.
354- (void)loadTreeModelFromTabContents {
355  TabSpecificContentSettings* content_settings =
356      tabContents_->GetTabSpecificContentSettings();
357  allowedTreeModel_.reset(content_settings->GetAllowedCookiesTreeModel());
358  blockedTreeModel_.reset(content_settings->GetBlockedCookiesTreeModel());
359
360  // Convert the model's icons from Skia to Cocoa.
361  std::vector<SkBitmap> skiaIcons;
362  allowedTreeModel_->GetIcons(&skiaIcons);
363  icons_.reset([[NSMutableArray alloc] init]);
364  for (std::vector<SkBitmap>::iterator it = skiaIcons.begin();
365       it != skiaIcons.end(); ++it) {
366    [icons_ addObject:gfx::SkBitmapToNSImage(*it)];
367  }
368
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:rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER)];
373
374  // Create the Cocoa model.
375  CookieTreeNode* root =
376      static_cast<CookieTreeNode*>(allowedTreeModel_->GetRoot());
377  scoped_nsobject<CocoaCookieTreeNode> model(
378      [[CocoaCookieTreeNode alloc] initWithNode:root]);
379  [self setCocoaAllowedTreeModel:model.get()];  // Takes ownership.
380  root = static_cast<CookieTreeNode*>(blockedTreeModel_->GetRoot());
381  model.reset(
382      [[CocoaCookieTreeNode alloc] initWithNode:root]);
383  [self setCocoaBlockedTreeModel:model.get()];  // Takes ownership.
384}
385
386-(void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting {
387  NSString* label;
388  switch (setting) {
389    case CONTENT_SETTING_BLOCK:
390      label = l10n_util::GetNSString(
391          IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED);
392      break;
393
394    case CONTENT_SETTING_ALLOW:
395      label = l10n_util::GetNSString(
396          IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED);
397      break;
398
399    case CONTENT_SETTING_SESSION_ONLY:
400      label = l10n_util::GetNSString(
401          IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED);
402      break;
403
404    default:
405      NOTREACHED();
406      label = [[[NSString alloc] init] autorelease];
407  }
408  [infoBarText_ setStringValue:label];
409  [self animateInfoBar];
410}
411
412-(void)showInfoBarForDomain:(const string16&)domain
413                    setting:(ContentSetting)setting {
414  NSString* label;
415  switch (setting) {
416    case CONTENT_SETTING_BLOCK:
417      label = l10n_util::GetNSStringF(
418          IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
419          domain);
420      break;
421
422    case CONTENT_SETTING_ALLOW:
423      label = l10n_util::GetNSStringF(
424          IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
425          domain);
426      break;
427
428    case CONTENT_SETTING_SESSION_ONLY:
429      label = l10n_util::GetNSStringF(
430          IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
431          domain);
432      break;
433
434    default:
435      NOTREACHED();
436      label = [[[NSString alloc] init] autorelease];
437  }
438  [infoBarText_ setStringValue:label];
439  [self animateInfoBar];
440}
441
442-(void)animateInfoBar {
443  if (infoBarVisible_)
444    return;
445
446  infoBarVisible_ = YES;
447
448  NSMutableArray* animations = [NSMutableArray arrayWithCapacity:3];
449
450  NSWindow* sheet = [self window];
451  NSRect sheetFrame = [sheet frame];
452  NSRect infoBarFrame = [infoBar_ frame];
453  NSRect tabViewFrame = [tabView_ frame];
454
455  // Calculate the end position of the info bar and set it to its start
456  // position.
457  infoBarFrame.origin.y = NSHeight(sheetFrame);
458  infoBarFrame.size.width = NSWidth(sheetFrame);
459  [infoBar_ setFrame:infoBarFrame];
460  [[[self window] contentView] addSubview:infoBar_];
461
462  // Calculate the new position of the sheet.
463  sheetFrame.origin.y -= NSHeight(infoBarFrame);
464  sheetFrame.size.height += NSHeight(infoBarFrame);
465
466  // Slide the infobar in.
467  [animations addObject:
468      [NSDictionary dictionaryWithObjectsAndKeys:
469          infoBar_, NSViewAnimationTargetKey,
470          [NSValue valueWithRect:infoBarFrame],
471              NSViewAnimationEndFrameKey,
472          nil]];
473  // Make sure the tab view ends up in the right position.
474  [animations addObject:
475      [NSDictionary dictionaryWithObjectsAndKeys:
476          tabView_, NSViewAnimationTargetKey,
477          [NSValue valueWithRect:tabViewFrame],
478              NSViewAnimationEndFrameKey,
479          nil]];
480
481  // Grow the sheet.
482  [animations addObject:
483      [NSDictionary dictionaryWithObjectsAndKeys:
484          sheet, NSViewAnimationTargetKey,
485          [NSValue valueWithRect:sheetFrame],
486              NSViewAnimationEndFrameKey,
487          nil]];
488  [animation_ setViewAnimations:animations];
489  // The default duration is 0.5s, which actually feels slow in here, so speed
490  // it up a bit.
491  [animation_ gtm_setDuration:0.2
492                    eventMask:NSLeftMouseUpMask];
493  [animation_ startAnimation];
494}
495
496- (void)         tabView:(NSTabView*)tabView
497    didSelectTabViewItem:(NSTabViewItem*)tabViewItem {
498  NSTreeController* treeController = nil;
499  switch ([tabView indexOfTabViewItem:tabViewItem]) {
500    case kAllowedCookiesTabIndex:
501      treeController = allowedTreeController_;
502      break;
503    case kBlockedCookiesTabIndex:
504      treeController = blockedTreeController_;
505      break;
506    default:
507      NOTREACHED();
508      return;
509  }
510  [detailsViewController_ configureBindingsForTreeController:treeController];
511}
512
513@end
514