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/website_settings_bubble_controller.h"
9 #import <AppKit/AppKit.h>
11 #include "base/mac/bind_objc_block.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/sys_string_conversions.h"
14 #import "chrome/browser/certificate_viewer.h"
15 #include "chrome/browser/infobars/infobar_service.h"
16 #import "chrome/browser/ui/browser_dialogs.h"
17 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
18 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
19 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
20 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
21 #import "chrome/browser/ui/cocoa/website_settings/permission_selector_button.h"
22 #include "chrome/browser/ui/website_settings/permission_menu_model.h"
23 #include "chrome/browser/ui/website_settings/website_settings_utils.h"
24 #include "chrome/common/url_constants.h"
25 #include "content/public/browser/cert_store.h"
26 #include "content/public/browser/page_navigator.h"
27 #include "content/public/browser/user_metrics.h"
28 #include "content/public/browser/web_contents.h"
29 #include "grit/chromium_strings.h"
30 #include "grit/generated_resources.h"
31 #include "grit/theme_resources.h"
32 #include "grit/ui_resources.h"
33 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
34 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
35 #import "ui/base/cocoa/flipped_view.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/resource/resource_bundle.h"
38 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
42 // The default width of the window, in view coordinates. It may be larger to
44 const CGFloat kDefaultWindowWidth = 310;
46 // Spacing in between sections.
47 const CGFloat kVerticalSpacing = 10;
49 // Padding between the window frame and content.
50 const CGFloat kFramePadding = 20;
52 // Padding between the window frame and content for the internal page bubble.
53 const CGFloat kInternalPageFramePadding = 10;
55 // Spacing between the headlines and description text on the Connection tab.
56 const CGFloat kConnectionHeadlineSpacing = 2;
58 // Spacing between images on the Connection tab and the text.
59 const CGFloat kConnectionImageSpacing = 10;
61 // Spacing between the image and text for internal pages.
62 const CGFloat kInternalPageImageSpacing = 10;
64 // Square size of the images on the Connections tab.
65 const CGFloat kConnectionImageSize = 30;
67 // Square size of the image that is shown for internal pages.
68 const CGFloat kInternalPageImageSize = 26;
70 // Square size of the permission images.
71 const CGFloat kPermissionImageSize = 19;
73 // Vertical adjustment for the permission images.
74 // They have an extra pixel of padding on the bottom edge.
75 const CGFloat kPermissionImageYAdjust = 1;
77 // Spacing between a permission image and the text.
78 const CGFloat kPermissionImageSpacing = 3;
80 // The spacing between individual items in the Permissions tab.
81 const CGFloat kPermissionsTabSpacing = 12;
83 // Extra spacing after a headline on the Permissions tab.
84 const CGFloat kPermissionsHeadlineSpacing = 2;
86 // The amount of horizontal space between a permission label and the popup.
87 const CGFloat kPermissionPopUpXSpacing = 3;
89 // The extra space to the left of the first tab in the tab strip.
90 const CGFloat kTabStripXPadding = kFramePadding;
92 // The amount of space between the visual borders of adjacent tabs.
93 const CGFloat kTabSpacing = 4;
95 // The amount of space above the tab strip.
96 const CGFloat kTabStripTopSpacing = 14;
98 // The height of the clickable area of the tab.
99 const CGFloat kTabHeight = 28;
101 // The amount of space above tab labels.
102 const CGFloat kTabLabelTopPadding = 6;
104 // The amount of padding to leave on either side of the tab label.
105 const CGFloat kTabLabelXPadding = 12;
107 // Return the text color to use for the indentity status when the site's
108 // identity has been verified.
109 NSColor* IdentityVerifiedTextColor() {
110 // RGB components are specified using integer RGB [0-255] values for easy
111 // comparison to other platforms.
112 return [NSColor colorWithCalibratedRed:0x07/255.0
120 @interface WebsiteSettingsTabSegmentedCell : NSSegmentedCell {
122 base::scoped_nsobject<NSImage> tabstripCenterImage_;
123 base::scoped_nsobject<NSImage> tabstripLeftImage_;
124 base::scoped_nsobject<NSImage> tabstripRightImage_;
126 base::scoped_nsobject<NSImage> tabCenterImage_;
127 base::scoped_nsobject<NSImage> tabLeftImage_;
128 base::scoped_nsobject<NSImage> tabRightImage_;
130 // Key track of the index of segment which has keyboard focus. This is not
131 // the same as the currently selected segment.
132 NSInteger keySegment_;
136 @implementation WebsiteSettingsTabSegmentedCell
138 if ((self = [super init])) {
139 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
140 tabstripCenterImage_.reset(rb.GetNativeImageNamed(
141 IDR_WEBSITE_SETTINGS_TABSTRIP_CENTER).CopyNSImage());
142 tabstripLeftImage_.reset(rb.GetNativeImageNamed(
143 IDR_WEBSITE_SETTINGS_TABSTRIP_LEFT).CopyNSImage());
144 tabstripRightImage_.reset(rb.GetNativeImageNamed(
145 IDR_WEBSITE_SETTINGS_TABSTRIP_RIGHT).CopyNSImage());
147 tabCenterImage_.reset(
148 rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_CENTER2).CopyNSImage());
150 rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_LEFT2).CopyNSImage());
151 tabRightImage_.reset(
152 rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_RIGHT2).CopyNSImage());
157 // Called when keyboard focus in the segmented control is moved forward.
158 - (void)makeNextSegmentKey {
159 [super makeNextSegmentKey];
160 keySegment_ = (keySegment_ + 1) % [self segmentCount];
163 // Called when keyboard focus in the segmented control is moved backwards.
164 - (void)makePreviousSegmentKey {
165 [super makePreviousSegmentKey];
166 if (--keySegment_ < 0)
167 keySegment_ += [self segmentCount];
170 - (void)setSelectedSegment:(NSInteger)selectedSegment {
171 keySegment_ = selectedSegment;
172 [super setSelectedSegment:selectedSegment];
175 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
176 CGFloat tabstripHeight = [tabCenterImage_ size].height;
178 // Draw the tab for the selected segment.
179 NSRect tabRect = [self hitRectForSegment:[self selectedSegment]];
180 tabRect.origin.y = 0;
181 tabRect.size.height = tabstripHeight;
183 NSDrawThreePartImage(tabRect,
188 NSCompositeSourceOver,
192 // Draw the background to the left of the selected tab.
193 NSRect backgroundRect = NSMakeRect(0, 0, NSMinX(tabRect), tabstripHeight);
194 NSDrawThreePartImage(backgroundRect,
196 tabstripCenterImage_,
199 NSCompositeSourceOver,
203 // Draw the background to the right of the selected tab.
204 backgroundRect.origin.x = NSMaxX(tabRect);
205 backgroundRect.size.width = NSMaxX(cellFrame) - NSMaxX(tabRect);
206 NSDrawThreePartImage(backgroundRect,
208 tabstripCenterImage_,
211 NSCompositeSourceOver,
215 // Call the superclass method to trigger drawing of the tab labels.
216 [self drawInteriorWithFrame:cellFrame inView:controlView];
217 if ([[controlView window] firstResponder] == controlView)
218 [self drawFocusRect];
221 - (void)drawFocusRect {
222 gfx::ScopedNSGraphicsContextSaveGState scoped_state;
223 NSSetFocusRingStyle(NSFocusRingOnly);
224 [[NSColor keyboardFocusIndicatorColor] set];
225 NSFrameRect([self hitRectForSegment:keySegment_]);
228 // Return the hit rect (i.e., the visual bounds of the tab) for
229 // the given segment.
230 - (NSRect)hitRectForSegment:(NSInteger)segment {
231 CGFloat tabstripHeight = [tabCenterImage_ size].height;
232 DCHECK_GT(tabstripHeight, kTabHeight);
234 NSRect rect = NSMakeRect(0, tabstripHeight - kTabHeight,
235 [self widthForSegment:segment], kTabHeight);
236 for (NSInteger i = 0; i < segment; ++i) {
237 rect.origin.x += [self widthForSegment:i];
239 int xAdjust = segment == 0 ? kTabStripXPadding : 0;
240 rect.size.width = std::floor(
241 [self widthForSegment:segment] - kTabSpacing - xAdjust);
242 rect.origin.x = std::floor(rect.origin.x + kTabSpacing / 2 + xAdjust);
247 - (void)drawSegment:(NSInteger)segment
248 inFrame:(NSRect)frame
249 withView:(NSView*)controlView {
250 // Call the superclass to draw the label, adjusting the rectangle so that
251 // the label appears centered in the tab.
253 frame.origin.x += kTabStripXPadding / 2;
254 frame.size.width -= kTabStripXPadding;
256 frame.origin.y += kTabLabelTopPadding;
257 frame.size.height -= kTabLabelTopPadding;
258 [super drawSegment:segment inFrame:frame withView:controlView];
261 // Overrides the default tracking behavior to only respond to clicks inside the
262 // visual borders of the tab.
263 - (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
264 NSInteger segmentCount = [self segmentCount];
265 for (NSInteger i = 0; i < segmentCount; ++i) {
266 if (NSPointInRect(startPoint, [self hitRectForSegment:i]))
272 // Overrides the default cell height to take up the full height of the
273 // segmented control. Otherwise, clicks on the lower part of a tab will be
275 - (NSSize)cellSizeForBounds:(NSRect)aRect {
276 return NSMakeSize([super cellSizeForBounds:aRect].width,
277 [tabstripCenterImage_ size].height);
280 // Returns the minimum size required to display this cell.
281 // It should always be exactly as tall as the tabstrip background image.
283 return NSMakeSize([super cellSize].width,
284 [tabstripCenterImage_ size].height);
288 @implementation WebsiteSettingsBubbleController
290 - (CGFloat)defaultWindowWidth {
291 return kDefaultWindowWidth;
294 - (id)initWithParentWindow:(NSWindow*)parentWindow
295 websiteSettingsUIBridge:(WebsiteSettingsUIBridge*)bridge
296 webContents:(content::WebContents*)webContents
297 isInternalPage:(BOOL)isInternalPage {
298 DCHECK(parentWindow);
300 webContents_ = webContents;
302 // Use an arbitrary height; it will be changed in performLayout.
303 NSRect contentRect = NSMakeRect(0, 0, [self defaultWindowWidth], 1);
304 // Create an empty window into which content is placed.
305 base::scoped_nsobject<InfoBubbleWindow> window(
306 [[InfoBubbleWindow alloc] initWithContentRect:contentRect
307 styleMask:NSBorderlessWindowMask
308 backing:NSBackingStoreBuffered
311 if ((self = [super initWithWindow:window.get()
312 parentWindow:parentWindow
313 anchoredAt:NSZeroPoint])) {
314 [[self bubble] setArrowLocation:info_bubble::kTopLeft];
316 // Create the container view that uses flipped coordinates.
317 NSRect contentFrame = NSMakeRect(0, 0, [self defaultWindowWidth], 300);
319 [[FlippedView alloc] initWithFrame:contentFrame]);
321 // Replace the window's content.
322 [[[self window] contentView] setSubviews:
323 [NSArray arrayWithObject:contentView_.get()]];
326 [self initializeContentsForInternalPage];
328 [self initializeContents];
330 bridge_.reset(bridge);
331 bridge_->set_bubble_controller(self);
336 - (void)windowWillClose:(NSNotification*)notification {
337 if (presenter_.get())
338 presenter_->OnUIClosing();
339 [super windowWillClose:notification];
342 - (void)setPresenter:(WebsiteSettings*)presenter {
343 presenter_.reset(presenter);
346 // Create the subviews for the bubble for internal Chrome pages.
347 - (void)initializeContentsForInternalPage {
348 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
350 NSPoint controlOrigin = NSMakePoint(
351 kInternalPageFramePadding,
352 kInternalPageFramePadding + info_bubble::kBubbleArrowHeight);
353 NSSize imageSize = NSMakeSize(kInternalPageImageSize,
354 kInternalPageImageSize);
355 NSImageView* imageView = [self addImageWithSize:imageSize
357 atPoint:controlOrigin];
358 [imageView setImage:rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_26).ToNSImage()];
360 controlOrigin.x += NSWidth([imageView frame]) + kInternalPageImageSpacing;
361 base::string16 text = l10n_util::GetStringUTF16(IDS_PAGE_INFO_INTERNAL_PAGE);
362 NSTextField* textField = [self addText:text
363 withSize:[NSFont smallSystemFontSize]
366 atPoint:controlOrigin];
367 // Center the text vertically with the image.
368 NSRect textFrame = [textField frame];
369 textFrame.origin.y += (imageSize.height - NSHeight(textFrame)) / 2;
370 [textField setFrame:textFrame];
372 // Adjust the contentView to fit everything.
373 CGFloat maxY = std::max(NSMaxY([imageView frame]), NSMaxY(textFrame));
374 [contentView_ setFrame:NSMakeRect(
375 0, 0, [self defaultWindowWidth], maxY + kInternalPageFramePadding)];
377 [self sizeAndPositionWindow];
380 // Create the subviews for the website settings bubble.
381 - (void)initializeContents {
382 // Keeps track of the position that the next control should be drawn at.
383 NSPoint controlOrigin = NSMakePoint(
385 kFramePadding + info_bubble::kBubbleArrowHeight);
387 // Create a text field (empty for now) to show the site identity.
388 identityField_ = [self addText:base::string16()
389 withSize:[NSFont systemFontSize]
392 atPoint:controlOrigin];
394 NSHeight([identityField_ frame]) + kConnectionHeadlineSpacing;
396 // Create a text field to identity status (e.g. verified, not verified).
397 identityStatusField_ = [self addText:base::string16()
398 withSize:[NSFont smallSystemFontSize]
401 atPoint:controlOrigin];
403 // Create the tab view and its two tabs.
405 base::scoped_nsobject<WebsiteSettingsTabSegmentedCell> cell(
406 [[WebsiteSettingsTabSegmentedCell alloc] init]);
407 CGFloat tabstripHeight = [cell cellSize].height;
408 NSRect tabstripFrame = NSMakeRect(
409 0, 0, [self defaultWindowWidth], tabstripHeight);
410 segmentedControl_.reset(
411 [[NSSegmentedControl alloc] initWithFrame:tabstripFrame]);
412 [segmentedControl_ setCell:cell];
413 [segmentedControl_ setSegmentCount:WebsiteSettingsUI::NUM_TAB_IDS];
414 [segmentedControl_ setTarget:self];
415 [segmentedControl_ setAction:@selector(tabSelected:)];
416 [segmentedControl_ setAutoresizingMask:NSViewWidthSizable];
418 NSFont* smallSystemFont =
419 [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
420 NSDictionary* textAttributes =
421 [NSDictionary dictionaryWithObject:smallSystemFont
422 forKey:NSFontAttributeName];
424 // Create the "Permissions" tab.
425 NSString* label = l10n_util::GetNSString(
426 IDS_WEBSITE_SETTINGS_TAB_LABEL_PERMISSIONS);
427 [segmentedControl_ setLabel:label
428 forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
429 NSSize textSize = [label sizeWithAttributes:textAttributes];
430 CGFloat tabWidth = textSize.width + 2 * kTabLabelXPadding;
431 [segmentedControl_ setWidth:tabWidth + kTabStripXPadding
432 forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
434 // Create the "Connection" tab.
435 label = l10n_util::GetNSString(IDS_WEBSITE_SETTINGS_TAB_LABEL_CONNECTION);
436 textSize = [label sizeWithAttributes:textAttributes];
437 [segmentedControl_ setLabel:label
438 forSegment:WebsiteSettingsUI::TAB_ID_CONNECTION];
440 DCHECK_EQ([segmentedControl_ segmentCount], WebsiteSettingsUI::NUM_TAB_IDS);
442 // Make both tabs the width of the widest. The first segment has some
443 // additional padding that is not part of the tab, which is used for drawing
444 // the background of the tab strip.
445 tabWidth = std::max(tabWidth,
446 textSize.width + 2 * kTabLabelXPadding);
447 [segmentedControl_ setWidth:tabWidth + kTabStripXPadding
448 forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
449 [segmentedControl_ setWidth:tabWidth
450 forSegment:WebsiteSettingsUI::TAB_ID_CONNECTION];
452 [segmentedControl_ setFont:smallSystemFont];
453 [contentView_ addSubview:segmentedControl_];
455 NSRect tabFrame = NSMakeRect(0, 0, [self defaultWindowWidth], 300);
456 tabView_.reset([[NSTabView alloc] initWithFrame:tabFrame]);
457 [tabView_ setTabViewType:NSNoTabsNoBorder];
458 [tabView_ setDrawsBackground:NO];
459 [tabView_ setControlSize:NSSmallControlSize];
460 [contentView_ addSubview:tabView_.get()];
462 permissionsTabContentView_ = [self addPermissionsTabToTabView:tabView_];
463 connectionTabContentView_ = [self addConnectionTabToTabView:tabView_];
464 [self setSelectedTab:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
466 [self performLayout];
469 // Create the contents of the Permissions tab and add it to the given tab view.
470 // Returns a weak reference to the tab view item's view.
471 - (NSView*)addPermissionsTabToTabView:(NSTabView*)tabView {
472 base::scoped_nsobject<NSTabViewItem> item([[NSTabViewItem alloc] init]);
473 [tabView_ insertTabViewItem:item.get()
474 atIndex:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
475 base::scoped_nsobject<NSView> contentView(
476 [[FlippedView alloc] initWithFrame:[tabView_ contentRect]]);
477 [contentView setAutoresizingMask:NSViewWidthSizable];
478 [item setView:contentView.get()];
480 // Initialize the two containers that hold the controls. The initial frames
481 // are arbitrary, and will be adjusted after the controls are laid out.
482 cookiesView_ = [[[FlippedView alloc]
483 initWithFrame:[tabView_ contentRect]] autorelease];
484 [cookiesView_ setAutoresizingMask:NSViewWidthSizable];
486 permissionsView_ = [[[FlippedView alloc]
487 initWithFrame:[tabView_ contentRect]] autorelease];
489 [contentView addSubview:cookiesView_];
490 [contentView addSubview:permissionsView_];
492 // Create the link button to view cookies and site data.
493 // Its position will be set in performLayout.
494 NSString* cookieButtonText = l10n_util::GetNSString(
495 IDS_WEBSITE_SETTINGS_SHOW_SITE_DATA);
496 cookiesButton_ = [self addLinkButtonWithText:cookieButtonText
498 [cookiesButton_ setTarget:self];
499 [cookiesButton_ setAction:@selector(showCookiesAndSiteData:)];
501 return contentView.get();
504 // Handler for the link button below the list of cookies.
505 - (void)showCookiesAndSiteData:(id)sender {
506 DCHECK(webContents_);
507 content::RecordAction(
508 base::UserMetricsAction("WebsiteSettings_CookiesDialogOpened"));
509 chrome::ShowCollectedCookiesDialog(webContents_);
512 // Handler for the link button to show certificate information.
513 - (void)showCertificateInfo:(id)sender {
514 DCHECK(certificateId_);
515 ShowCertificateViewerByID(webContents_, [self parentWindow], certificateId_);
518 // Handler for the link to show help information about the connection tab.
519 - (void)showHelpPage:(id)sender {
520 webContents_->OpenURL(content::OpenURLParams(
521 GURL(chrome::kPageInfoHelpCenterURL), content::Referrer(),
522 NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, false));
525 // Create the contents of the Connection tab and add it to the given tab view.
526 // Returns a weak reference to the tab view item's view.
527 - (NSView*)addConnectionTabToTabView:(NSTabView*)tabView {
528 base::scoped_nsobject<NSTabViewItem> item([[NSTabViewItem alloc] init]);
529 base::scoped_nsobject<NSView> contentView(
530 [[FlippedView alloc] initWithFrame:[tabView_ contentRect]]);
531 [contentView setAutoresizingMask:NSViewWidthSizable];
533 // Place all the text and images at the same position. The positions will be
534 // adjusted in performLayout.
535 NSPoint textPosition = NSMakePoint(
536 kFramePadding + kConnectionImageSize + kConnectionImageSpacing,
538 NSPoint imagePosition = NSMakePoint(kFramePadding, kFramePadding);
539 NSSize imageSize = NSMakeSize(kConnectionImageSize, kConnectionImageSize);
541 identityStatusIcon_ = [self addImageWithSize:imageSize
543 atPoint:imagePosition];
544 identityStatusDescriptionField_ =
545 [self addText:base::string16()
546 withSize:[NSFont smallSystemFontSize]
548 toView:contentView.get()
549 atPoint:textPosition];
551 separatorAfterIdentity_ = [self addSeparatorToView:contentView];
552 [separatorAfterIdentity_ setAutoresizingMask:NSViewWidthSizable];
554 connectionStatusIcon_ = [self addImageWithSize:imageSize
556 atPoint:imagePosition];
557 connectionStatusDescriptionField_ =
558 [self addText:base::string16()
559 withSize:[NSFont smallSystemFontSize]
561 toView:contentView.get()
562 atPoint:textPosition];
563 certificateInfoButton_ = nil; // This will be created only if necessary.
564 separatorAfterConnection_ = [self addSeparatorToView:contentView];
565 [separatorAfterConnection_ setAutoresizingMask:NSViewWidthSizable];
567 firstVisitIcon_ = [self addImageWithSize:imageSize
569 atPoint:imagePosition];
570 firstVisitHeaderField_ =
571 [self addText:l10n_util::GetStringUTF16(IDS_PAGE_INFO_SITE_INFO_TITLE)
572 withSize:[NSFont smallSystemFontSize]
574 toView:contentView.get()
575 atPoint:textPosition];
576 firstVisitDescriptionField_ =
577 [self addText:base::string16()
578 withSize:[NSFont smallSystemFontSize]
580 toView:contentView.get()
581 atPoint:textPosition];
583 separatorAfterFirstVisit_ = [self addSeparatorToView:contentView];
584 [separatorAfterFirstVisit_ setAutoresizingMask:NSViewWidthSizable];
586 NSString* helpButtonText = l10n_util::GetNSString(
587 IDS_PAGE_INFO_HELP_CENTER_LINK);
588 helpButton_ = [self addLinkButtonWithText:helpButtonText
590 [helpButton_ setTarget:self];
591 [helpButton_ setAction:@selector(showHelpPage:)];
593 [item setView:contentView.get()];
594 [tabView_ insertTabViewItem:item.get()
595 atIndex:WebsiteSettingsUI::TAB_ID_CONNECTION];
597 return contentView.get();
600 // Set the Y position of |view| to the given position, and return the position
601 // of its bottom edge.
602 - (CGFloat)setYPositionOfView:(NSView*)view to:(CGFloat)position {
603 NSRect frame = [view frame];
604 frame.origin.y = position;
605 [view setFrame:frame];
606 return position + NSHeight(frame);
609 - (void)setWidthOfView:(NSView*)view to:(CGFloat)width {
610 [view setFrameSize:NSMakeSize(width, NSHeight([view frame]))];
613 // Layout all of the controls in the window. This should be called whenever
614 // the content has changed.
615 - (void)performLayout {
616 // Make the content at least as wide as the permissions view.
617 CGFloat contentWidth = std::max([self defaultWindowWidth],
618 NSWidth([permissionsView_ frame]));
620 // Set the width of the content view now, so that all the text fields will
621 // be sized to fit before their heights and vertical positions are adjusted.
622 // The tab view will only resize the currently selected tab, so resize both
623 // tab content views manually.
624 [self setWidthOfView:contentView_ to:contentWidth];
625 [self setWidthOfView:permissionsTabContentView_ to:contentWidth];
626 [self setWidthOfView:connectionTabContentView_ to:contentWidth];
628 // Place the identity status immediately below the identity.
629 [self sizeTextFieldHeightToFit:identityField_];
630 [self sizeTextFieldHeightToFit:identityStatusField_];
631 CGFloat yPos = NSMaxY([identityField_ frame]) + kConnectionHeadlineSpacing;
632 yPos = [self setYPositionOfView:identityStatusField_ to:yPos];
634 // Lay out the Permissions tab.
636 yPos = [self setYPositionOfView:cookiesView_ to:kFramePadding];
638 // Put the link button for cookies and site data just below the cookie info.
639 NSRect cookiesButtonFrame = [cookiesButton_ frame];
640 cookiesButtonFrame.origin.y = yPos;
641 cookiesButtonFrame.origin.x = kFramePadding;
642 [cookiesButton_ setFrame:cookiesButtonFrame];
644 // Put the permission info just below the link button.
645 [self setYPositionOfView:permissionsView_
646 to:NSMaxY(cookiesButtonFrame) + kFramePadding];
648 // Lay out the Connection tab.
650 // Lay out the identity status section.
651 [self sizeTextFieldHeightToFit:identityStatusDescriptionField_];
652 yPos = std::max(NSMaxY([identityStatusDescriptionField_ frame]),
653 NSMaxY([identityStatusIcon_ frame]));
654 if (certificateInfoButton_) {
655 NSRect certificateButtonFrame = [certificateInfoButton_ frame];
656 certificateButtonFrame.origin.x = NSMinX(
657 [identityStatusDescriptionField_ frame]);
658 certificateButtonFrame.origin.y = yPos + kVerticalSpacing;
659 [certificateInfoButton_ setFrame:certificateButtonFrame];
660 yPos = NSMaxY(certificateButtonFrame);
662 yPos = [self setYPositionOfView:separatorAfterIdentity_
663 to:yPos + kVerticalSpacing];
664 yPos += kVerticalSpacing;
666 // Lay out the connection status section.
667 [self sizeTextFieldHeightToFit:connectionStatusDescriptionField_];
668 [self setYPositionOfView:connectionStatusIcon_ to:yPos];
669 [self setYPositionOfView:connectionStatusDescriptionField_ to:yPos];
670 yPos = std::max(NSMaxY([connectionStatusDescriptionField_ frame]),
671 NSMaxY([connectionStatusIcon_ frame]));
672 yPos = [self setYPositionOfView:separatorAfterConnection_
673 to:yPos + kVerticalSpacing];
674 yPos += kVerticalSpacing;
676 // Lay out the last visit section.
677 [self setYPositionOfView:firstVisitIcon_ to:yPos];
678 [self sizeTextFieldHeightToFit:firstVisitHeaderField_];
679 yPos = [self setYPositionOfView:firstVisitHeaderField_ to:yPos];
680 yPos += kConnectionHeadlineSpacing;
681 [self sizeTextFieldHeightToFit:firstVisitDescriptionField_];
682 yPos = [self setYPositionOfView:firstVisitDescriptionField_ to:yPos];
683 yPos = [self setYPositionOfView:separatorAfterFirstVisit_
684 to:yPos + kVerticalSpacing];
685 yPos += kVerticalSpacing;
686 [self setYPositionOfView:helpButton_ to:yPos];
688 // Adjust the tab view size and place it below the identity status.
690 yPos = NSMaxY([identityStatusField_ frame]) + kTabStripTopSpacing;
691 yPos = [self setYPositionOfView:segmentedControl_ to:yPos];
693 CGFloat connectionTabHeight = NSMaxY([helpButton_ frame]) + kVerticalSpacing;
695 NSRect tabViewFrame = [tabView_ frame];
696 tabViewFrame.origin.y = yPos;
697 tabViewFrame.size.height =
698 std::max(connectionTabHeight, NSMaxY([permissionsView_ frame]));
699 tabViewFrame.size.width = contentWidth;
700 [tabView_ setFrame:tabViewFrame];
702 // Adjust the contentView to fit everything.
703 [contentView_ setFrame:NSMakeRect(
704 0, 0, NSWidth(tabViewFrame), NSMaxY(tabViewFrame))];
706 [self sizeAndPositionWindow];
709 // Adjust the size of the window to match the size of the content, and position
710 // the bubble anchor appropriately.
711 - (void)sizeAndPositionWindow {
712 NSRect windowFrame = [contentView_ frame];
713 windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size
715 // Adjust the origin by the difference in height.
716 windowFrame.origin = [[self window] frame].origin;
717 windowFrame.origin.y -= NSHeight(windowFrame) -
718 NSHeight([[self window] frame]);
720 // Resize the window. Only animate if the window is visible, otherwise it
721 // could be "growing" while it's opening, looking awkward.
722 [[self window] setFrame:windowFrame
724 animate:[[self window] isVisible]];
726 // Adjust the anchor for the bubble.
727 NSPoint anchorPoint =
728 [self anchorPointForWindowWithHeight:NSHeight(windowFrame)
729 parentWindow:[self parentWindow]];
730 [self setAnchorPoint:anchorPoint];
733 // Takes in the bubble's height and the parent window, which should be a
734 // BrowserWindow, and gets the proper anchor point for the bubble. The returned
735 // point is in screen coordinates.
736 - (NSPoint)anchorPointForWindowWithHeight:(CGFloat)bubbleHeight
737 parentWindow:(NSWindow*)parent {
738 BrowserWindowController* controller = [parent windowController];
739 NSPoint origin = NSZeroPoint;
740 if ([controller isKindOfClass:[BrowserWindowController class]]) {
741 LocationBarViewMac* locationBar = [controller locationBarBridge];
743 NSPoint bubblePoint = locationBar->GetPageInfoBubblePoint();
744 origin = [parent convertBaseToScreen:bubblePoint];
750 // Sets properties on the given |field| to act as the title or description
751 // labels in the bubble.
752 - (void)configureTextFieldAsLabel:(NSTextField*)textField {
753 [textField setEditable:NO];
754 [textField setSelectable:YES];
755 [textField setDrawsBackground:NO];
756 [textField setBezeled:NO];
759 // Adjust the height of the given text field to match its text.
760 - (void)sizeTextFieldHeightToFit:(NSTextField*)textField {
761 NSRect frame = [textField frame];
763 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:
765 [textField setFrame:frame];
768 // Create a new text field and add it to the given array of subviews.
769 // The array will retain a reference to the object.
770 - (NSTextField*)addText:(const base::string16&)text
771 withSize:(CGFloat)fontSize
774 atPoint:(NSPoint)point {
775 // Size the text to take up the full available width, with some padding.
776 // The height is arbitrary as it will be adjusted later.
777 CGFloat width = NSWidth([view frame]) - point.x - kFramePadding;
778 NSRect frame = NSMakeRect(point.x, point.y, width, 100);
779 base::scoped_nsobject<NSTextField> textField(
780 [[NSTextField alloc] initWithFrame:frame]);
781 [self configureTextFieldAsLabel:textField.get()];
782 [textField setStringValue:base::SysUTF16ToNSString(text)];
783 NSFont* font = bold ? [NSFont boldSystemFontOfSize:fontSize]
784 : [NSFont systemFontOfSize:fontSize];
785 [textField setFont:font];
786 [self sizeTextFieldHeightToFit:textField];
787 [textField setAutoresizingMask:NSViewWidthSizable];
788 [view addSubview:textField.get()];
789 return textField.get();
792 // Add an image as a subview of the given view, placed at a pre-determined x
793 // position and the given y position. Return the new NSImageView.
794 - (NSImageView*)addImageWithSize:(NSSize)size
796 atPoint:(NSPoint)point {
797 NSRect frame = NSMakeRect(point.x, point.y, size.width, size.height);
798 base::scoped_nsobject<NSImageView> imageView(
799 [[NSImageView alloc] initWithFrame:frame]);
800 [imageView setImageFrameStyle:NSImageFrameNone];
801 [view addSubview:imageView.get()];
802 return imageView.get();
805 // Add a separator as a subview of the given view. Return the new view.
806 - (NSView*)addSeparatorToView:(NSView*)view {
807 // Take up almost the full width of the container's frame.
808 CGFloat width = NSWidth([view frame]) - 2 * kFramePadding;
810 // Use an arbitrary position; it will be adjusted in performLayout.
812 [self separatorWithFrame:NSMakeRect(kFramePadding, 0, width, 0)];
813 [view addSubview:spacer];
817 // Add a link button with the given text to |view|.
818 - (NSButton*)addLinkButtonWithText:(NSString*)text toView:(NSView*)view {
819 // Frame size is arbitrary; it will be adjusted by the layout tweaker.
820 NSRect frame = NSMakeRect(kFramePadding, 0, 100, 10);
821 base::scoped_nsobject<NSButton> button(
822 [[NSButton alloc] initWithFrame:frame]);
823 base::scoped_nsobject<HyperlinkButtonCell> cell(
824 [[HyperlinkButtonCell alloc] initTextCell:text]);
825 [cell setControlSize:NSSmallControlSize];
826 [button setCell:cell.get()];
827 [button setButtonType:NSMomentaryPushInButton];
828 [button setBezelStyle:NSRegularSquareBezelStyle];
829 [view addSubview:button.get()];
831 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()];
835 // Add a pop-up button for |permissionInfo| to the given view.
836 - (NSPopUpButton*)addPopUpButtonForPermission:
837 (const WebsiteSettingsUI::PermissionInfo&)permissionInfo
839 atPoint:(NSPoint)point {
841 GURL url = webContents_ ? webContents_->GetURL() : GURL();
842 __block WebsiteSettingsBubbleController* weakSelf = self;
843 PermissionMenuModel::ChangeCallback callback =
844 base::BindBlock(^(const WebsiteSettingsUI::PermissionInfo& permission) {
845 [weakSelf onPermissionChanged:permission.type to:permission.setting];
847 base::scoped_nsobject<PermissionSelectorButton> button(
848 [[PermissionSelectorButton alloc] initWithPermissionInfo:permissionInfo
850 withCallback:callback]);
851 // Determine the largest possible size for this button.
852 CGFloat maxTitleWidth =
853 [button maxTitleWidthWithDefaultSetting:permissionInfo.default_setting];
855 // Ensure the containing view is large enough to contain the button with its
856 // widest possible title.
857 NSRect containerFrame = [view frame];
858 containerFrame.size.width = std::max(
859 NSWidth(containerFrame), point.x + maxTitleWidth + kFramePadding);
860 [view setFrame:containerFrame];
861 [view addSubview:button.get()];
865 // Called when the user changes the setting of a permission.
866 - (void)onPermissionChanged:(ContentSettingsType)permissionType
867 to:(ContentSetting)newSetting {
869 presenter_->OnSitePermissionChanged(permissionType, newSetting);
872 // Called when the user changes the selected segment in the segmented control.
873 - (void)tabSelected:(id)sender {
874 [tabView_ selectTabViewItemAtIndex:[segmentedControl_ selectedSegment]];
877 // Adds a new row to the UI listing the permissions. Returns the amount of
878 // vertical space that was taken up by the row.
879 - (NSPoint)addPermission:
880 (const WebsiteSettingsUI::PermissionInfo&)permissionInfo
882 atPoint:(NSPoint)point {
883 base::scoped_nsobject<NSImage> image(
884 [WebsiteSettingsUI::GetPermissionIcon(permissionInfo).ToNSImage()
886 NSImageView* imageView = [self addImageWithSize:[image size]
889 [imageView setImage:image];
890 point.x += kPermissionImageSize + kPermissionImageSpacing;
892 base::string16 labelText =
893 WebsiteSettingsUI::PermissionTypeToUIString(permissionInfo.type) +
894 base::ASCIIToUTF16(":");
896 NSTextField* label = [self addText:labelText
897 withSize:[NSFont smallSystemFontSize]
902 NSPoint popUpPosition = NSMakePoint(NSMaxX([label frame]), point.y);
903 NSPopUpButton* button = [self addPopUpButtonForPermission:permissionInfo
905 atPoint:popUpPosition];
907 // Adjust the vertical position of the button so that its title text is
908 // aligned with the label. Assumes that the text is the same size in both.
909 // Also adjust the horizontal position to remove excess space due to the
911 NSRect titleRect = [[button cell] titleRectForBounds:[button bounds]];
912 popUpPosition.x -= titleRect.origin.x - kPermissionPopUpXSpacing;
913 popUpPosition.y -= titleRect.origin.y;
914 [button setFrameOrigin:popUpPosition];
916 // Align the icon with the text.
917 [self alignPermissionIcon:imageView withTextField:label];
919 // Permissions specified by policy or an extension cannot be changed.
920 if (permissionInfo.source == content_settings::SETTING_SOURCE_EXTENSION ||
921 permissionInfo.source == content_settings::SETTING_SOURCE_POLICY) {
922 [button setEnabled:NO];
925 NSRect buttonFrame = [button frame];
926 return NSMakePoint(NSMaxX(buttonFrame), NSMaxY(buttonFrame));
929 // Align an image with a text field by vertically centering the image on
930 // the cap height of the first line of text.
931 - (void)alignPermissionIcon:(NSImageView*)imageView
932 withTextField:(NSTextField*)textField {
933 NSFont* font = [textField font];
935 // Calculate the offset from the top of the text field.
936 CGFloat capHeight = [font capHeight];
937 CGFloat offset = (kPermissionImageSize - capHeight) / 2 -
938 ([font ascender] - capHeight) - kPermissionImageYAdjust;
940 NSRect frame = [imageView frame];
941 frame.origin.y -= offset;
942 [imageView setFrame:frame];
945 - (CGFloat)addCookieInfo:
946 (const WebsiteSettingsUI::CookieInfo&)cookieInfo
948 atPoint:(NSPoint)point {
949 WebsiteSettingsUI::PermissionInfo info;
950 info.type = CONTENT_SETTINGS_TYPE_COOKIES;
951 info.setting = CONTENT_SETTING_ALLOW;
952 NSImage* image = WebsiteSettingsUI::GetPermissionIcon(info).ToNSImage();
953 NSImageView* imageView = [self addImageWithSize:[image size]
956 [imageView setImage:image];
957 point.x += kPermissionImageSize + kPermissionImageSpacing;
959 base::string16 labelText = l10n_util::GetStringFUTF16(
960 IDS_WEBSITE_SETTINGS_SITE_DATA_STATS_LINE,
961 base::UTF8ToUTF16(cookieInfo.cookie_source),
962 base::IntToString16(cookieInfo.allowed),
963 base::IntToString16(cookieInfo.blocked));
965 NSTextField* label = [self addText:labelText
966 withSize:[NSFont smallSystemFontSize]
971 // Align the icon with the text.
972 [self alignPermissionIcon:imageView withTextField:label];
974 return NSHeight([label frame]);
977 // Set the content of the identity and identity status fields.
978 - (void)setIdentityInfo:(const WebsiteSettingsUI::IdentityInfo&)identityInfo {
979 [identityField_ setStringValue:
980 base::SysUTF8ToNSString(identityInfo.site_identity)];
981 [identityStatusField_ setStringValue:
982 base::SysUTF16ToNSString(identityInfo.GetIdentityStatusText())];
984 WebsiteSettings::SiteIdentityStatus status = identityInfo.identity_status;
985 if (status == WebsiteSettings::SITE_IDENTITY_STATUS_CERT ||
986 status == WebsiteSettings::SITE_IDENTITY_STATUS_EV_CERT) {
987 [identityStatusField_ setTextColor:IdentityVerifiedTextColor()];
989 // If there is a certificate, add a button for viewing the certificate info.
990 certificateId_ = identityInfo.cert_id;
991 if (certificateId_) {
992 if (!certificateInfoButton_) {
993 NSString* text = l10n_util::GetNSString(IDS_PAGEINFO_CERT_INFO_BUTTON);
994 certificateInfoButton_ = [self addLinkButtonWithText:text
995 toView:connectionTabContentView_];
997 [certificateInfoButton_ setTarget:self];
998 [certificateInfoButton_ setAction:@selector(showCertificateInfo:)];
1001 certificateInfoButton_ = nil;
1004 [identityStatusIcon_ setImage:WebsiteSettingsUI::GetIdentityIcon(
1005 identityInfo.identity_status).ToNSImage()];
1006 [identityStatusDescriptionField_ setStringValue:
1007 base::SysUTF8ToNSString(identityInfo.identity_status_description)];
1009 [connectionStatusIcon_ setImage:WebsiteSettingsUI::GetConnectionIcon(
1010 identityInfo.connection_status).ToNSImage()];
1011 [connectionStatusDescriptionField_ setStringValue:
1012 base::SysUTF8ToNSString(identityInfo.connection_status_description)];
1014 [self performLayout];
1017 - (void)setCookieInfo:(const CookieInfoList&)cookieInfoList {
1018 // The contents of the permissions view can cause the whole window to get
1019 // bigger, but currently permissions are always set before cookie info.
1020 // Check to make sure that's still the case.
1021 DCHECK_GT([[permissionsView_ subviews] count], 0U);
1023 [cookiesView_ setSubviews:[NSArray array]];
1024 NSPoint controlOrigin = NSMakePoint(kFramePadding, 0);
1026 base::string16 sectionTitle = l10n_util::GetStringUTF16(
1027 IDS_WEBSITE_SETTINGS_TITLE_SITE_DATA);
1028 NSTextField* header = [self addText:sectionTitle
1029 withSize:[NSFont smallSystemFontSize]
1032 atPoint:controlOrigin];
1033 controlOrigin.y += NSHeight([header frame]) + kPermissionsHeadlineSpacing;
1035 for (CookieInfoList::const_iterator it = cookieInfoList.begin();
1036 it != cookieInfoList.end();
1038 controlOrigin.y += kPermissionsTabSpacing;
1039 CGFloat rowHeight = [self addCookieInfo:*it
1041 atPoint:controlOrigin];
1042 controlOrigin.y += rowHeight;
1045 controlOrigin.y += kPermissionsTabSpacing;
1046 [cookiesView_ setFrameSize:
1047 NSMakeSize(NSWidth([cookiesView_ frame]), controlOrigin.y)];
1049 [self performLayout];
1052 - (void)setPermissionInfo:(const PermissionInfoList&)permissionInfoList {
1053 [permissionsView_ setSubviews:[NSArray array]];
1054 NSPoint controlOrigin = NSMakePoint(kFramePadding, 0);
1056 base::string16 sectionTitle = l10n_util::GetStringUTF16(
1057 IDS_WEBSITE_SETTINGS_TITLE_SITE_PERMISSIONS);
1058 NSTextField* header = [self addText:sectionTitle
1059 withSize:[NSFont smallSystemFontSize]
1061 toView:permissionsView_
1062 atPoint:controlOrigin];
1063 [self sizeTextFieldHeightToFit:header];
1064 controlOrigin.y += NSHeight([header frame]) + kPermissionsHeadlineSpacing;
1066 for (PermissionInfoList::const_iterator permission =
1067 permissionInfoList.begin();
1068 permission != permissionInfoList.end();
1070 controlOrigin.y += kPermissionsTabSpacing;
1071 NSPoint rowBottomRight = [self addPermission:*permission
1072 toView:permissionsView_
1073 atPoint:controlOrigin];
1074 controlOrigin.y = rowBottomRight.y;
1076 controlOrigin.y += kFramePadding;
1077 [permissionsView_ setFrameSize:
1078 NSMakeSize(NSWidth([permissionsView_ frame]), controlOrigin.y)];
1079 [self performLayout];
1082 - (void)setFirstVisit:(const base::string16&)firstVisit {
1083 [firstVisitIcon_ setImage:
1084 WebsiteSettingsUI::GetFirstVisitIcon(firstVisit).ToNSImage()];
1085 [firstVisitDescriptionField_ setStringValue:
1086 base::SysUTF16ToNSString(firstVisit)];
1087 [self performLayout];
1090 - (void)setSelectedTab:(WebsiteSettingsUI::TabId)tabId {
1091 NSInteger index = static_cast<NSInteger>(tabId);
1092 [segmentedControl_ setSelectedSegment:index];
1093 [tabView_ selectTabViewItemAtIndex:index];
1098 WebsiteSettingsUIBridge::WebsiteSettingsUIBridge()
1099 : bubble_controller_(nil) {
1102 WebsiteSettingsUIBridge::~WebsiteSettingsUIBridge() {
1105 void WebsiteSettingsUIBridge::set_bubble_controller(
1106 WebsiteSettingsBubbleController* controller) {
1107 bubble_controller_ = controller;
1110 void WebsiteSettingsUIBridge::Show(gfx::NativeWindow parent,
1112 content::WebContents* web_contents,
1114 const content::SSLStatus& ssl) {
1115 bool is_internal_page = InternalChromePage(url);
1117 // Create the bridge. This will be owned by the bubble controller.
1118 WebsiteSettingsUIBridge* bridge = new WebsiteSettingsUIBridge();
1120 // Create the bubble controller. It will dealloc itself when it closes.
1121 WebsiteSettingsBubbleController* bubble_controller =
1122 [[WebsiteSettingsBubbleController alloc]
1123 initWithParentWindow:parent
1124 websiteSettingsUIBridge:bridge
1125 webContents:web_contents
1126 isInternalPage:is_internal_page];
1128 if (!is_internal_page) {
1129 // Initialize the presenter, which holds the model and controls the UI.
1130 // This is also owned by the bubble controller.
1131 WebsiteSettings* presenter = new WebsiteSettings(
1134 TabSpecificContentSettings::FromWebContents(web_contents),
1135 InfoBarService::FromWebContents(web_contents),
1138 content::CertStore::GetInstance());
1139 [bubble_controller setPresenter:presenter];
1142 [bubble_controller showWindow:nil];
1145 void WebsiteSettingsUIBridge::SetIdentityInfo(
1146 const WebsiteSettingsUI::IdentityInfo& identity_info) {
1147 [bubble_controller_ setIdentityInfo:identity_info];
1150 void WebsiteSettingsUIBridge::SetCookieInfo(
1151 const CookieInfoList& cookie_info_list) {
1152 [bubble_controller_ setCookieInfo:cookie_info_list];
1155 void WebsiteSettingsUIBridge::SetPermissionInfo(
1156 const PermissionInfoList& permission_info_list) {
1157 [bubble_controller_ setPermissionInfo:permission_info_list];
1160 void WebsiteSettingsUIBridge::SetFirstVisit(const base::string16& first_visit) {
1161 [bubble_controller_ setFirstVisit:first_visit];
1164 void WebsiteSettingsUIBridge::SetSelectedTab(TabId tab_id) {
1165 [bubble_controller_ setSelectedTab:tab_id];