Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / website_settings / permission_bubble_controller.mm
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.
4
5 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_controller.h"
6
7 #include <algorithm>
8
9 #include "base/mac/bind_objc_block.h"
10 #include "base/mac/foundation_util.h"
11 #include "base/mac/mac_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_finder.h"
16 #import "chrome/browser/ui/chrome_style.h"
17 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
18 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
19 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
20 #import "chrome/browser/ui/cocoa/hover_close_button.h"
21 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
22 #import "chrome/browser/ui/cocoa/info_bubble_window.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"
39
40 using base::UserMetricsAction;
41
42 namespace {
43
44 const CGFloat kHorizontalPadding = 13.0f;
45 const CGFloat kVerticalPadding = 13.0f;
46 const CGFloat kBetweenButtonsPadding = 10.0f;
47 const CGFloat kButtonRightEdgePadding = 17.0f;
48 const CGFloat kTitlePaddingX = 50.0f;
49 const CGFloat kBubbleMinWidth = 315.0f;
50 const NSSize kPermissionIconSize = {18, 18};
51
52 class MenuDelegate : public ui::SimpleMenuModel::Delegate {
53  public:
54   explicit MenuDelegate(PermissionBubbleController* bubble)
55       : bubble_controller_(bubble) {}
56   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
57     return false;
58   }
59   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
60     return true;
61   }
62   virtual bool GetAcceleratorForCommandId(
63       int command_id,
64       ui::Accelerator* accelerator) OVERRIDE {
65     return false;
66   }
67   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
68     [bubble_controller_ onMenuItemClicked:command_id];
69   }
70  private:
71   PermissionBubbleController* bubble_controller_;  // Weak, owns us.
72   DISALLOW_COPY_AND_ASSIGN(MenuDelegate);
73 };
74
75 }  // namespace
76
77 // NSPopUpButton with a menu containing two items: allow and block.
78 // One AllowBlockMenuButton is used for each requested permission, but only when
79 // the permission bubble is in 'customize' mode.
80 @interface AllowBlockMenuButton : NSPopUpButton {
81  @private
82   scoped_ptr<PermissionMenuModel> menuModel_;
83   base::scoped_nsobject<MenuController> menuController_;
84 }
85
86 - (id)initForURL:(const GURL&)url
87          allowed:(BOOL)allow
88            index:(int)index
89         delegate:(PermissionBubbleView::Delegate*)delegate;
90
91 // Returns the maximum width of its possible titles.
92 - (CGFloat)maximumTitleWidth;
93 @end
94
95 @implementation AllowBlockMenuButton
96
97 - (id)initForURL:(const GURL&)url
98          allowed:(BOOL)allow
99            index:(int)index
100         delegate:(PermissionBubbleView::Delegate*)delegate {
101   if (self = [super initWithFrame:NSZeroRect pullsDown:NO]) {
102     ContentSetting setting =
103         allow ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
104     [self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
105     [self setBordered:NO];
106
107     __block PermissionBubbleView::Delegate* blockDelegate = delegate;
108     __block AllowBlockMenuButton* blockSelf = self;
109     PermissionMenuModel::ChangeCallback changeCallback =
110         base::BindBlock(^(const WebsiteSettingsUI::PermissionInfo& permission) {
111             blockDelegate->ToggleAccept(
112                 index, permission.setting == CONTENT_SETTING_ALLOW);
113             [blockSelf setFrameSize:
114                 SizeForWebsiteSettingsButtonTitle(blockSelf,
115                                                   [blockSelf title])];
116         });
117
118     menuModel_.reset(new PermissionMenuModel(url, setting, changeCallback));
119     menuController_.reset([[MenuController alloc] initWithModel:menuModel_.get()
120                                          useWithPopUpButtonCell:NO]);
121     [self setMenu:[menuController_ menu]];
122     [self selectItemAtIndex:menuModel_->GetIndexOfCommandId(setting)];
123     // Although the frame is reset, below, this sizes the cell properly.
124     [self sizeToFit];
125     // Adjust the size to fit the current title.  Using only -sizeToFit leaves
126     // an ugly amount of whitespace between the title and the arrows because it
127     // will fit to the largest element in the menu, not just the selected item.
128     [self setFrameSize:SizeForWebsiteSettingsButtonTitle(self, [self title])];
129   }
130   return self;
131 }
132
133 - (CGFloat)maximumTitleWidth {
134   CGFloat maxTitleWidth = 0;
135   for (NSMenuItem* item in [self itemArray]) {
136     NSSize size = SizeForWebsiteSettingsButtonTitle(self, [item title]);
137     maxTitleWidth = std::max(maxTitleWidth, size.width);
138   }
139   return maxTitleWidth;
140 }
141
142 @end
143
144 // The window used for the permission bubble controller.
145 // Subclassed to allow browser-handled keyboard events to be passed from the
146 // permission bubble to its parent window, which is a browser window.
147 @interface PermissionBubbleWindow : InfoBubbleWindow
148 @end
149
150 @implementation PermissionBubbleWindow
151 - (BOOL)performKeyEquivalent:(NSEvent*)event {
152   // Only handle events if they should be forwarded to the parent window.
153   if ([self allowShareParentKeyState]) {
154     content::NativeWebKeyboardEvent wrappedEvent(event);
155     if ([BrowserWindowUtils shouldHandleKeyboardEvent:wrappedEvent]) {
156       // Turn off sharing of key window state while the keyboard event is
157       // processed.  This avoids recursion - with the key window state shared,
158       // the parent window would just forward the event back to this class.
159       [self setAllowShareParentKeyState:NO];
160       BOOL eventHandled =
161           [BrowserWindowUtils handleKeyboardEvent:event
162                                          inWindow:[self parentWindow]];
163       [self setAllowShareParentKeyState:YES];
164       return eventHandled;
165     }
166   }
167   return NO;
168 }
169 @end
170
171 @interface PermissionBubbleController ()
172
173 // Returns an autoreleased NSView displaying the icon and label for |request|.
174 - (NSView*)labelForRequest:(PermissionBubbleRequest*)request;
175
176 // Returns an autoreleased NSView displaying the title for the bubble
177 // requesting settings for |host|.
178 - (NSView*)titleWithHostname:(const std::string&)host;
179
180 // Returns an autoreleased NSView displaying a menu for |request|.  The
181 // menu will be initialized as 'allow' if |allow| is YES.
182 - (NSView*)menuForRequest:(PermissionBubbleRequest*)request
183                   atIndex:(int)index
184                     allow:(BOOL)allow;
185
186 // Returns an autoreleased NSView of a button with |title| and |action|.
187 - (NSView*)buttonWithTitle:(NSString*)title
188                     action:(SEL)action;
189
190 // Returns an autoreleased NSView displaying a block button.
191 - (NSView*)blockButton;
192
193 // Returns an autoreleased NSView with a block button and a drop-down menu
194 // with one item, which will change the UI to allow customizing the permissions.
195 - (NSView*)blockButtonWithCustomizeMenu;
196
197 // Returns an autoreleased NSView displaying the close 'x' button.
198 - (NSView*)closeButton;
199
200 // Called when the 'ok' button is pressed.
201 - (void)ok:(id)sender;
202
203 // Called when the 'allow' button is pressed.
204 - (void)onAllow:(id)sender;
205
206 // Called when the 'block' button is pressed.
207 - (void)onBlock:(id)sender;
208
209 // Called when the 'close' button is pressed.
210 - (void)onClose:(id)sender;
211
212 // Called when the 'customize' button is pressed.
213 - (void)onCustomize:(id)sender;
214
215 // Sets the width of both |viewA| and |viewB| to be the larger of the
216 // two views' widths.  Does not change either view's origin or height.
217 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB;
218
219 // Sets the offset of |viewA| so that its vertical center is aligned with the
220 // vertical center of |viewB|.
221 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB;
222
223 @end
224
225 @implementation PermissionBubbleController
226
227 - (id)initWithParentWindow:(NSWindow*)parentWindow
228                     bridge:(PermissionBubbleCocoa*)bridge {
229   DCHECK(parentWindow);
230   DCHECK(bridge);
231   base::scoped_nsobject<PermissionBubbleWindow> window(
232       [[PermissionBubbleWindow alloc]
233           initWithContentRect:ui::kWindowSizeDeterminedLater
234                     styleMask:NSBorderlessWindowMask
235                       backing:NSBackingStoreBuffered
236                         defer:NO]);
237   [window setAllowedAnimations:info_bubble::kAnimateNone];
238   [window setReleasedWhenClosed:NO];
239   if ((self = [super initWithWindow:window
240                        parentWindow:parentWindow
241                          anchoredAt:NSZeroPoint])) {
242     [self setShouldCloseOnResignKey:NO];
243     [self setShouldOpenAsKeyWindow:NO];
244     [[self bubble] setArrowLocation:info_bubble::kTopLeft];
245     bridge_ = bridge;
246     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
247     [center addObserver:self
248                selector:@selector(parentWindowDidMove:)
249                    name:NSWindowDidMoveNotification
250                  object:parentWindow];
251   }
252   return self;
253 }
254
255 - (void)windowWillClose:(NSNotification*)notification {
256   bridge_->OnBubbleClosing();
257   [super windowWillClose:notification];
258 }
259
260 - (void)parentWindowWillBecomeFullScreen:(NSNotification*)notification {
261   // Override the base class implementation, which would have closed the bubble.
262 }
263
264 - (void)parentWindowDidResize:(NSNotification*)notification {
265   DCHECK(bridge_);
266   [self setAnchorPoint:bridge_->GetAnchorPoint()];
267 }
268
269 - (void)parentWindowDidMove:(NSNotification*)notification {
270   DCHECK(bridge_);
271   [self setAnchorPoint:bridge_->GetAnchorPoint()];
272 }
273
274 - (void)showAtAnchor:(NSPoint)anchorPoint
275          withDelegate:(PermissionBubbleView::Delegate*)delegate
276           forRequests:(const std::vector<PermissionBubbleRequest*>&)requests
277          acceptStates:(const std::vector<bool>&)acceptStates
278     customizationMode:(BOOL)customizationMode {
279   DCHECK(!requests.empty());
280   DCHECK(delegate);
281   DCHECK(!customizationMode || (requests.size() == acceptStates.size()));
282   delegate_ = delegate;
283
284   NSView* contentView = [[self window] contentView];
285   [contentView setSubviews:@[]];
286
287   // Create one button to use as a guide for the permissions' y-offsets.
288   base::scoped_nsobject<NSView> allowOrOkButton;
289   if (customizationMode) {
290     NSString* okTitle = l10n_util::GetNSString(IDS_OK);
291     allowOrOkButton.reset([[self buttonWithTitle:okTitle
292                                           action:@selector(ok:)] retain]);
293   } else {
294     NSString* allowTitle = l10n_util::GetNSString(IDS_PERMISSION_ALLOW);
295     allowOrOkButton.reset([[self buttonWithTitle:allowTitle
296                                           action:@selector(onAllow:)] retain]);
297   }
298   CGFloat yOffset = 2 * kVerticalPadding + NSMaxY([allowOrOkButton frame]);
299   BOOL singlePermission = requests.size() == 1;
300
301   base::scoped_nsobject<NSMutableArray> permissionMenus;
302   if (customizationMode)
303     permissionMenus.reset([[NSMutableArray alloc] init]);
304
305   CGFloat maxPermissionLineWidth = 0;
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;
312     [permissionView setFrameOrigin:origin];
313     [contentView addSubview:permissionView];
314
315     if (customizationMode) {
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]);
331   }
332
333   base::scoped_nsobject<NSView> titleView(
334       [[self titleWithHostname:requests[0]->GetRequestingHostname().host()]
335           retain]);
336   [contentView addSubview:titleView];
337   [titleView setFrameOrigin:NSMakePoint(kHorizontalPadding,
338                                         kVerticalPadding + yOffset)];
339
340   // 'x' button in the upper-right-hand corner.
341   base::scoped_nsobject<NSView> closeButton([[self closeButton] retain]);
342
343   // Determine the dimensions of the bubble.
344   // Once the height and width are set, the buttons and permission menus can
345   // be laid out correctly.
346   NSRect bubbleFrame = NSMakeRect(0, 0, kBubbleMinWidth, 0);
347
348   // Fix the height of the bubble relative to the title.
349   bubbleFrame.size.height = NSMaxY([titleView frame]) + kVerticalPadding +
350                             info_bubble::kBubbleArrowHeight;
351
352   if (customizationMode) {
353     // Add the maximum menu width to the bubble width.
354     CGFloat maxMenuWidth = 0;
355     for (AllowBlockMenuButton* button in permissionMenus.get()) {
356       maxMenuWidth = std::max(maxMenuWidth, [button maximumTitleWidth]);
357     }
358     maxPermissionLineWidth += maxMenuWidth;
359   }
360
361   // The title and 'x' button row must fit within the bubble.
362   CGFloat titleRowWidth = NSMaxX([titleView frame]) +
363                           NSWidth([closeButton frame]) +
364                           chrome_style::kCloseButtonPadding;
365
366   bubbleFrame.size.width = std::max(
367       NSWidth(bubbleFrame), std::max(titleRowWidth, maxPermissionLineWidth));
368
369   // Now that the bubble's dimensions have been set, lay out the buttons and
370   // menus.
371
372   // Place the close button at the upper-right-hand corner of the bubble.
373   NSPoint closeButtonOrigin =
374       NSMakePoint(NSWidth(bubbleFrame) - NSWidth([closeButton frame]) -
375                       chrome_style::kCloseButtonPadding,
376                   NSHeight(bubbleFrame) - NSWidth([closeButton frame]) -
377                       chrome_style::kCloseButtonPadding);
378   // Account for the bubble's arrow.
379   closeButtonOrigin.y -= info_bubble::kBubbleArrowHeight;
380
381   [closeButton setFrameOrigin:closeButtonOrigin];
382   [contentView addSubview:closeButton];
383
384   // Position the allow/ok button.
385   CGFloat xOrigin = NSWidth(bubbleFrame) - NSWidth([allowOrOkButton frame]) -
386                     kButtonRightEdgePadding;
387   [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)];
388   [contentView addSubview:allowOrOkButton];
389
390   if (customizationMode) {
391     // Adjust the horizontal origin for each menu so that its right edge
392     // lines up with the right edge of the ok button.
393     CGFloat rightEdge = NSMaxX([allowOrOkButton frame]);
394     for (NSView* view in permissionMenus.get()) {
395       [view setFrameOrigin:NSMakePoint(rightEdge - NSWidth([view frame]),
396                                        NSMinY([view frame]))];
397     }
398   } else {
399     base::scoped_nsobject<NSView> blockButton;
400     if (singlePermission)
401       blockButton.reset([[self blockButton] retain]);
402     else
403       blockButton.reset([[self blockButtonWithCustomizeMenu] retain]);
404     CGFloat width = [PermissionBubbleController matchWidthsOf:blockButton
405                                                         andOf:allowOrOkButton];
406     // Ensure the allow/ok button is still in the correct position.
407     xOrigin = NSWidth(bubbleFrame) - width - kHorizontalPadding;
408     [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)];
409     // Line up the block button.
410     xOrigin = NSMinX([allowOrOkButton frame]) - width - kBetweenButtonsPadding;
411     [blockButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)];
412     [contentView addSubview:blockButton];
413   }
414
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:anchorPoint];
429     [self showWindow:nil];
430   }
431 }
432
433 - (NSView*)labelForRequest:(PermissionBubbleRequest*)request {
434   DCHECK(request);
435   base::scoped_nsobject<NSView> permissionView(
436       [[NSView alloc] initWithFrame:NSZeroRect]);
437   base::scoped_nsobject<NSImageView> permissionIcon(
438       [[NSImageView alloc] initWithFrame:NSZeroRect]);
439   [permissionIcon setImage:ui::ResourceBundle::GetSharedInstance().
440       GetNativeImageNamed(request->GetIconID()).ToNSImage()];
441   [permissionIcon setFrameSize:kPermissionIconSize];
442   [permissionView addSubview:permissionIcon];
443
444   base::scoped_nsobject<NSTextField> permissionLabel(
445       [[NSTextField alloc] initWithFrame:NSZeroRect]);
446   base::string16 label = request->GetMessageTextFragment();
447   [permissionLabel setDrawsBackground:NO];
448   [permissionLabel setBezeled:NO];
449   [permissionLabel setEditable:NO];
450   [permissionLabel setSelectable:NO];
451   [permissionLabel
452       setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
453   [permissionLabel setStringValue:base::SysUTF16ToNSString(label)];
454   [permissionLabel sizeToFit];
455   [permissionLabel setFrameOrigin:
456       NSMakePoint(NSWidth([permissionIcon frame]), 0)];
457   [permissionView addSubview:permissionLabel];
458
459   // Match the horizontal centers of the two subviews.  Note that the label's
460   // center is rounded down, and the icon's center, up.  It looks better that
461   // way - with the text's center slightly lower than the icon's center - if the
462   // height delta is not evenly split.
463   NSRect iconFrame = [permissionIcon frame];
464   NSRect labelFrame = [permissionLabel frame];
465   NSRect unionFrame = NSUnionRect(iconFrame, labelFrame);
466
467   iconFrame.origin.y =
468       std::ceil((NSHeight(unionFrame) - NSHeight(iconFrame)) / 2);
469   labelFrame.origin.y =
470       std::floor((NSHeight(unionFrame) - NSHeight(labelFrame)) / 2);
471
472   [permissionLabel setFrame:labelFrame];
473   [permissionIcon setFrame:iconFrame];
474   [permissionView setFrame:unionFrame];
475
476   return permissionView.autorelease();
477 }
478
479 - (NSView*)titleWithHostname:(const std::string&)host {
480   base::scoped_nsobject<NSTextField> titleView(
481       [[NSTextField alloc] initWithFrame:NSZeroRect]);
482   [titleView setDrawsBackground:NO];
483   [titleView setBezeled:NO];
484   [titleView setEditable:NO];
485   [titleView setSelectable:NO];
486   [titleView setStringValue:
487       l10n_util::GetNSStringF(IDS_PERMISSIONS_BUBBLE_PROMPT,
488                               base::UTF8ToUTF16(host))];
489   [titleView setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
490   [titleView sizeToFit];
491   NSRect titleFrame = [titleView frame];
492   [titleView setFrameSize:NSMakeSize(NSWidth(titleFrame) + kTitlePaddingX,
493                                      NSHeight(titleFrame))];
494   return titleView.autorelease();
495 }
496
497 - (NSView*)menuForRequest:(PermissionBubbleRequest*)request
498                   atIndex:(int)index
499                     allow:(BOOL)allow {
500   DCHECK(request);
501   DCHECK(delegate_);
502   base::scoped_nsobject<AllowBlockMenuButton> button(
503       [[AllowBlockMenuButton alloc] initForURL:request->GetRequestingHostname()
504                                        allowed:allow
505                                          index:index
506                                       delegate:delegate_]);
507   return button.autorelease();
508 }
509
510 - (NSView*)buttonWithTitle:(NSString*)title
511                     action:(SEL)action {
512   base::scoped_nsobject<NSButton> button(
513       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
514   [button setButtonType:NSMomentaryPushInButton];
515   [button setTitle:title];
516   [button setTarget:self];
517   [button setAction:action];
518   [button sizeToFit];
519   return button.autorelease();
520 }
521
522 - (NSView*)blockButton {
523   NSString* blockTitle = l10n_util::GetNSString(IDS_PERMISSION_DENY);
524   return [self buttonWithTitle:blockTitle
525                         action:@selector(onBlock:)];
526 }
527
528 - (NSView*)blockButtonWithCustomizeMenu {
529   menuDelegate_.reset(new MenuDelegate(self));
530   base::scoped_nsobject<SplitBlockButton> blockButton([[SplitBlockButton alloc]
531       initWithMenuDelegate:menuDelegate_.get()]);
532   [blockButton sizeToFit];
533   [blockButton setEnabled:YES];
534   [blockButton setAction:@selector(onBlock:)];
535   [blockButton setTarget:self];
536   return blockButton.autorelease();
537 }
538
539 - (NSView*)closeButton {
540   int dimension = chrome_style::GetCloseButtonSize();
541   NSRect frame = NSMakeRect(0, 0, dimension, dimension);
542   base::scoped_nsobject<NSButton> button(
543       [[WebUIHoverCloseButton alloc] initWithFrame:frame]);
544   [button setAction:@selector(onClose:)];
545   [button setTarget:self];
546   return button.autorelease();
547 }
548
549 - (void)ok:(id)sender {
550   DCHECK(delegate_);
551   delegate_->Closing();
552 }
553
554 - (void)onAllow:(id)sender {
555   DCHECK(delegate_);
556   delegate_->Accept();
557 }
558
559 - (void)onBlock:(id)sender {
560   DCHECK(delegate_);
561   delegate_->Deny();
562 }
563
564 - (void)onClose:(id)sender {
565   DCHECK(delegate_);
566   delegate_->Closing();
567 }
568
569 - (void)onCustomize:(id)sender {
570   DCHECK(delegate_);
571   delegate_->SetCustomizationMode();
572 }
573
574 - (void)onMenuItemClicked:(int)commandId {
575   DCHECK(commandId == 0);
576   [self onCustomize:nil];
577 }
578
579 - (void)activateTabWithContents:(content::WebContents*)newContents
580                previousContents:(content::WebContents*)oldContents
581                         atIndex:(NSInteger)index
582                          reason:(int)reason {
583   // The show/hide of this bubble is handled by the PermissionBubbleManager.
584   // So bypass the base class, which would close the bubble here.
585 }
586
587 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB {
588   NSRect frameA = [viewA frame];
589   NSRect frameB = [viewB frame];
590   CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB));
591   [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))];
592   [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))];
593   return width;
594 }
595
596 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB {
597   NSRect frameA = [viewA frame];
598   NSRect frameB = [viewB frame];
599   frameA.origin.y =
600       NSMinY(frameB) + std::floor((NSHeight(frameB) - NSHeight(frameA)) / 2);
601   [viewA setFrameOrigin:frameA.origin];
602 }
603
604 @end  // implementation PermissionBubbleController