| OLD | NEW |
| (Empty) |
| 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/macros.h" | |
| 11 #include "base/strings/sys_string_conversions.h" | |
| 12 #include "base/strings/utf_string_conversions.h" | |
| 13 #include "chrome/browser/permissions/permission_request.h" | |
| 14 #include "chrome/browser/ui/browser.h" | |
| 15 #include "chrome/browser/ui/browser_finder.h" | |
| 16 #include "chrome/browser/ui/browser_window.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/chrome_style.h" | |
| 20 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h" | |
| 21 #import "chrome/browser/ui/cocoa/hover_close_button.h" | |
| 22 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | |
| 23 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | |
| 24 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" | |
| 25 #include "chrome/browser/ui/cocoa/page_info/permission_selector_button.h" | |
| 26 #include "chrome/browser/ui/cocoa/page_info/split_block_button.h" | |
| 27 #include "chrome/browser/ui/cocoa/page_info/website_settings_utils_cocoa.h" | |
| 28 #include "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h" | |
| 29 #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" | |
| 30 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" | |
| 31 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" | |
| 32 #include "chrome/browser/ui/page_info/permission_menu_model.h" | |
| 33 #include "chrome/browser/ui/website_settings/permission_prompt.h" | |
| 34 #include "chrome/common/pref_names.h" | |
| 35 #include "chrome/grit/generated_resources.h" | |
| 36 #include "components/prefs/pref_service.h" | |
| 37 #include "components/strings/grit/components_strings.h" | |
| 38 #include "components/url_formatter/elide_url.h" | |
| 39 #include "content/public/browser/native_web_keyboard_event.h" | |
| 40 #include "content/public/browser/user_metrics.h" | |
| 41 #include "skia/ext/skia_utils_mac.h" | |
| 42 #include "ui/base/cocoa/cocoa_base_utils.h" | |
| 43 #import "ui/base/cocoa/controls/hyperlink_text_view.h" | |
| 44 #import "ui/base/cocoa/menu_controller.h" | |
| 45 #include "ui/base/cocoa/window_size_constants.h" | |
| 46 #include "ui/base/l10n/l10n_util_mac.h" | |
| 47 #include "ui/base/models/simple_menu_model.h" | |
| 48 #include "ui/gfx/color_palette.h" | |
| 49 #include "ui/gfx/image/image_skia_util_mac.h" | |
| 50 #include "ui/gfx/paint_vector_icon.h" | |
| 51 #include "url/gurl.h" | |
| 52 | |
| 53 using base::UserMetricsAction; | |
| 54 | |
| 55 namespace { | |
| 56 | |
| 57 // Distance between permission icon and permission label. | |
| 58 const CGFloat kHorizontalIconPadding = 8.0f; | |
| 59 | |
| 60 // Distance between two permission labels. | |
| 61 const CGFloat kVerticalPermissionPadding = 2.0f; | |
| 62 | |
| 63 const CGFloat kHorizontalPadding = 13.0f; | |
| 64 const CGFloat kVerticalPadding = 15.0f; | |
| 65 const CGFloat kBetweenButtonsPadding = 10.0f; | |
| 66 const CGFloat kButtonRightEdgePadding = 17.0f; | |
| 67 const CGFloat kTitlePaddingX = 50.0f; | |
| 68 const CGFloat kBubbleMinWidth = 315.0f; | |
| 69 const NSSize kPermissionIconSize = {18, 18}; | |
| 70 | |
| 71 const NSInteger kFullscreenLeftOffset = 40; | |
| 72 | |
| 73 } // namespace | |
| 74 | |
| 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 std::unique_ptr<PermissionMenuModel> menuModel_; | |
| 81 base::scoped_nsobject<MenuController> menuController_; | |
| 82 } | |
| 83 | |
| 84 - (id)initForURL:(const GURL&)url | |
| 85 allowed:(BOOL)allow | |
| 86 index:(int)index | |
| 87 delegate:(PermissionPrompt::Delegate*)delegate | |
| 88 profile:(Profile*)profile; | |
| 89 | |
| 90 // Returns the maximum width of its possible titles. | |
| 91 - (CGFloat)maximumTitleWidth; | |
| 92 @end | |
| 93 | |
| 94 @implementation AllowBlockMenuButton | |
| 95 | |
| 96 - (id)initForURL:(const GURL&)url | |
| 97 allowed:(BOOL)allow | |
| 98 index:(int)index | |
| 99 delegate:(PermissionPrompt::Delegate*)delegate | |
| 100 profile:(Profile*)profile { | |
| 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 PermissionPrompt::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( | |
| 119 new PermissionMenuModel(profile, url, setting, changeCallback)); | |
| 120 menuController_.reset([[MenuController alloc] initWithModel:menuModel_.get() | |
| 121 useWithPopUpButtonCell:NO]); | |
| 122 [self setMenu:[menuController_ menu]]; | |
| 123 [self selectItemAtIndex:menuModel_->GetIndexOfCommandId(setting)]; | |
| 124 // Although the frame is reset, below, this sizes the cell properly. | |
| 125 [self sizeToFit]; | |
| 126 // Adjust the size to fit the current title. Using only -sizeToFit leaves | |
| 127 // an ugly amount of whitespace between the title and the arrows because it | |
| 128 // will fit to the largest element in the menu, not just the selected item. | |
| 129 [self setFrameSize:SizeForWebsiteSettingsButtonTitle(self, [self title])]; | |
| 130 } | |
| 131 return self; | |
| 132 } | |
| 133 | |
| 134 - (CGFloat)maximumTitleWidth { | |
| 135 CGFloat maxTitleWidth = 0; | |
| 136 for (NSMenuItem* item in [self itemArray]) { | |
| 137 NSSize size = SizeForWebsiteSettingsButtonTitle(self, [item title]); | |
| 138 maxTitleWidth = std::max(maxTitleWidth, size.width); | |
| 139 } | |
| 140 return maxTitleWidth; | |
| 141 } | |
| 142 | |
| 143 @end | |
| 144 | |
| 145 @interface PermissionBubbleController () | |
| 146 | |
| 147 // Determines if the bubble has an anchor in a corner or no anchor at all. | |
| 148 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation; | |
| 149 | |
| 150 // Returns the expected parent for this bubble. | |
| 151 - (NSWindow*)getExpectedParentWindow; | |
| 152 | |
| 153 // Returns an autoreleased NSView displaying the icon and label for |request|. | |
| 154 - (NSView*)labelForRequest:(PermissionRequest*)request; | |
| 155 | |
| 156 // Returns an autoreleased NSView displaying the title for the bubble | |
| 157 // requesting settings for |host|. | |
| 158 - (NSView*)titleWithOrigin:(const GURL&)origin; | |
| 159 | |
| 160 // Returns an autoreleased NSView displaying a menu for |request|. The | |
| 161 // menu will be initialized as 'allow' if |allow| is YES. | |
| 162 - (NSView*)menuForRequest:(PermissionRequest*)request | |
| 163 atIndex:(int)index | |
| 164 allow:(BOOL)allow; | |
| 165 | |
| 166 // Returns an autoreleased NSView of a button with |title| and |action|. | |
| 167 - (NSView*)buttonWithTitle:(NSString*)title | |
| 168 action:(SEL)action; | |
| 169 | |
| 170 // Returns an autoreleased NSView displaying a block button. | |
| 171 - (NSView*)blockButton; | |
| 172 | |
| 173 // Returns an autoreleased NSView displaying the close 'x' button. | |
| 174 - (NSView*)closeButton; | |
| 175 | |
| 176 // Called when the 'ok' button is pressed. | |
| 177 - (void)ok:(id)sender; | |
| 178 | |
| 179 // Called when the 'allow' button is pressed. | |
| 180 - (void)onAllow:(id)sender; | |
| 181 | |
| 182 // Called when the 'block' button is pressed. | |
| 183 - (void)onBlock:(id)sender; | |
| 184 | |
| 185 // Called when the 'close' button is pressed. | |
| 186 - (void)onClose:(id)sender; | |
| 187 | |
| 188 // Returns the constant offset from the left to use for fullscreen permission | |
| 189 // bubbles. Only used in tests. | |
| 190 + (NSInteger)getFullscreenLeftOffset; | |
| 191 | |
| 192 // Sets the width of both |viewA| and |viewB| to be the larger of the | |
| 193 // two views' widths. Does not change either view's origin or height. | |
| 194 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB; | |
| 195 | |
| 196 // Sets the offset of |viewA| so that its vertical center is aligned with the | |
| 197 // vertical center of |viewB|. | |
| 198 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB; | |
| 199 | |
| 200 // BaseBubbleController override. | |
| 201 - (IBAction)cancel:(id)sender; | |
| 202 | |
| 203 @end | |
| 204 | |
| 205 @implementation PermissionBubbleController | |
| 206 | |
| 207 - (id)initWithBrowser:(Browser*)browser bridge:(PermissionBubbleCocoa*)bridge { | |
| 208 DCHECK(browser); | |
| 209 DCHECK(bridge); | |
| 210 browser_ = browser; | |
| 211 base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc] | |
| 212 initWithContentRect:ui::kWindowSizeDeterminedLater | |
| 213 styleMask:NSBorderlessWindowMask | |
| 214 backing:NSBackingStoreBuffered | |
| 215 defer:NO]); | |
| 216 | |
| 217 [window setAllowedAnimations:info_bubble::kAnimateNone]; | |
| 218 [window setReleasedWhenClosed:NO]; | |
| 219 if ((self = [super initWithWindow:window | |
| 220 parentWindow:[self getExpectedParentWindow] | |
| 221 anchoredAt:NSZeroPoint])) { | |
| 222 [self setShouldCloseOnResignKey:NO]; | |
| 223 [self setShouldOpenAsKeyWindow:YES]; | |
| 224 [[self bubble] setArrowLocation:[self getExpectedArrowLocation]]; | |
| 225 bridge_ = bridge; | |
| 226 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | |
| 227 [center addObserver:self | |
| 228 selector:@selector(parentWindowDidMove:) | |
| 229 name:NSWindowDidMoveNotification | |
| 230 object:[self getExpectedParentWindow]]; | |
| 231 } | |
| 232 return self; | |
| 233 } | |
| 234 | |
| 235 - (LocationBarDecoration*)decorationForBubble { | |
| 236 if (![self hasVisibleLocationBar]) | |
| 237 return nullptr; | |
| 238 | |
| 239 LocationBarViewMac* location_bar = | |
| 240 [[self.parentWindow windowController] locationBarBridge]; | |
| 241 return location_bar->GetPageInfoDecoration(); | |
| 242 } | |
| 243 | |
| 244 + (NSPoint)getAnchorPointForBrowser:(Browser*)browser { | |
| 245 NSPoint anchor; | |
| 246 NSWindow* parentWindow = browser->window()->GetNativeWindow(); | |
| 247 if ([PermissionBubbleController hasVisibleLocationBarForBrowser:browser]) { | |
| 248 LocationBarViewMac* location_bar = | |
| 249 [[parentWindow windowController] locationBarBridge]; | |
| 250 anchor = location_bar->GetPageInfoBubblePoint(); | |
| 251 } else { | |
| 252 // Position the bubble on the left of the screen if there is no page info | |
| 253 // button to point at. | |
| 254 NSRect contentFrame = [[parentWindow contentView] frame]; | |
| 255 anchor = NSMakePoint(NSMinX(contentFrame) + kFullscreenLeftOffset, | |
| 256 NSMaxY(contentFrame)); | |
| 257 } | |
| 258 | |
| 259 return ui::ConvertPointFromWindowToScreen(parentWindow, anchor); | |
| 260 } | |
| 261 | |
| 262 + (bool)hasVisibleLocationBarForBrowser:(Browser*)browser { | |
| 263 if (!browser->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR)) | |
| 264 return false; | |
| 265 | |
| 266 if (!browser->exclusive_access_manager()->context()->IsFullscreen()) | |
| 267 return true; | |
| 268 | |
| 269 // If the browser is in browser-initiated full screen, a preference can cause | |
| 270 // the toolbar to be hidden. | |
| 271 if (browser->exclusive_access_manager() | |
| 272 ->fullscreen_controller() | |
| 273 ->IsFullscreenForBrowser()) { | |
| 274 PrefService* prefs = browser->profile()->GetPrefs(); | |
| 275 bool show_toolbar = prefs->GetBoolean(prefs::kShowFullscreenToolbar); | |
| 276 return show_toolbar; | |
| 277 } | |
| 278 | |
| 279 // Otherwise this is fullscreen without a toolbar, so there is no visible | |
| 280 // location bar. | |
| 281 return false; | |
| 282 } | |
| 283 | |
| 284 - (void)windowWillClose:(NSNotification*)notification { | |
| 285 [[NSNotificationCenter defaultCenter] | |
| 286 removeObserver:self | |
| 287 name:NSWindowDidMoveNotification | |
| 288 object:nil]; | |
| 289 bridge_->OnBubbleClosing(); | |
| 290 [super windowWillClose:notification]; | |
| 291 } | |
| 292 | |
| 293 - (void)parentWindowWillToggleFullScreen:(NSNotification*)notification { | |
| 294 // Override the base class implementation, which would have closed the bubble. | |
| 295 } | |
| 296 | |
| 297 - (void)parentWindowDidResize:(NSNotification*)notification { | |
| 298 // Override the base class implementation, which sets the anchor point. But | |
| 299 // it's not necessary since BrowserWindowController will notify the | |
| 300 // PermissionRequestManager to update the anchor position on a resize. | |
| 301 } | |
| 302 | |
| 303 - (void)parentWindowDidMove:(NSNotification*)notification { | |
| 304 DCHECK(bridge_); | |
| 305 [self setAnchorPoint:[self getExpectedAnchorPoint]]; | |
| 306 } | |
| 307 | |
| 308 - (void)showWithDelegate:(PermissionPrompt::Delegate*)delegate | |
| 309 forRequests:(const std::vector<PermissionRequest*>&)requests | |
| 310 acceptStates:(const std::vector<bool>&)acceptStates { | |
| 311 DCHECK(!requests.empty()); | |
| 312 DCHECK(delegate); | |
| 313 delegate_ = delegate; | |
| 314 | |
| 315 NSView* contentView = [[self window] contentView]; | |
| 316 [contentView setSubviews:@[]]; | |
| 317 | |
| 318 BOOL singlePermission = requests.size() == 1; | |
| 319 | |
| 320 // Create one button to use as a guide for the permissions' y-offsets. | |
| 321 base::scoped_nsobject<NSView> allowOrOkButton; | |
| 322 if (singlePermission) { | |
| 323 NSString* allowTitle = l10n_util::GetNSString(IDS_PERMISSION_ALLOW); | |
| 324 allowOrOkButton.reset([[self buttonWithTitle:allowTitle | |
| 325 action:@selector(onAllow:)] retain]); | |
| 326 } else { | |
| 327 NSString* okTitle = l10n_util::GetNSString(IDS_OK); | |
| 328 allowOrOkButton.reset([[self buttonWithTitle:okTitle | |
| 329 action:@selector(ok:)] retain]); | |
| 330 } | |
| 331 CGFloat yOffset = 2 * kVerticalPadding + NSMaxY([allowOrOkButton frame]); | |
| 332 | |
| 333 base::scoped_nsobject<NSMutableArray> permissionMenus; | |
| 334 if (!singlePermission) | |
| 335 permissionMenus.reset([[NSMutableArray alloc] init]); | |
| 336 | |
| 337 CGFloat maxPermissionLineWidth = 0; | |
| 338 CGFloat verticalPadding = 0.0f; | |
| 339 for (auto it = requests.begin(); it != requests.end(); it++) { | |
| 340 base::scoped_nsobject<NSView> permissionView( | |
| 341 [[self labelForRequest:(*it)] retain]); | |
| 342 NSPoint origin = [permissionView frame].origin; | |
| 343 origin.x += kHorizontalPadding; | |
| 344 origin.y += yOffset + verticalPadding; | |
| 345 [permissionView setFrameOrigin:origin]; | |
| 346 [contentView addSubview:permissionView]; | |
| 347 | |
| 348 if (!singlePermission) { | |
| 349 int index = it - requests.begin(); | |
| 350 base::scoped_nsobject<NSView> menu( | |
| 351 [[self menuForRequest:(*it) | |
| 352 atIndex:index | |
| 353 allow:acceptStates[index] ? YES : NO] retain]); | |
| 354 // Align vertically. Horizontal alignment will be adjusted once the | |
| 355 // widest permission is know. | |
| 356 [PermissionBubbleController alignCenterOf:menu | |
| 357 verticallyToCenterOf:permissionView]; | |
| 358 [permissionMenus addObject:menu]; | |
| 359 [contentView addSubview:menu]; | |
| 360 } | |
| 361 maxPermissionLineWidth = std::max( | |
| 362 maxPermissionLineWidth, NSMaxX([permissionView frame])); | |
| 363 yOffset += NSHeight([permissionView frame]); | |
| 364 | |
| 365 // Add extra padding for all but first permission. | |
| 366 verticalPadding = kVerticalPermissionPadding; | |
| 367 } | |
| 368 | |
| 369 base::scoped_nsobject<NSView> titleView( | |
| 370 [[self titleWithOrigin:requests[0]->GetOrigin()] retain]); | |
| 371 [contentView addSubview:titleView]; | |
| 372 [titleView setFrameOrigin:NSMakePoint(kHorizontalPadding, | |
| 373 kVerticalPadding + yOffset)]; | |
| 374 | |
| 375 // 'x' button in the upper-right-hand corner. | |
| 376 base::scoped_nsobject<NSView> closeButton([[self closeButton] retain]); | |
| 377 | |
| 378 // Determine the dimensions of the bubble. | |
| 379 // Once the height and width are set, the buttons and permission menus can | |
| 380 // be laid out correctly. | |
| 381 NSRect bubbleFrame = NSMakeRect(0, 0, kBubbleMinWidth, 0); | |
| 382 | |
| 383 // Fix the height of the bubble relative to the title. | |
| 384 bubbleFrame.size.height = NSMaxY([titleView frame]) + kVerticalPadding + | |
| 385 info_bubble::kBubbleArrowHeight; | |
| 386 | |
| 387 if (!singlePermission) { | |
| 388 // Add the maximum menu width to the bubble width. | |
| 389 CGFloat maxMenuWidth = 0; | |
| 390 for (AllowBlockMenuButton* button in permissionMenus.get()) { | |
| 391 maxMenuWidth = std::max(maxMenuWidth, [button maximumTitleWidth]); | |
| 392 } | |
| 393 maxPermissionLineWidth += maxMenuWidth; | |
| 394 } | |
| 395 | |
| 396 // The title and 'x' button row must fit within the bubble. | |
| 397 CGFloat titleRowWidth = NSMaxX([titleView frame]) + | |
| 398 NSWidth([closeButton frame]) + | |
| 399 chrome_style::kCloseButtonPadding; | |
| 400 | |
| 401 bubbleFrame.size.width = std::max( | |
| 402 NSWidth(bubbleFrame), std::max(titleRowWidth, maxPermissionLineWidth)); | |
| 403 | |
| 404 // Now that the bubble's dimensions have been set, lay out the buttons and | |
| 405 // menus. | |
| 406 | |
| 407 // Place the close button at the upper-right-hand corner of the bubble. | |
| 408 NSPoint closeButtonOrigin = | |
| 409 NSMakePoint(NSWidth(bubbleFrame) - NSWidth([closeButton frame]) - | |
| 410 chrome_style::kCloseButtonPadding, | |
| 411 NSHeight(bubbleFrame) - NSWidth([closeButton frame]) - | |
| 412 chrome_style::kCloseButtonPadding); | |
| 413 // Account for the bubble's arrow. | |
| 414 closeButtonOrigin.y -= info_bubble::kBubbleArrowHeight; | |
| 415 | |
| 416 [closeButton setFrameOrigin:closeButtonOrigin]; | |
| 417 [contentView addSubview:closeButton]; | |
| 418 | |
| 419 // Position the allow/ok button. | |
| 420 CGFloat xOrigin = NSWidth(bubbleFrame) - NSWidth([allowOrOkButton frame]) - | |
| 421 kButtonRightEdgePadding; | |
| 422 [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; | |
| 423 [contentView addSubview:allowOrOkButton]; | |
| 424 | |
| 425 if (singlePermission) { | |
| 426 base::scoped_nsobject<NSView> blockButton; | |
| 427 blockButton.reset([[self blockButton] retain]); | |
| 428 CGFloat width = [PermissionBubbleController matchWidthsOf:blockButton | |
| 429 andOf:allowOrOkButton]; | |
| 430 // Ensure the allow/ok button is still in the correct position. | |
| 431 xOrigin = NSWidth(bubbleFrame) - width - kHorizontalPadding; | |
| 432 [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; | |
| 433 // Line up the block button. | |
| 434 xOrigin = NSMinX([allowOrOkButton frame]) - width - kBetweenButtonsPadding; | |
| 435 [blockButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; | |
| 436 [contentView addSubview:blockButton]; | |
| 437 } else { | |
| 438 // Adjust the horizontal origin for each menu so that its right edge | |
| 439 // lines up with the right edge of the ok button. | |
| 440 CGFloat rightEdge = NSMaxX([allowOrOkButton frame]); | |
| 441 for (NSView* view in permissionMenus.get()) { | |
| 442 [view setFrameOrigin:NSMakePoint(rightEdge - NSWidth([view frame]), | |
| 443 NSMinY([view frame]))]; | |
| 444 } | |
| 445 } | |
| 446 | |
| 447 bubbleFrame = [[self window] frameRectForContentRect:bubbleFrame]; | |
| 448 if ([[self window] isVisible]) { | |
| 449 // Unfortunately, calling -setFrame followed by -setFrameOrigin (called | |
| 450 // within -setAnchorPoint) causes flickering. Avoid the flickering by | |
| 451 // manually adjusting the new frame's origin so that the top left stays the | |
| 452 // same, and only calling -setFrame. | |
| 453 NSRect currentWindowFrame = [[self window] frame]; | |
| 454 bubbleFrame.origin = currentWindowFrame.origin; | |
| 455 bubbleFrame.origin.y = bubbleFrame.origin.y + | |
| 456 currentWindowFrame.size.height - bubbleFrame.size.height; | |
| 457 [[self window] setFrame:bubbleFrame display:YES]; | |
| 458 } else { | |
| 459 [[self window] setFrame:bubbleFrame display:NO]; | |
| 460 [self setAnchorPoint:[self getExpectedAnchorPoint]]; | |
| 461 [self showWindow:nil]; | |
| 462 [[self window] makeFirstResponder:nil]; | |
| 463 [[self window] setInitialFirstResponder:allowOrOkButton.get()]; | |
| 464 } | |
| 465 } | |
| 466 | |
| 467 - (void)updateAnchorPosition { | |
| 468 [self setParentWindow:[self getExpectedParentWindow]]; | |
| 469 [self setAnchorPoint:[self getExpectedAnchorPoint]]; | |
| 470 [[self bubble] setArrowLocation:[self getExpectedArrowLocation]]; | |
| 471 } | |
| 472 | |
| 473 - (NSPoint)getExpectedAnchorPoint { | |
| 474 return [PermissionBubbleController getAnchorPointForBrowser:browser_]; | |
| 475 } | |
| 476 | |
| 477 - (bool)hasVisibleLocationBar { | |
| 478 return [PermissionBubbleController hasVisibleLocationBarForBrowser:browser_]; | |
| 479 } | |
| 480 | |
| 481 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation { | |
| 482 return info_bubble::kTopLeading; | |
| 483 } | |
| 484 | |
| 485 - (NSWindow*)getExpectedParentWindow { | |
| 486 DCHECK(browser_->window()); | |
| 487 return browser_->window()->GetNativeWindow(); | |
| 488 } | |
| 489 | |
| 490 - (NSView*)labelForRequest:(PermissionRequest*)request { | |
| 491 DCHECK(request); | |
| 492 base::scoped_nsobject<NSView> permissionView( | |
| 493 [[NSView alloc] initWithFrame:NSZeroRect]); | |
| 494 base::scoped_nsobject<NSImageView> permissionIcon( | |
| 495 [[NSImageView alloc] initWithFrame:NSZeroRect]); | |
| 496 [permissionIcon | |
| 497 setImage:NSImageFromImageSkia(gfx::CreateVectorIcon( | |
| 498 request->GetIconId(), 18, gfx::kChromeIconGrey))]; | |
| 499 [permissionIcon setFrameSize:kPermissionIconSize]; | |
| 500 [permissionView addSubview:permissionIcon]; | |
| 501 | |
| 502 base::scoped_nsobject<NSTextField> permissionLabel( | |
| 503 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
| 504 base::string16 label = request->GetMessageTextFragment(); | |
| 505 [permissionLabel setDrawsBackground:NO]; | |
| 506 [permissionLabel setBezeled:NO]; | |
| 507 [permissionLabel setEditable:NO]; | |
| 508 [permissionLabel setSelectable:NO]; | |
| 509 [permissionLabel | |
| 510 setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; | |
| 511 [permissionLabel setStringValue:base::SysUTF16ToNSString(label)]; | |
| 512 [permissionLabel sizeToFit]; | |
| 513 [permissionLabel setFrameOrigin: | |
| 514 NSMakePoint(NSWidth([permissionIcon frame]) + kHorizontalIconPadding, 0)]; | |
| 515 [permissionView addSubview:permissionLabel]; | |
| 516 | |
| 517 // Match the horizontal centers of the two subviews. Note that the label's | |
| 518 // center is rounded down, and the icon's center, up. It looks better that | |
| 519 // way - with the text's center slightly lower than the icon's center - if the | |
| 520 // height delta is not evenly split. | |
| 521 NSRect iconFrame = [permissionIcon frame]; | |
| 522 NSRect labelFrame = [permissionLabel frame]; | |
| 523 NSRect unionFrame = NSUnionRect(iconFrame, labelFrame); | |
| 524 | |
| 525 iconFrame.origin.y = | |
| 526 std::ceil((NSHeight(unionFrame) - NSHeight(iconFrame)) / 2); | |
| 527 labelFrame.origin.y = | |
| 528 std::floor((NSHeight(unionFrame) - NSHeight(labelFrame)) / 2); | |
| 529 | |
| 530 [permissionLabel setFrame:labelFrame]; | |
| 531 [permissionIcon setFrame:iconFrame]; | |
| 532 [permissionView setFrame:unionFrame]; | |
| 533 | |
| 534 return permissionView.autorelease(); | |
| 535 } | |
| 536 | |
| 537 - (NSView*)titleWithOrigin:(const GURL&)origin { | |
| 538 base::scoped_nsobject<NSTextField> titleView( | |
| 539 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
| 540 [titleView setDrawsBackground:NO]; | |
| 541 [titleView setBezeled:NO]; | |
| 542 [titleView setEditable:NO]; | |
| 543 [titleView setSelectable:NO]; | |
| 544 [titleView setStringValue:l10n_util::GetNSStringF( | |
| 545 IDS_PERMISSIONS_BUBBLE_PROMPT, | |
| 546 url_formatter::FormatUrlForSecurityDisplay( | |
| 547 origin, url_formatter::SchemeDisplay:: | |
| 548 OMIT_CRYPTOGRAPHIC))]; | |
| 549 [titleView setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; | |
| 550 [titleView sizeToFit]; | |
| 551 NSRect titleFrame = [titleView frame]; | |
| 552 [titleView setFrameSize:NSMakeSize(NSWidth(titleFrame) + kTitlePaddingX, | |
| 553 NSHeight(titleFrame))]; | |
| 554 return titleView.autorelease(); | |
| 555 } | |
| 556 | |
| 557 - (NSView*)menuForRequest:(PermissionRequest*)request | |
| 558 atIndex:(int)index | |
| 559 allow:(BOOL)allow { | |
| 560 DCHECK(request); | |
| 561 DCHECK(delegate_); | |
| 562 base::scoped_nsobject<AllowBlockMenuButton> button( | |
| 563 [[AllowBlockMenuButton alloc] initForURL:request->GetOrigin() | |
| 564 allowed:allow | |
| 565 index:index | |
| 566 delegate:delegate_ | |
| 567 profile:browser_->profile()]); | |
| 568 return button.autorelease(); | |
| 569 } | |
| 570 | |
| 571 - (NSView*)buttonWithTitle:(NSString*)title | |
| 572 action:(SEL)action { | |
| 573 base::scoped_nsobject<NSButton> button( | |
| 574 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]); | |
| 575 [button setButtonType:NSMomentaryPushInButton]; | |
| 576 [button setTitle:title]; | |
| 577 [button setTarget:self]; | |
| 578 [button setAction:action]; | |
| 579 [button sizeToFit]; | |
| 580 return button.autorelease(); | |
| 581 } | |
| 582 | |
| 583 - (NSView*)blockButton { | |
| 584 NSString* blockTitle = l10n_util::GetNSString(IDS_PERMISSION_DENY); | |
| 585 return [self buttonWithTitle:blockTitle | |
| 586 action:@selector(onBlock:)]; | |
| 587 } | |
| 588 | |
| 589 - (NSView*)closeButton { | |
| 590 int dimension = chrome_style::GetCloseButtonSize(); | |
| 591 NSRect frame = NSMakeRect(0, 0, dimension, dimension); | |
| 592 base::scoped_nsobject<NSButton> button( | |
| 593 [[WebUIHoverCloseButton alloc] initWithFrame:frame]); | |
| 594 [button setAction:@selector(onClose:)]; | |
| 595 [button setTarget:self]; | |
| 596 return button.autorelease(); | |
| 597 } | |
| 598 | |
| 599 - (void)ok:(id)sender { | |
| 600 if (delegate_) | |
| 601 delegate_->Accept(); | |
| 602 } | |
| 603 | |
| 604 - (void)onAllow:(id)sender { | |
| 605 if (delegate_) | |
| 606 delegate_->Accept(); | |
| 607 } | |
| 608 | |
| 609 - (void)onBlock:(id)sender { | |
| 610 if (delegate_) | |
| 611 delegate_->Deny(); | |
| 612 } | |
| 613 | |
| 614 - (void)onClose:(id)sender { | |
| 615 if (delegate_) | |
| 616 delegate_->Closing(); | |
| 617 } | |
| 618 | |
| 619 + (NSInteger)getFullscreenLeftOffset { | |
| 620 return kFullscreenLeftOffset; | |
| 621 } | |
| 622 | |
| 623 - (void)activateTabWithContents:(content::WebContents*)newContents | |
| 624 previousContents:(content::WebContents*)oldContents | |
| 625 atIndex:(NSInteger)index | |
| 626 reason:(int)reason { | |
| 627 // The show/hide of this bubble is handled by the PermissionRequestManager. | |
| 628 // So bypass the base class, which would close the bubble here. | |
| 629 } | |
| 630 | |
| 631 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB { | |
| 632 NSRect frameA = [viewA frame]; | |
| 633 NSRect frameB = [viewB frame]; | |
| 634 CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB)); | |
| 635 [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))]; | |
| 636 [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))]; | |
| 637 return width; | |
| 638 } | |
| 639 | |
| 640 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB { | |
| 641 NSRect frameA = [viewA frame]; | |
| 642 NSRect frameB = [viewB frame]; | |
| 643 frameA.origin.y = | |
| 644 NSMinY(frameB) + std::floor((NSHeight(frameB) - NSHeight(frameA)) / 2); | |
| 645 [viewA setFrameOrigin:frameA.origin]; | |
| 646 } | |
| 647 | |
| 648 - (IBAction)cancel:(id)sender { | |
| 649 // This is triggered by ESC when the bubble has focus. | |
| 650 if (delegate_) | |
| 651 delegate_->Closing(); | |
| 652 [super cancel:sender]; | |
| 653 } | |
| 654 | |
| 655 @end // implementation PermissionBubbleController | |
| OLD | NEW |