Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / website_settings / permission_bubble_controller.mm
blob9b57585f54115f7b6386b460fd17ad14f4c5e206
1 // Copyright 2014 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/website_settings/permission_bubble_controller.h"
7 #include <algorithm>
9 #include "base/mac/bind_objc_block.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_finder.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #import "chrome/browser/ui/chrome_style.h"
16 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
17 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
19 #import "chrome/browser/ui/cocoa/hover_close_button.h"
20 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
21 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
22 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
23 #include "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
24 #include "chrome/browser/ui/cocoa/website_settings/permission_selector_button.h"
25 #include "chrome/browser/ui/cocoa/website_settings/split_block_button.h"
26 #include "chrome/browser/ui/cocoa/website_settings/website_settings_utils_cocoa.h"
27 #include "chrome/browser/ui/website_settings/permission_bubble_request.h"
28 #include "chrome/browser/ui/website_settings/permission_bubble_view.h"
29 #include "chrome/browser/ui/website_settings/permission_menu_model.h"
30 #include "chrome/grit/generated_resources.h"
31 #include "content/public/browser/native_web_keyboard_event.h"
32 #include "content/public/browser/user_metrics.h"
33 #include "skia/ext/skia_utils_mac.h"
34 #import "ui/base/cocoa/controls/hyperlink_text_view.h"
35 #import "ui/base/cocoa/menu_controller.h"
36 #include "ui/base/cocoa/window_size_constants.h"
37 #include "ui/base/l10n/l10n_util_mac.h"
38 #include "ui/base/models/simple_menu_model.h"
40 using base::UserMetricsAction;
42 namespace {
44 // Distance between permission icon and permission label.
45 const CGFloat kHorizontalIconPadding = 8.0f;
47 // Distance between two permission labels.
48 const CGFloat kVerticalPermissionPadding = 2.0f;
50 const CGFloat kHorizontalPadding = 13.0f;
51 const CGFloat kVerticalPadding = 15.0f;
52 const CGFloat kBetweenButtonsPadding = 10.0f;
53 const CGFloat kButtonRightEdgePadding = 17.0f;
54 const CGFloat kTitlePaddingX = 50.0f;
55 const CGFloat kBubbleMinWidth = 315.0f;
56 const NSSize kPermissionIconSize = {18, 18};
58 class MenuDelegate : public ui::SimpleMenuModel::Delegate {
59  public:
60   explicit MenuDelegate(PermissionBubbleController* bubble)
61       : bubble_controller_(bubble) {}
62   bool IsCommandIdChecked(int command_id) const override { return false; }
63   bool IsCommandIdEnabled(int command_id) const override { return true; }
64   bool GetAcceleratorForCommandId(int command_id,
65                                   ui::Accelerator* accelerator) override {
66     return false;
67   }
68  private:
69   PermissionBubbleController* bubble_controller_;  // Weak, owns us.
70   DISALLOW_COPY_AND_ASSIGN(MenuDelegate);
73 }  // namespace
75 // NSPopUpButton with a menu containing two items: allow and block.
76 // One AllowBlockMenuButton is used for each requested permission when there are
77 // multiple permissions in the bubble.
78 @interface AllowBlockMenuButton : NSPopUpButton {
79  @private
80   scoped_ptr<PermissionMenuModel> menuModel_;
81   base::scoped_nsobject<MenuController> menuController_;
84 - (id)initForURL:(const GURL&)url
85          allowed:(BOOL)allow
86            index:(int)index
87         delegate:(PermissionBubbleView::Delegate*)delegate;
89 // Returns the maximum width of its possible titles.
90 - (CGFloat)maximumTitleWidth;
91 @end
93 @implementation AllowBlockMenuButton
95 - (id)initForURL:(const GURL&)url
96          allowed:(BOOL)allow
97            index:(int)index
98         delegate:(PermissionBubbleView::Delegate*)delegate {
99   if (self = [super initWithFrame:NSZeroRect pullsDown:NO]) {
100     ContentSetting setting =
101         allow ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
102     [self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
103     [self setBordered:NO];
105     __block PermissionBubbleView::Delegate* blockDelegate = delegate;
106     __block AllowBlockMenuButton* blockSelf = self;
107     PermissionMenuModel::ChangeCallback changeCallback =
108         base::BindBlock(^(const WebsiteSettingsUI::PermissionInfo& permission) {
109             blockDelegate->ToggleAccept(
110                 index, permission.setting == CONTENT_SETTING_ALLOW);
111             [blockSelf setFrameSize:
112                 SizeForWebsiteSettingsButtonTitle(blockSelf,
113                                                   [blockSelf title])];
114         });
116     menuModel_.reset(new PermissionMenuModel(url, setting, changeCallback));
117     menuController_.reset([[MenuController alloc] initWithModel:menuModel_.get()
118                                          useWithPopUpButtonCell:NO]);
119     [self setMenu:[menuController_ menu]];
120     [self selectItemAtIndex:menuModel_->GetIndexOfCommandId(setting)];
121     // Although the frame is reset, below, this sizes the cell properly.
122     [self sizeToFit];
123     // Adjust the size to fit the current title.  Using only -sizeToFit leaves
124     // an ugly amount of whitespace between the title and the arrows because it
125     // will fit to the largest element in the menu, not just the selected item.
126     [self setFrameSize:SizeForWebsiteSettingsButtonTitle(self, [self title])];
127   }
128   return self;
131 - (CGFloat)maximumTitleWidth {
132   CGFloat maxTitleWidth = 0;
133   for (NSMenuItem* item in [self itemArray]) {
134     NSSize size = SizeForWebsiteSettingsButtonTitle(self, [item title]);
135     maxTitleWidth = std::max(maxTitleWidth, size.width);
136   }
137   return maxTitleWidth;
140 @end
142 // The window used for the permission bubble controller.
143 // Subclassed to allow browser-handled keyboard events to be passed from the
144 // permission bubble to its parent window, which is a browser window.
145 @interface PermissionBubbleWindow : InfoBubbleWindow
146 @end
148 @implementation PermissionBubbleWindow
149 - (BOOL)performKeyEquivalent:(NSEvent*)event {
150   // Before forwarding to parent, handle locally.
151   if ([super performKeyEquivalent:event])
152     return YES;
154   // Only handle events if they should be forwarded to the parent window.
155   if ([self allowShareParentKeyState]) {
156     content::NativeWebKeyboardEvent wrappedEvent(event);
157     if ([BrowserWindowUtils shouldHandleKeyboardEvent:wrappedEvent]) {
158       // Turn off sharing of key window state while the keyboard event is
159       // processed.  This avoids recursion - with the key window state shared,
160       // the parent window would just forward the event back to this class.
161       [self setAllowShareParentKeyState:NO];
162       BOOL eventHandled =
163           [BrowserWindowUtils handleKeyboardEvent:event
164                                          inWindow:[self parentWindow]];
165       [self setAllowShareParentKeyState:YES];
166       return eventHandled;
167     }
168   }
169   return NO;
171 @end
173 @interface PermissionBubbleController ()
175 // Determines if the bubble has an anchor in a corner or no anchor at all.
176 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation;
178 // Returns the expected parent for this bubble.
179 - (NSWindow*)getExpectedParentWindow;
181 // Returns an autoreleased NSView displaying the icon and label for |request|.
182 - (NSView*)labelForRequest:(PermissionBubbleRequest*)request;
184 // Returns an autoreleased NSView displaying the title for the bubble
185 // requesting settings for |host|.
186 - (NSView*)titleWithHostname:(const std::string&)host;
188 // Returns an autoreleased NSView displaying a menu for |request|.  The
189 // menu will be initialized as 'allow' if |allow| is YES.
190 - (NSView*)menuForRequest:(PermissionBubbleRequest*)request
191                   atIndex:(int)index
192                     allow:(BOOL)allow;
194 // Returns an autoreleased NSView of a button with |title| and |action|.
195 - (NSView*)buttonWithTitle:(NSString*)title
196                     action:(SEL)action;
198 // Returns an autoreleased NSView displaying a block button.
199 - (NSView*)blockButton;
201 // Returns an autoreleased NSView displaying the close 'x' button.
202 - (NSView*)closeButton;
204 // Called when the 'ok' button is pressed.
205 - (void)ok:(id)sender;
207 // Called when the 'allow' button is pressed.
208 - (void)onAllow:(id)sender;
210 // Called when the 'block' button is pressed.
211 - (void)onBlock:(id)sender;
213 // Called when the 'close' button is pressed.
214 - (void)onClose:(id)sender;
216 // Sets the width of both |viewA| and |viewB| to be the larger of the
217 // two views' widths.  Does not change either view's origin or height.
218 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB;
220 // Sets the offset of |viewA| so that its vertical center is aligned with the
221 // vertical center of |viewB|.
222 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB;
224 @end
226 @implementation PermissionBubbleController
228 - (id)initWithBrowser:(Browser*)browser bridge:(PermissionBubbleCocoa*)bridge {
229   DCHECK(browser);
230   DCHECK(bridge);
231   browser_ = browser;
232   base::scoped_nsobject<PermissionBubbleWindow> window(
233       [[PermissionBubbleWindow alloc]
234           initWithContentRect:ui::kWindowSizeDeterminedLater
235                     styleMask:NSBorderlessWindowMask
236                       backing:NSBackingStoreBuffered
237                         defer:NO]);
238   [window setAllowedAnimations:info_bubble::kAnimateNone];
239   [window setReleasedWhenClosed:NO];
240   if ((self = [super initWithWindow:window
241                        parentWindow:[self getExpectedParentWindow]
242                          anchoredAt:NSZeroPoint])) {
243     [self setShouldCloseOnResignKey:NO];
244     [self setShouldOpenAsKeyWindow:YES];
245     [[self bubble] setArrowLocation:[self getExpectedArrowLocation]];
246     bridge_ = bridge;
247     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
248     [center addObserver:self
249                selector:@selector(parentWindowDidMove:)
250                    name:NSWindowDidMoveNotification
251                  object:[self getExpectedParentWindow]];
252   }
253   return self;
256 - (void)windowWillClose:(NSNotification*)notification {
257   bridge_->OnBubbleClosing();
258   [super windowWillClose:notification];
261 - (void)parentWindowWillToggleFullScreen:(NSNotification*)notification {
262   // Override the base class implementation, which would have closed the bubble.
265 - (void)parentWindowDidResize:(NSNotification*)notification {
266   DCHECK(bridge_);
267   [self setAnchorPoint:[self getExpectedAnchorPoint]];
270 - (void)parentWindowDidMove:(NSNotification*)notification {
271   DCHECK(bridge_);
272   [self setAnchorPoint:[self getExpectedAnchorPoint]];
275 - (void)showWithDelegate:(PermissionBubbleView::Delegate*)delegate
276              forRequests:(const std::vector<PermissionBubbleRequest*>&)requests
277             acceptStates:(const std::vector<bool>&)acceptStates {
278   DCHECK(!requests.empty());
279   DCHECK(delegate);
280   delegate_ = delegate;
282   NSView* contentView = [[self window] contentView];
283   [contentView setSubviews:@[]];
285   BOOL singlePermission = requests.size() == 1;
287   // Create one button to use as a guide for the permissions' y-offsets.
288   base::scoped_nsobject<NSView> allowOrOkButton;
289   if (singlePermission) {
290     NSString* allowTitle = l10n_util::GetNSString(IDS_PERMISSION_ALLOW);
291     allowOrOkButton.reset([[self buttonWithTitle:allowTitle
292                                           action:@selector(onAllow:)] retain]);
293   } else {
294     NSString* okTitle = l10n_util::GetNSString(IDS_OK);
295     allowOrOkButton.reset([[self buttonWithTitle:okTitle
296                                           action:@selector(ok:)] retain]);
297   }
298   CGFloat yOffset = 2 * kVerticalPadding + NSMaxY([allowOrOkButton frame]);
300   base::scoped_nsobject<NSMutableArray> permissionMenus;
301   if (!singlePermission)
302     permissionMenus.reset([[NSMutableArray alloc] init]);
304   CGFloat maxPermissionLineWidth = 0;
305   CGFloat verticalPadding = 0.0f;
306   for (auto it = requests.begin(); it != requests.end(); it++) {
307     base::scoped_nsobject<NSView> permissionView(
308         [[self labelForRequest:(*it)] retain]);
309     NSPoint origin = [permissionView frame].origin;
310     origin.x += kHorizontalPadding;
311     origin.y += yOffset + verticalPadding;
312     [permissionView setFrameOrigin:origin];
313     [contentView addSubview:permissionView];
315     if (!singlePermission) {
316       int index = it - requests.begin();
317       base::scoped_nsobject<NSView> menu(
318           [[self menuForRequest:(*it)
319                         atIndex:index
320                           allow:acceptStates[index] ? YES : NO] retain]);
321       // Align vertically.  Horizontal alignment will be adjusted once the
322       // widest permission is know.
323       [PermissionBubbleController alignCenterOf:menu
324                            verticallyToCenterOf:permissionView];
325       [permissionMenus addObject:menu];
326       [contentView addSubview:menu];
327     }
328     maxPermissionLineWidth = std::max(
329         maxPermissionLineWidth, NSMaxX([permissionView frame]));
330     yOffset += NSHeight([permissionView frame]);
332     // Add extra padding for all but first permission.
333     verticalPadding = kVerticalPermissionPadding;
334   }
336   base::scoped_nsobject<NSView> titleView(
337       [[self titleWithHostname:requests[0]->GetRequestingHostname().host()]
338           retain]);
339   [contentView addSubview:titleView];
340   [titleView setFrameOrigin:NSMakePoint(kHorizontalPadding,
341                                         kVerticalPadding + yOffset)];
343   // 'x' button in the upper-right-hand corner.
344   base::scoped_nsobject<NSView> closeButton([[self closeButton] retain]);
346   // Determine the dimensions of the bubble.
347   // Once the height and width are set, the buttons and permission menus can
348   // be laid out correctly.
349   NSRect bubbleFrame = NSMakeRect(0, 0, kBubbleMinWidth, 0);
351   // Fix the height of the bubble relative to the title.
352   bubbleFrame.size.height = NSMaxY([titleView frame]) + kVerticalPadding +
353                             info_bubble::kBubbleArrowHeight;
355   if (!singlePermission) {
356     // Add the maximum menu width to the bubble width.
357     CGFloat maxMenuWidth = 0;
358     for (AllowBlockMenuButton* button in permissionMenus.get()) {
359       maxMenuWidth = std::max(maxMenuWidth, [button maximumTitleWidth]);
360     }
361     maxPermissionLineWidth += maxMenuWidth;
362   }
364   // The title and 'x' button row must fit within the bubble.
365   CGFloat titleRowWidth = NSMaxX([titleView frame]) +
366                           NSWidth([closeButton frame]) +
367                           chrome_style::kCloseButtonPadding;
369   bubbleFrame.size.width = std::max(
370       NSWidth(bubbleFrame), std::max(titleRowWidth, maxPermissionLineWidth));
372   // Now that the bubble's dimensions have been set, lay out the buttons and
373   // menus.
375   // Place the close button at the upper-right-hand corner of the bubble.
376   NSPoint closeButtonOrigin =
377       NSMakePoint(NSWidth(bubbleFrame) - NSWidth([closeButton frame]) -
378                       chrome_style::kCloseButtonPadding,
379                   NSHeight(bubbleFrame) - NSWidth([closeButton frame]) -
380                       chrome_style::kCloseButtonPadding);
381   // Account for the bubble's arrow.
382   closeButtonOrigin.y -= info_bubble::kBubbleArrowHeight;
384   [closeButton setFrameOrigin:closeButtonOrigin];
385   [contentView addSubview:closeButton];
387   // Position the allow/ok button.
388   CGFloat xOrigin = NSWidth(bubbleFrame) - NSWidth([allowOrOkButton frame]) -
389                     kButtonRightEdgePadding;
390   [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)];
391   [contentView addSubview:allowOrOkButton];
393   if (singlePermission) {
394     base::scoped_nsobject<NSView> blockButton;
395     blockButton.reset([[self blockButton] retain]);
396     CGFloat width = [PermissionBubbleController matchWidthsOf:blockButton
397                                                         andOf:allowOrOkButton];
398     // Ensure the allow/ok button is still in the correct position.
399     xOrigin = NSWidth(bubbleFrame) - width - kHorizontalPadding;
400     [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)];
401     // Line up the block button.
402     xOrigin = NSMinX([allowOrOkButton frame]) - width - kBetweenButtonsPadding;
403     [blockButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)];
404     [contentView addSubview:blockButton];
405   } else {
406     // Adjust the horizontal origin for each menu so that its right edge
407     // lines up with the right edge of the ok button.
408     CGFloat rightEdge = NSMaxX([allowOrOkButton frame]);
409     for (NSView* view in permissionMenus.get()) {
410       [view setFrameOrigin:NSMakePoint(rightEdge - NSWidth([view frame]),
411                                        NSMinY([view frame]))];
412     }
413   }
415   bubbleFrame = [[self window] frameRectForContentRect:bubbleFrame];
416   if ([[self window] isVisible]) {
417     // Unfortunately, calling -setFrame followed by -setFrameOrigin  (called
418     // within -setAnchorPoint) causes flickering.  Avoid the flickering by
419     // manually adjusting the new frame's origin so that the top left stays the
420     // same, and only calling -setFrame.
421     NSRect currentWindowFrame = [[self window] frame];
422     bubbleFrame.origin = currentWindowFrame.origin;
423     bubbleFrame.origin.y = bubbleFrame.origin.y +
424         currentWindowFrame.size.height - bubbleFrame.size.height;
425     [[self window] setFrame:bubbleFrame display:YES];
426   } else {
427     [[self window] setFrame:bubbleFrame display:NO];
428     [self setAnchorPoint:[self getExpectedAnchorPoint]];
429     [self showWindow:nil];
430     [[self window] makeFirstResponder:nil];
431     [[self window] setInitialFirstResponder:allowOrOkButton.get()];
432   }
435 - (void)updateAnchorPosition {
436   [self setParentWindow:[self getExpectedParentWindow]];
437   [self setAnchorPoint:[self getExpectedAnchorPoint]];
440 - (NSPoint)getExpectedAnchorPoint {
441   NSPoint anchor;
442   if ([self hasLocationBar]) {
443     LocationBarViewMac* location_bar =
444         [[[self getExpectedParentWindow] windowController] locationBarBridge];
445     anchor = location_bar->GetPageInfoBubblePoint();
446   } else {
447     // Center the bubble if there's no location bar.
448     NSRect contentFrame = [[[self getExpectedParentWindow] contentView] frame];
449     anchor = NSMakePoint(NSMidX(contentFrame), NSMaxY(contentFrame));
450   }
452   return [[self getExpectedParentWindow] convertBaseToScreen:anchor];
455 - (bool)hasLocationBar {
456   return browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
459 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation {
460   return [self hasLocationBar] ? info_bubble::kTopLeft : info_bubble::kNoArrow;
463 - (NSWindow*)getExpectedParentWindow {
464   DCHECK(browser_->window());
465   return browser_->window()->GetNativeWindow();
468 - (NSView*)labelForRequest:(PermissionBubbleRequest*)request {
469   DCHECK(request);
470   base::scoped_nsobject<NSView> permissionView(
471       [[NSView alloc] initWithFrame:NSZeroRect]);
472   base::scoped_nsobject<NSImageView> permissionIcon(
473       [[NSImageView alloc] initWithFrame:NSZeroRect]);
474   [permissionIcon setImage:ui::ResourceBundle::GetSharedInstance().
475       GetNativeImageNamed(request->GetIconId()).ToNSImage()];
476   [permissionIcon setFrameSize:kPermissionIconSize];
477   [permissionView addSubview:permissionIcon];
479   base::scoped_nsobject<NSTextField> permissionLabel(
480       [[NSTextField alloc] initWithFrame:NSZeroRect]);
481   base::string16 label = request->GetMessageTextFragment();
482   [permissionLabel setDrawsBackground:NO];
483   [permissionLabel setBezeled:NO];
484   [permissionLabel setEditable:NO];
485   [permissionLabel setSelectable:NO];
486   [permissionLabel
487       setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
488   [permissionLabel setStringValue:base::SysUTF16ToNSString(label)];
489   [permissionLabel sizeToFit];
490   [permissionLabel setFrameOrigin:
491       NSMakePoint(NSWidth([permissionIcon frame]) + kHorizontalIconPadding, 0)];
492   [permissionView addSubview:permissionLabel];
494   // Match the horizontal centers of the two subviews.  Note that the label's
495   // center is rounded down, and the icon's center, up.  It looks better that
496   // way - with the text's center slightly lower than the icon's center - if the
497   // height delta is not evenly split.
498   NSRect iconFrame = [permissionIcon frame];
499   NSRect labelFrame = [permissionLabel frame];
500   NSRect unionFrame = NSUnionRect(iconFrame, labelFrame);
502   iconFrame.origin.y =
503       std::ceil((NSHeight(unionFrame) - NSHeight(iconFrame)) / 2);
504   labelFrame.origin.y =
505       std::floor((NSHeight(unionFrame) - NSHeight(labelFrame)) / 2);
507   [permissionLabel setFrame:labelFrame];
508   [permissionIcon setFrame:iconFrame];
509   [permissionView setFrame:unionFrame];
511   return permissionView.autorelease();
514 - (NSView*)titleWithHostname:(const std::string&)host {
515   base::scoped_nsobject<NSTextField> titleView(
516       [[NSTextField alloc] initWithFrame:NSZeroRect]);
517   [titleView setDrawsBackground:NO];
518   [titleView setBezeled:NO];
519   [titleView setEditable:NO];
520   [titleView setSelectable:NO];
521   [titleView setStringValue:
522       l10n_util::GetNSStringF(IDS_PERMISSIONS_BUBBLE_PROMPT,
523                               base::UTF8ToUTF16(host))];
524   [titleView setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
525   [titleView sizeToFit];
526   NSRect titleFrame = [titleView frame];
527   [titleView setFrameSize:NSMakeSize(NSWidth(titleFrame) + kTitlePaddingX,
528                                      NSHeight(titleFrame))];
529   return titleView.autorelease();
532 - (NSView*)menuForRequest:(PermissionBubbleRequest*)request
533                   atIndex:(int)index
534                     allow:(BOOL)allow {
535   DCHECK(request);
536   DCHECK(delegate_);
537   base::scoped_nsobject<AllowBlockMenuButton> button(
538       [[AllowBlockMenuButton alloc] initForURL:request->GetRequestingHostname()
539                                        allowed:allow
540                                          index:index
541                                       delegate:delegate_]);
542   return button.autorelease();
545 - (NSView*)buttonWithTitle:(NSString*)title
546                     action:(SEL)action {
547   base::scoped_nsobject<NSButton> button(
548       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
549   [button setButtonType:NSMomentaryPushInButton];
550   [button setTitle:title];
551   [button setTarget:self];
552   [button setAction:action];
553   [button sizeToFit];
554   return button.autorelease();
557 - (NSView*)blockButton {
558   NSString* blockTitle = l10n_util::GetNSString(IDS_PERMISSION_DENY);
559   return [self buttonWithTitle:blockTitle
560                         action:@selector(onBlock:)];
563 - (NSView*)closeButton {
564   int dimension = chrome_style::GetCloseButtonSize();
565   NSRect frame = NSMakeRect(0, 0, dimension, dimension);
566   base::scoped_nsobject<NSButton> button(
567       [[WebUIHoverCloseButton alloc] initWithFrame:frame]);
568   [button setAction:@selector(onClose:)];
569   [button setTarget:self];
570   return button.autorelease();
573 - (void)ok:(id)sender {
574   DCHECK(delegate_);
575   delegate_->Accept();
578 - (void)onAllow:(id)sender {
579   DCHECK(delegate_);
580   delegate_->Accept();
583 - (void)onBlock:(id)sender {
584   DCHECK(delegate_);
585   delegate_->Deny();
588 - (void)onClose:(id)sender {
589   DCHECK(delegate_);
590   delegate_->Closing();
593 - (void)activateTabWithContents:(content::WebContents*)newContents
594                previousContents:(content::WebContents*)oldContents
595                         atIndex:(NSInteger)index
596                          reason:(int)reason {
597   // The show/hide of this bubble is handled by the PermissionBubbleManager.
598   // So bypass the base class, which would close the bubble here.
601 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB {
602   NSRect frameA = [viewA frame];
603   NSRect frameB = [viewB frame];
604   CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB));
605   [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))];
606   [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))];
607   return width;
610 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB {
611   NSRect frameA = [viewA frame];
612   NSRect frameB = [viewB frame];
613   frameA.origin.y =
614       NSMinY(frameB) + std::floor((NSHeight(frameB) - NSHeight(frameA)) / 2);
615   [viewA setFrameOrigin:frameA.origin];
618 @end  // implementation PermissionBubbleController