summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/content_blocked_bubble_controller.mm
diff options
context:
space:
mode:
authorthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-21 16:59:19 +0000
committerthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-21 16:59:19 +0000
commit6f2c41a3bc45fc8ad62720ab45b9ea085eea74f4 (patch)
treeaef3350de06e1daa2ef4a6271d0fbc29f2d8a23f /chrome/browser/cocoa/content_blocked_bubble_controller.mm
parent024afb4486f3d91faa87443eaad3ebde584e9613 (diff)
downloadchromium_src-6f2c41a3bc45fc8ad62720ab45b9ea085eea74f4.zip
chromium_src-6f2c41a3bc45fc8ad62720ab45b9ea085eea74f4.tar.gz
chromium_src-6f2c41a3bc45fc8ad62720ab45b9ea085eea74f4.tar.bz2
Mac: Show content blocked bubbles.
Two screenshots at http://imgur.com/5NDoC&pJJwP . xib changes: All 5 xibs contain a window of type InfoBubbleWindow with a custom InfoBubbleView. The view is connected to the controller's |bubble_| outlet. The controller is the window's |delegate|, and the window is the controller's |window|. The window is not visible at launch. It autorecalculates its key view loop. For the non-cookie xibs, the two buttons at the bottom are in GTMWidthBasedTweakers. Issues: * Clicking "blocked" icon with open bubble doesn't close the bubble BUG=35594,34894 TEST=Go to popuptest.com, start test. Click "Popups blocked" icon in omnibox. Bubble should appear. It should look sane in multiple locales (I tried English and German). The left button and the radio buttons are disabled at the moment, but the close button and the popup links should be functional. Review URL: http://codereview.chromium.org/650073 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@39581 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa/content_blocked_bubble_controller.mm')
-rw-r--r--chrome/browser/cocoa/content_blocked_bubble_controller.mm388
1 files changed, 388 insertions, 0 deletions
diff --git a/chrome/browser/cocoa/content_blocked_bubble_controller.mm b/chrome/browser/cocoa/content_blocked_bubble_controller.mm
new file mode 100644
index 0000000..35d885a
--- /dev/null
+++ b/chrome/browser/cocoa/content_blocked_bubble_controller.mm
@@ -0,0 +1,388 @@
+// Copyright (c) 2010 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.
+
+#include "app/l10n_util.h"
+#include "base/logging.h"
+#include "base/mac_util.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/blocked_popup_container.h"
+#import "chrome/browser/cocoa/content_blocked_bubble_controller.h"
+#import "chrome/browser/cocoa/hyperlink_button_cell.h"
+#import "chrome/browser/cocoa/info_bubble_view.h"
+#include "chrome/browser/host_content_settings_map.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+#include "grit/generated_resources.h"
+#include "skia/ext/skia_utils_mac.h"
+#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
+
+namespace {
+
+// Must match the tag of the unblock radio button in the xib files.
+const int kAllowTag = 1;
+
+// Must match the tag of the block radio button in the xib files.
+const int kBlockTag = 2;
+
+// Height of one link in the popup list.
+const int kLinkHeight = 16;
+
+// Space between two popup links.
+const int kLinkPadding = 4;
+
+// Space taken in total by one popup link.
+const int kLinkLineHeight = kLinkHeight + kLinkPadding;
+
+// Space between popup list and surrounding UI elements.
+const int kLinkOuterPadding = 8;
+
+} // namespace
+
+// If the bubble's tab contents get destroyed, tells the bubble about it.
+class BubbleCloser : public NotificationObserver {
+ public:
+ BubbleCloser(ContentBlockedBubbleController* controller,
+ TabContents* tab_contents)
+ : controller_(controller) {
+ registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(tab_contents));
+ }
+ virtual ~BubbleCloser() {}
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
+ DCHECK(source == Source<TabContents>([controller_ tabContents]));
+ [controller_ setTabContents:NULL];
+ }
+ private:
+ ContentBlockedBubbleController* controller_; // weak, owns us
+ NotificationRegistrar registrar_;
+};
+
+
+// Like |ReplaceStringPlaceholders(const string16&, const string16&, size_t*)|,
+// but for a NSString formatString.
+static NSString* ReplaceNSStringPlaceholders(NSString* formatString,
+ const string16& a,
+ size_t* offset) {
+ return base::SysUTF16ToNSString(
+ ReplaceStringPlaceholders(base::SysNSStringToUTF16(formatString),
+ a,
+ offset));
+}
+
+
+@interface ContentBlockedBubbleController(Private)
+- (id)initWithType:(ContentSettingsType)settingsType
+ parentWindow:(NSWindow*)parentWindow
+ anchoredAt:(NSPoint)anchoredAt
+ host:(const std::string&)host
+ displayHost:(NSString*)displayHost
+ tabContents:(TabContents*)tabContents
+ profile:(Profile*)profile;
+- (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
+ title:(NSString*)title
+ icon:(NSImage*)icon
+ referenceFrame:(NSRect)referenceFrame;
+- (void)initializeTitle;
+- (void)initializeRadioGroup;
+- (void)initializePopupList;
+- (void)popupLinkClicked:(id)sender;
+@end
+
+
+@implementation ContentBlockedBubbleController
+
++ (ContentBlockedBubbleController*)showForType:(ContentSettingsType)settingsType
+ parentWindow:(NSWindow*)parentWindow
+ anchoredAt:(NSPoint)anchor
+ host:(const std::string&)host
+ displayHost:(NSString*)displayHost
+ tabContents:(TabContents*)tabContents
+ profile:(Profile*)profile {
+ // Autoreleases itself on bubble close.
+ return [[ContentBlockedBubbleController alloc] initWithType:settingsType
+ parentWindow:parentWindow
+ anchoredAt:anchor
+ host:host
+ displayHost:displayHost
+ tabContents:tabContents
+ profile:profile];
+}
+
+@synthesize tabContents = tabContents_;
+
+- (id)initWithType:(ContentSettingsType)settingsType
+ parentWindow:(NSWindow*)parentWindow
+ anchoredAt:(NSPoint)anchoredAt
+ host:(const std::string&)host
+ displayHost:(NSString*)displayHost
+ tabContents:(TabContents*)tabContents
+ profile:(Profile*)profile {
+ NSString* const nibPaths[CONTENT_SETTINGS_NUM_TYPES] = {
+ @"ContentBlockedCookies",
+ @"ContentBlockedImages",
+ @"ContentBlockedJavaScript",
+ @"ContentBlockedPlugins",
+ @"ContentBlockedPopups",
+ };
+ DCHECK_EQ(arraysize(nibPaths),
+ static_cast<size_t>(CONTENT_SETTINGS_NUM_TYPES));
+ NSString* nibPath =
+ [mac_util::MainAppBundle() pathForResource:nibPaths[settingsType]
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ parentWindow_ = parentWindow;
+ anchor_ = anchoredAt;
+
+ settingsType_ = settingsType;
+ host_ = host;
+ displayHost_.reset([displayHost retain]);
+ tabContents_ = tabContents;
+ profile_ = profile;
+ closer_.reset(new BubbleCloser(self, tabContents_));
+
+ // Watch to see if the parent window closes, and if so, close this one.
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(parentWindowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:parentWindow_];
+ }
+ return self;
+}
+
+- (void)initializeTitle {
+ // Layout title post-localization.
+ CGFloat titleDeltaY = [GTMUILocalizerAndLayoutTweaker
+ sizeToFitFixedWidthTextField:titleLabel_];
+ NSRect windowFrame = [[self window] frame];
+ windowFrame.size.height += titleDeltaY;
+ [[self window] setFrame:windowFrame display:NO];
+ NSRect titleFrame = [titleLabel_ frame];
+ titleFrame.origin.y -= titleDeltaY;
+ [titleLabel_ setFrame:titleFrame];
+}
+
+- (void)initializeRadioGroup {
+ // Select appropriate radio button..
+ if (profile_) { // NULL in tests.
+ HostContentSettingsMap* contentSettingsMap =
+ profile_->GetHostContentSettingsMap();
+ ContentSetting setting = contentSettingsMap->GetContentSetting(
+ host_, settingsType_);
+ [allowBlockRadioGroup_ selectCellWithTag:
+ setting == CONTENT_SETTING_ALLOW ? kAllowTag : kBlockTag];
+ }
+
+ // Copy |host_| into radio group label.
+ NSCell* radioCell = [allowBlockRadioGroup_ cellWithTag:kAllowTag];
+ [radioCell setTitle:ReplaceNSStringPlaceholders([radioCell title],
+ UTF8ToUTF16(host_),
+ NULL)];
+
+ // Layout radio group labels post-localization.
+ [GTMUILocalizerAndLayoutTweaker
+ wrapRadioGroupForWidth:allowBlockRadioGroup_];
+ CGFloat radioDeltaY = [GTMUILocalizerAndLayoutTweaker
+ sizeToFitView:allowBlockRadioGroup_].height;
+ NSRect windowFrame = [[self window] frame];
+ windowFrame.size.height += radioDeltaY;
+ [[self window] setFrame:windowFrame display:NO];
+}
+
+- (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
+ title:(NSString*)title
+ icon:(NSImage*)icon
+ referenceFrame:(NSRect)referenceFrame {
+ scoped_nsobject<HyperlinkButtonCell> cell([[HyperlinkButtonCell alloc]
+ initTextCell:title]);
+ [cell.get() setAlignment:NSNaturalTextAlignment];
+ if (icon) {
+ [cell.get() setImagePosition:NSImageLeft];
+ [cell.get() setImage:icon];
+ } else {
+ [cell.get() setImagePosition:NSNoImage];
+ }
+ [cell.get() setControlSize:NSSmallControlSize];
+
+ NSButton* button = [[[NSButton alloc] initWithFrame:frame] autorelease];
+ // Cell must be set immediately after construction.
+ [button setCell:cell.get()];
+
+ // If the link text is too long, clamp it.
+ [button sizeToFit];
+ int maxWidth = NSWidth([bubble_ frame]) - 2 * NSMinX(referenceFrame);
+ NSRect buttonFrame = [button frame];
+ if (NSWidth(buttonFrame) > maxWidth) {
+ buttonFrame.size.width = maxWidth;
+ [button setFrame:buttonFrame];
+ }
+
+ [button setTarget:self];
+ [button setAction:@selector(popupLinkClicked:)];
+ return button;
+}
+
+- (void)initializePopupList {
+ // I didn't put the buttons into a NSMatrix because then they are only one
+ // entity in the key view loop. This way, one can tab through all of them.
+ BlockedPopupContainer::BlockedContents blockedContents;
+ DCHECK(tabContents_->blocked_popup_container());
+ tabContents_->blocked_popup_container()->GetBlockedContents(
+ &blockedContents);
+
+ // Get the pre-resize frame of the radio group. Its origin is where the
+ // popup list should go.
+ NSRect radioFrame = [allowBlockRadioGroup_ frame];
+
+ // Make room for the popup list. The bubble view and its subviews autosize
+ // themselves when the window is enlarged.
+ // Heading and radio box are already 1 * kLinkOuterPadding apart in the nib,
+ // so only 1 * kLinkOuterPadding more is needed.
+ int delta = blockedContents.size() * kLinkLineHeight - kLinkPadding +
+ kLinkOuterPadding;
+ NSRect windowFrame = [[self window] frame];
+ windowFrame.size.height += delta;
+ [[self window] setFrame:windowFrame display:NO];
+
+ // Create popup list.
+ int topLinkY = NSMaxY(radioFrame) + delta - kLinkHeight;
+ int row = 0;
+ for (BlockedPopupContainer::BlockedContents::const_iterator
+ it(blockedContents.begin()); it != blockedContents.end();
+ ++it, ++row) {
+ SkBitmap icon = (*it)->GetFavIcon();
+ NSImage* image = nil;
+ if (!icon.empty())
+ image = gfx::SkBitmapToNSImage(icon);
+
+ string16 title((*it)->GetTitle());
+ // The popup may not have committed a load yet, in which case it won't
+ // have a URL or title.
+ if (title.empty())
+ title = l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE);
+
+ NSRect linkFrame =
+ NSMakeRect(NSMinX(radioFrame), topLinkY - kLinkLineHeight * row,
+ 200, kLinkHeight);
+ NSButton* button = [self
+ hyperlinkButtonWithFrame:linkFrame
+ title:base::SysUTF16ToNSString(title)
+ icon:image
+ referenceFrame:radioFrame];
+ [bubble_ addSubview:button];
+ popupLinks_[button] = *it;
+ }
+}
+
+- (void)awakeFromNib {
+ [bubble_ setBubbleType:kWhiteInfoBubble];
+ [bubble_ setArrowLocation:kTopRight];
+
+ [self initializeTitle];
+ if (allowBlockRadioGroup_) // not bound in cookie bubble xib
+ [self initializeRadioGroup];
+ if (settingsType_ == CONTENT_SETTINGS_TYPE_POPUPS && tabContents_)
+ [self initializePopupList];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Bubble-management related stuff
+
+// TODO(thakis): All that junk below should be in some superclass that all the
+// bubble controllers (bookmark bubble, extension installed bubble, page/browser
+// action bubble, content blocked bubble) derive from -- http://crbug.com/36366
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)parentWindowWillClose:(NSNotification*)notification {
+ [self close];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ // We caught a close so we don't need to watch for the parent closing.
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self autorelease];
+}
+
+// We want this to be a child of a browser window. addChildWindow:
+// (called from this function) will bring the window on-screen;
+// unfortunately, [NSWindowController showWindow:] will also bring it
+// on-screen (but will cause unexpected changes to the window's
+// position). We cannot have an addChildWindow: and a subsequent
+// showWindow:. Thus, we have our own version.
+- (void)showWindow:(id)sender {
+ NSWindow* window = [self window]; // completes nib load
+
+ NSPoint origin = [parentWindow_ convertBaseToScreen:anchor_];
+ origin.x -= NSWidth([window frame]) - kBubbleArrowXOffset -
+ kBubbleArrowWidth / 2;
+ origin.y -= NSHeight([window frame]);
+ [window setFrameOrigin:origin];
+ [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
+ [window makeKeyAndOrderFront:self];
+}
+
+- (void)close {
+ [parentWindow_ removeChildWindow:[self window]];
+ [super close];
+}
+
+// The controller is the delegate of the window so it receives did resign key
+// notifications. When key is resigned mirror Windows behavior and close the
+// window.
+- (void)windowDidResignKey:(NSNotification*)notification {
+ NSWindow* window = [self window];
+ DCHECK_EQ([notification object], window);
+ if ([window isVisible]) {
+ // If the window isn't visible, it is already closed, and this notification
+ // has been sent as part of the closing operation, so no need to close.
+ [self close];
+ }
+}
+
+// By implementing this, ESC causes the window to go away.
+- (IBAction)cancel:(id)sender {
+ // This is not a "real" cancel as potential changes to the radio group are not
+ // undone. That's ok.
+ [self close];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Actual application logic
+
+- (IBAction)allowBlockToggled:(id)sender {
+ NSButtonCell *selectedCell = [sender selectedCell];
+ ContentSetting setting = [selectedCell tag] == kAllowTag ?
+ CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
+ profile_->GetHostContentSettingsMap()->SetContentSetting(host_,
+ settingsType_,
+ setting);
+}
+
+- (IBAction)closeBubble:(id)sender {
+ [self close];
+}
+
+- (IBAction)manageBlocking:(id)sender {
+ // TODO(thakis): Implement, http://crbug.com/34894
+ NOTIMPLEMENTED();
+}
+
+- (void)popupLinkClicked:(id)sender {
+ content_blocked_bubble::PopupLinks::iterator i(popupLinks_.find(sender));
+ DCHECK(i != popupLinks_.end());
+ if (tabContents_ && tabContents_->blocked_popup_container())
+ tabContents_->blocked_popup_container()->LaunchPopupForContents(i->second);
+}
+
+@end // ContentBlockedBubbleController