blob: 52c0115bab73676b15f463ae12b23066fece6e3b [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/content_settings/collected_cookies_mac.h"
#include <vector>
#include "base/mac/bundle_locations.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/content_settings/cookie_settings.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_dialogs.h"
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
#import "chrome/browser/ui/cocoa/content_settings/cookie_details_view_controller.h"
#import "chrome/browser/ui/cocoa/vertical_gradient_view.h"
#include "chrome/browser/ui/collected_cookies_infobar_delegate.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/web_contents.h"
#include "grit/theme_resources.h"
#include "third_party/apple_sample_code/ImageAndTextCell.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_util_mac.h"
namespace {
// Colors for the infobar.
const double kBannerGradientColorTop[3] =
{255.0 / 255.0, 242.0 / 255.0, 183.0 / 255.0};
const double kBannerGradientColorBottom[3] =
{250.0 / 255.0, 230.0 / 255.0, 145.0 / 255.0};
const double kBannerStrokeColor = 135.0 / 255.0;
enum TabViewItemIndices {
kAllowedCookiesTabIndex = 0,
kBlockedCookiesTabIndex
};
} // namespace
namespace chrome {
// Declared in browser_dialogs.h so others don't have to depend on our header.
void ShowCollectedCookiesDialog(content::WebContents* web_contents) {
// Deletes itself on close.
new CollectedCookiesMac(web_contents);
}
} // namespace chrome
#pragma mark Constrained window delegate
CollectedCookiesMac::CollectedCookiesMac(content::WebContents* web_contents) {
TabSpecificContentSettings* content_settings =
TabSpecificContentSettings::FromWebContents(web_contents);
registrar_.Add(this, chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN,
content::Source<TabSpecificContentSettings>(content_settings));
sheet_controller_.reset([[CollectedCookiesWindowController alloc]
initWithWebContents:web_contents
collectedCookiesMac:this]);
base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
[[CustomConstrainedWindowSheet alloc]
initWithCustomWindow:[sheet_controller_ window]]);
window_.reset(new ConstrainedWindowMac(
this, web_contents, sheet));
}
CollectedCookiesMac::~CollectedCookiesMac() {
}
void CollectedCookiesMac::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(type == chrome::NOTIFICATION_COLLECTED_COOKIES_SHOWN);
window_->CloseWebContentsModalDialog();
}
void CollectedCookiesMac::PerformClose() {
window_->CloseWebContentsModalDialog();
}
void CollectedCookiesMac::OnConstrainedWindowClosed(
ConstrainedWindowMac* window) {
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
#pragma mark Window Controller
@interface CollectedCookiesWindowController (Private)
- (void)showInfoBarForDomain:(const base::string16&)domain
setting:(ContentSetting)setting;
- (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting;
- (void)animateInfoBar;
@end
@implementation CollectedCookiesWindowController
@synthesize allowedTreeController = allowedTreeController_;
@synthesize blockedTreeController = blockedTreeController_;
@synthesize allowedOutlineView = allowedOutlineView_;
@synthesize blockedOutlineView = blockedOutlineView_;
@synthesize infoBar = infoBar_;
@synthesize infoBarIcon = infoBarIcon_;
@synthesize infoBarText = infoBarText_;
@synthesize tabView = tabView_;
@synthesize blockedScrollView = blockedScrollView_;
@synthesize blockedCookiesText = blockedCookiesText_;
@synthesize cookieDetailsViewPlaceholder = cookieDetailsViewPlaceholder_;
@synthesize allowedCookiesButtonsEnabled =
allowedCookiesButtonsEnabled_;
@synthesize blockedCookiesButtonsEnabled =
blockedCookiesButtonsEnabled_;
@synthesize deleteCookiesButtonEnabled = deleteCookiesButtonEnabled_;
- (id)initWithWebContents:(content::WebContents*)webContents
collectedCookiesMac:(CollectedCookiesMac*)collectedCookiesMac {
DCHECK(webContents);
NSString* nibpath =
[base::mac::FrameworkBundle() pathForResource:@"CollectedCookies"
ofType:@"nib"];
if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
webContents_ = webContents;
collectedCookiesMac_ = collectedCookiesMac;
[self loadTreeModelFromWebContents];
animation_.reset([[NSViewAnimation alloc] init]);
[animation_ setAnimationBlockingMode:NSAnimationNonblocking];
}
return self;
}
- (void)awakeFromNib {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
NSImage* infoIcon = rb.GetNativeImageNamed(IDR_INFO).ToNSImage();
[infoBarIcon_ setImage:infoIcon];
// Initialize the banner gradient and stroke color.
NSColor* bannerStartingColor =
[NSColor colorWithCalibratedRed:kBannerGradientColorTop[0]
green:kBannerGradientColorTop[1]
blue:kBannerGradientColorTop[2]
alpha:1.0];
NSColor* bannerEndingColor =
[NSColor colorWithCalibratedRed:kBannerGradientColorBottom[0]
green:kBannerGradientColorBottom[1]
blue:kBannerGradientColorBottom[2]
alpha:1.0];
base::scoped_nsobject<NSGradient> bannerGradient(
[[NSGradient alloc] initWithStartingColor:bannerStartingColor
endingColor:bannerEndingColor]);
[infoBar_ setGradient:bannerGradient];
NSColor* bannerStrokeColor =
[NSColor colorWithCalibratedWhite:kBannerStrokeColor
alpha:1.0];
[infoBar_ setStrokeColor:bannerStrokeColor];
// Change the label of the blocked cookies part if necessary.
Profile* profile =
Profile::FromBrowserContext(webContents_->GetBrowserContext());
if (profile->GetPrefs()->GetBoolean(prefs::kBlockThirdPartyCookies)) {
[blockedCookiesText_ setStringValue:l10n_util::GetNSString(
IDS_COLLECTED_COOKIES_BLOCKED_THIRD_PARTY_BLOCKING_ENABLED)];
CGFloat textDeltaY = [GTMUILocalizerAndLayoutTweaker
sizeToFitFixedWidthTextField:blockedCookiesText_];
// Shrink the blocked cookies outline view.
NSRect frame = [blockedScrollView_ frame];
frame.size.height -= textDeltaY;
[blockedScrollView_ setFrame:frame];
// Move the label down so it actually fits.
frame = [blockedCookiesText_ frame];
frame.origin.y -= textDeltaY;
[blockedCookiesText_ setFrame:frame];
}
detailsViewController_.reset([[CookieDetailsViewController alloc] init]);
NSView* detailView = [detailsViewController_.get() view];
NSRect viewFrameRect = [cookieDetailsViewPlaceholder_ frame];
[[detailsViewController_.get() view] setFrame:viewFrameRect];
[[cookieDetailsViewPlaceholder_ superview]
replaceSubview:cookieDetailsViewPlaceholder_
with:detailView];
[self tabView:tabView_ didSelectTabViewItem:[tabView_ selectedTabViewItem]];
}
- (void)windowWillClose:(NSNotification*)notif {
if (contentSettingsChanged_) {
CollectedCookiesInfoBarDelegate::Create(
InfoBarService::FromWebContents(webContents_));
}
[allowedOutlineView_ setDelegate:nil];
[blockedOutlineView_ setDelegate:nil];
[animation_ stopAnimation];
}
- (IBAction)closeSheet:(id)sender {
collectedCookiesMac_->PerformClose();
}
- (void)addException:(ContentSetting)setting
forTreeController:(NSTreeController*)controller {
NSArray* nodes = [controller selectedNodes];
BOOL multipleDomainsChanged = NO;
base::string16 lastDomain;
for (NSTreeNode* treeNode in nodes) {
CocoaCookieTreeNode* node = [treeNode representedObject];
CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
if (cookie->GetDetailedInfo().node_type !=
CookieTreeNode::DetailedInfo::TYPE_HOST) {
continue;
}
Profile* profile =
Profile::FromBrowserContext(webContents_->GetBrowserContext());
CookieTreeHostNode* host_node =
static_cast<CookieTreeHostNode*>(cookie);
host_node->CreateContentException(
CookieSettings::Factory::GetForProfile(profile).get(), setting);
if (!lastDomain.empty())
multipleDomainsChanged = YES;
lastDomain = host_node->GetTitle();
}
if (multipleDomainsChanged)
[self showInfoBarForMultipleDomainsAndSetting:setting];
else
[self showInfoBarForDomain:lastDomain setting:setting];
contentSettingsChanged_ = YES;
}
- (IBAction)allowOrigin:(id)sender {
[self addException:CONTENT_SETTING_ALLOW
forTreeController:blockedTreeController_];
}
- (IBAction)allowForSessionFromOrigin:(id)sender {
[self addException:CONTENT_SETTING_SESSION_ONLY
forTreeController:blockedTreeController_];
}
- (IBAction)blockOrigin:(id)sender {
[self addException:CONTENT_SETTING_BLOCK
forTreeController:allowedTreeController_];
}
- (IBAction)deleteSelected:(id)sender {
NSArray* nodes = [[self class] normalizeNodeSelection:
[allowedTreeController_ selectedNodes]];
for (NSTreeNode* cocoaTreeNode in nodes) {
CookieTreeNode* cookieNode = static_cast<CookieTreeNode*>(
[[cocoaTreeNode representedObject] treeNode]);
allowedTreeModel_->DeleteCookieNode(cookieNode);
}
}
- (CocoaCookieTreeNode*)cocoaAllowedTreeModel {
return allowedControllerBridge_->cocoa_model();
}
- (CookiesTreeModel*)allowedTreeModel {
return allowedTreeModel_.get();
}
- (CocoaCookieTreeNode*)cocoaBlockedTreeModel {
return blockedControllerBridge_->cocoa_model();
}
- (CookiesTreeModel*)blockedTreeModel {
return blockedTreeModel_.get();
}
- (void)outlineView:(NSOutlineView*)outlineView
willDisplayCell:(id)cell
forTableColumn:(NSTableColumn*)tableColumn
item:(id)item {
CocoaCookieTreeNode* node = [item representedObject];
int index;
if (outlineView == allowedOutlineView_)
index = allowedTreeModel_->GetIconIndex([node treeNode]);
else
index = blockedTreeModel_->GetIconIndex([node treeNode]);
NSImage* icon = nil;
if (index >= 0)
icon = [icons_ objectAtIndex:index];
else
icon = [icons_ lastObject];
DCHECK([cell isKindOfClass:[ImageAndTextCell class]]);
[static_cast<ImageAndTextCell*>(cell) setImage:icon];
}
- (void)outlineViewSelectionDidChange:(NSNotification*)notif {
BOOL isAllowedOutlineView;
if ([notif object] == allowedOutlineView_) {
isAllowedOutlineView = YES;
} else if ([notif object] == blockedOutlineView_) {
isAllowedOutlineView = NO;
} else {
NOTREACHED();
return;
}
NSTreeController* controller =
isAllowedOutlineView ? allowedTreeController_ : blockedTreeController_;
NSArray* nodes = [controller selectedNodes];
if (isAllowedOutlineView)
[self setDeleteCookiesButtonEnabled:([nodes count] > 0)];
else
[self setDeleteCookiesButtonEnabled:NO];
for (NSTreeNode* treeNode in nodes) {
CocoaCookieTreeNode* node = [treeNode representedObject];
CookieTreeNode* cookie = static_cast<CookieTreeNode*>([node treeNode]);
if (cookie->GetDetailedInfo().node_type !=
CookieTreeNode::DetailedInfo::TYPE_HOST) {
continue;
}
CookieTreeHostNode* host_node =
static_cast<CookieTreeHostNode*>(cookie);
if (host_node->CanCreateContentException()) {
if (isAllowedOutlineView) {
[self setAllowedCookiesButtonsEnabled:YES];
} else {
[self setBlockedCookiesButtonsEnabled:YES];
}
return;
}
}
if (isAllowedOutlineView) {
[self setAllowedCookiesButtonsEnabled:NO];
} else {
[self setBlockedCookiesButtonsEnabled:NO];
}
}
// Initializes the |allowedTreeModel_| and |blockedTreeModel_|, and builds
// the |cocoaAllowedTreeModel_| and |cocoaBlockedTreeModel_|.
- (void)loadTreeModelFromWebContents {
TabSpecificContentSettings* content_settings =
TabSpecificContentSettings::FromWebContents(webContents_);
allowedTreeModel_ = content_settings->CreateAllowedCookiesTreeModel();
blockedTreeModel_ = content_settings->CreateBlockedCookiesTreeModel();
// Convert the model's icons from Skia to Cocoa.
std::vector<gfx::ImageSkia> skiaIcons;
allowedTreeModel_->GetIcons(&skiaIcons);
icons_.reset([[NSMutableArray alloc] init]);
for (std::vector<gfx::ImageSkia>::iterator it = skiaIcons.begin();
it != skiaIcons.end(); ++it) {
[icons_ addObject:gfx::NSImageFromImageSkia(*it)];
}
// Default icon will be the last item in the array.
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
// TODO(rsesek): Rename this resource now that it's in multiple places.
[icons_ addObject:
rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER).ToNSImage()];
// Create the Cocoa model.
allowedControllerBridge_.reset(
new CookiesTreeControllerBridge(allowedTreeModel_.get()));
blockedControllerBridge_.reset(
new CookiesTreeControllerBridge(blockedTreeModel_.get()));
}
- (void)showInfoBarForMultipleDomainsAndSetting:(ContentSetting)setting {
NSString* label;
switch (setting) {
case CONTENT_SETTING_BLOCK:
label = l10n_util::GetNSString(
IDS_COLLECTED_COOKIES_MULTIPLE_BLOCK_RULES_CREATED);
break;
case CONTENT_SETTING_ALLOW:
label = l10n_util::GetNSString(
IDS_COLLECTED_COOKIES_MULTIPLE_ALLOW_RULES_CREATED);
break;
case CONTENT_SETTING_SESSION_ONLY:
label = l10n_util::GetNSString(
IDS_COLLECTED_COOKIES_MULTIPLE_SESSION_RULES_CREATED);
break;
default:
NOTREACHED();
label = [[[NSString alloc] init] autorelease];
}
[infoBarText_ setStringValue:label];
[self animateInfoBar];
}
+ (NSArray*)normalizeNodeSelection:(NSArray*)selection {
NSMutableArray* normalized = [NSMutableArray arrayWithArray:selection];
for (NSTreeNode* node in selection) {
NSTreeNode* parent = node;
while ((parent = [parent parentNode])) {
if ([normalized containsObject:parent]) {
[normalized removeObject:node];
break;
}
}
}
return normalized;
}
- (void)showInfoBarForDomain:(const base::string16&)domain
setting:(ContentSetting)setting {
NSString* label;
switch (setting) {
case CONTENT_SETTING_BLOCK:
label = l10n_util::GetNSStringF(
IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
domain);
break;
case CONTENT_SETTING_ALLOW:
label = l10n_util::GetNSStringF(
IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
domain);
break;
case CONTENT_SETTING_SESSION_ONLY:
label = l10n_util::GetNSStringF(
IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
domain);
break;
default:
NOTREACHED();
label = [[[NSString alloc] init] autorelease];
}
[infoBarText_ setStringValue:label];
[self animateInfoBar];
}
- (void)animateInfoBar {
if (infoBarVisible_)
return;
infoBarVisible_ = YES;
NSWindow* sheet = [self window];
NSRect sheetFrame = [sheet frame];
NSRect infoBarFrame = [infoBar_ frame];
NSRect tabViewFrame = [tabView_ frame];
// Calculate the end position of the info bar and set it to its start
// position.
infoBarFrame.origin.y = NSHeight(sheetFrame);
infoBarFrame.size.width = NSWidth(sheetFrame);
[infoBar_ setFrame:infoBarFrame];
[[[self window] contentView] addSubview:infoBar_];
// Calculate the new position of the sheet.
sheetFrame.origin.y -= NSHeight(infoBarFrame);
sheetFrame.size.height += NSHeight(infoBarFrame);
NSArray* animations = @[
// Slide the infobar in.
@{
NSViewAnimationTargetKey : infoBar_,
NSViewAnimationEndFrameKey : [NSValue valueWithRect:infoBarFrame]
},
// Make sure the tab view ends up in the right position.
@{
NSViewAnimationTargetKey : tabView_,
NSViewAnimationEndFrameKey : [NSValue valueWithRect:tabViewFrame]
},
// Grow the sheet.
@{
NSViewAnimationTargetKey : sheet,
NSViewAnimationEndFrameKey : [NSValue valueWithRect:sheetFrame]
}
];
[animation_ setViewAnimations:animations];
// The default duration is 0.5s, which actually feels slow in here, so speed
// it up a bit.
[animation_ gtm_setDuration:0.2 eventMask:NSLeftMouseUpMask];
[animation_ startAnimation];
}
- (void)tabView:(NSTabView*)tabView
didSelectTabViewItem:(NSTabViewItem*)tabViewItem {
NSTreeController* treeController = nil;
switch ([tabView indexOfTabViewItem:tabViewItem]) {
case kAllowedCookiesTabIndex:
treeController = allowedTreeController_;
break;
case kBlockedCookiesTabIndex:
treeController = blockedTreeController_;
break;
default:
NOTREACHED();
return;
}
[detailsViewController_ configureBindingsForTreeController:treeController];
}
@end