- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / website_settings_bubble_controller.mm
1 // Copyright (c) 2012 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_bubble_controller.h"
6
7 #include <cmath>
8
9 #import <AppKit/AppKit.h>
10
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/sys_string_conversions.h"
13 #import "chrome/browser/certificate_viewer.h"
14 #include "chrome/browser/infobars/infobar_service.h"
15 #import "chrome/browser/ui/browser_dialogs.h"
16 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
17 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
18 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
19 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
20 #include "chrome/browser/ui/website_settings/website_settings_utils.h"
21 #include "chrome/common/url_constants.h"
22 #include "content/public/browser/cert_store.h"
23 #include "content/public/browser/page_navigator.h"
24 #include "content/public/browser/user_metrics.h"
25 #include "content/public/browser/web_contents.h"
26 #include "grit/chromium_strings.h"
27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h"
29 #include "grit/ui_resources.h"
30 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
31 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
32 #import "ui/base/cocoa/flipped_view.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
36
37 namespace {
38
39 // The default width of the window, in view coordinates. It may be larger to
40 // fit the content.
41 const CGFloat kDefaultWindowWidth = 310;
42
43 // Spacing in between sections.
44 const CGFloat kVerticalSpacing = 10;
45
46 // Padding between the window frame and content.
47 const CGFloat kFramePadding = 20;
48
49 // Padding between the window frame and content for the internal page bubble.
50 const CGFloat kInternalPageFramePadding = 10;
51
52 // Spacing between the headlines and description text on the Connection tab.
53 const CGFloat kConnectionHeadlineSpacing = 2;
54
55 // Spacing between images on the Connection tab and the text.
56 const CGFloat kConnectionImageSpacing = 10;
57
58 // Spacing between the image and text for internal pages.
59 const CGFloat kInternalPageImageSpacing = 10;
60
61 // Square size of the images on the Connections tab.
62 const CGFloat kConnectionImageSize = 30;
63
64 // Square size of the image that is shown for internal pages.
65 const CGFloat kInternalPageImageSize = 26;
66
67 // Square size of the permission images.
68 const CGFloat kPermissionImageSize = 19;
69
70 // Vertical adjustment for the permission images.
71 // They have an extra pixel of padding on the bottom edge.
72 const CGFloat kPermissionImageYAdjust = 1;
73
74 // Spacing between a permission image and the text.
75 const CGFloat kPermissionImageSpacing = 3;
76
77 // The spacing between individual items in the Permissions tab.
78 const CGFloat kPermissionsTabSpacing = 12;
79
80 // Extra spacing after a headline on the Permissions tab.
81 const CGFloat kPermissionsHeadlineSpacing = 2;
82
83 // The amount of horizontal space between a permission label and the popup.
84 const CGFloat kPermissionPopUpXSpacing = 3;
85
86 // The amount of horizontal space between the permission popup title and the
87 // arrow icon.
88 const CGFloat kPermissionButtonTitleRightPadding = 4;
89
90 // The extra space to the left of the first tab in the tab strip.
91 const CGFloat kTabStripXPadding = kFramePadding;
92
93 // The amount of space between the visual borders of adjacent tabs.
94 const CGFloat kTabSpacing = 4;
95
96 // The amount of space above the tab strip.
97 const CGFloat kTabStripTopSpacing = 14;
98
99 // The height of the clickable area of the tab.
100 const CGFloat kTabHeight = 28;
101
102 // The amount of space above tab labels.
103 const CGFloat kTabLabelTopPadding = 6;
104
105 // The amount of padding to leave on either side of the tab label.
106 const CGFloat kTabLabelXPadding = 12;
107
108 // Return the text color to use for the indentity status when the site's
109 // identity has been verified.
110 NSColor* IdentityVerifiedTextColor() {
111   // RGB components are specified using integer RGB [0-255] values for easy
112   // comparison to other platforms.
113   return [NSColor colorWithCalibratedRed:0x07/255.0
114                                    green:0x95/255.0
115                                     blue:0
116                                    alpha:1.0];
117 }
118
119 }  // namespace
120
121 @interface WebsiteSettingsTabSegmentedCell : NSSegmentedCell {
122  @private
123   base::scoped_nsobject<NSImage> tabstripCenterImage_;
124   base::scoped_nsobject<NSImage> tabstripLeftImage_;
125   base::scoped_nsobject<NSImage> tabstripRightImage_;
126
127   base::scoped_nsobject<NSImage> tabCenterImage_;
128   base::scoped_nsobject<NSImage> tabLeftImage_;
129   base::scoped_nsobject<NSImage> tabRightImage_;
130
131   // Key track of the index of segment which has keyboard focus. This is not
132   // the same as the currently selected segment.
133   NSInteger keySegment_;
134 }
135 @end
136
137 @implementation WebsiteSettingsTabSegmentedCell
138 - (id)init {
139   if ((self = [super init])) {
140     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
141     tabstripCenterImage_.reset(rb.GetNativeImageNamed(
142         IDR_WEBSITE_SETTINGS_TABSTRIP_CENTER).CopyNSImage());
143     tabstripLeftImage_.reset(rb.GetNativeImageNamed(
144         IDR_WEBSITE_SETTINGS_TABSTRIP_LEFT).CopyNSImage());
145     tabstripRightImage_.reset(rb.GetNativeImageNamed(
146         IDR_WEBSITE_SETTINGS_TABSTRIP_RIGHT).CopyNSImage());
147
148     tabCenterImage_.reset(
149         rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_CENTER2).CopyNSImage());
150     tabLeftImage_.reset(
151         rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_LEFT2).CopyNSImage());
152     tabRightImage_.reset(
153         rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_RIGHT2).CopyNSImage());
154   }
155   return self;
156 }
157
158 // Called when keyboard focus in the segmented control is moved forward.
159 - (void)makeNextSegmentKey {
160   [super makeNextSegmentKey];
161   keySegment_ = (keySegment_ + 1) % [self segmentCount];
162 }
163
164 // Called when keyboard focus in the segmented control is moved backwards.
165 - (void)makePreviousSegmentKey {
166   [super makePreviousSegmentKey];
167   if (--keySegment_ < 0)
168     keySegment_ += [self segmentCount];
169 }
170
171 - (void)setSelectedSegment:(NSInteger)selectedSegment {
172   keySegment_ = selectedSegment;
173   [super setSelectedSegment:selectedSegment];
174 }
175
176 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
177   CGFloat tabstripHeight = [tabCenterImage_ size].height;
178
179   // Draw the tab for the selected segment.
180   NSRect tabRect = [self hitRectForSegment:[self selectedSegment]];
181   tabRect.origin.y = 0;
182   tabRect.size.height = tabstripHeight;
183
184   NSDrawThreePartImage(tabRect,
185                        tabLeftImage_,
186                        tabCenterImage_,
187                        tabRightImage_,
188                        /*vertical=*/ NO,
189                        NSCompositeSourceOver,
190                        1,
191                        /*flipped=*/ YES);
192
193   // Draw the background to the left of the selected tab.
194   NSRect backgroundRect = NSMakeRect(0, 0, NSMinX(tabRect), tabstripHeight);
195   NSDrawThreePartImage(backgroundRect,
196                        nil,
197                        tabstripCenterImage_,
198                        tabstripLeftImage_,
199                        /*vertical=*/ NO,
200                        NSCompositeSourceOver,
201                        1,
202                        /*flipped=*/ YES);
203
204   // Draw the background to the right of the selected tab.
205   backgroundRect.origin.x = NSMaxX(tabRect);
206   backgroundRect.size.width = NSMaxX(cellFrame) - NSMaxX(tabRect);
207   NSDrawThreePartImage(backgroundRect,
208                        tabstripRightImage_,
209                        tabstripCenterImage_,
210                        nil,
211                        /*vertical=*/ NO,
212                        NSCompositeSourceOver,
213                        1,
214                        /*flipped=*/ YES);
215
216   // Call the superclass method to trigger drawing of the tab labels.
217   [self drawInteriorWithFrame:cellFrame inView:controlView];
218   if ([[controlView window] firstResponder] == controlView)
219     [self drawFocusRect];
220 }
221
222 - (void)drawFocusRect {
223   gfx::ScopedNSGraphicsContextSaveGState scoped_state;
224   NSSetFocusRingStyle(NSFocusRingOnly);
225   [[NSColor keyboardFocusIndicatorColor] set];
226   NSFrameRect([self hitRectForSegment:keySegment_]);
227 }
228
229 // Return the hit rect (i.e., the visual bounds of the tab) for
230 // the given segment.
231 - (NSRect)hitRectForSegment:(NSInteger)segment {
232   CGFloat tabstripHeight = [tabCenterImage_ size].height;
233   DCHECK_GT(tabstripHeight, kTabHeight);
234
235   NSRect rect = NSMakeRect(0, tabstripHeight - kTabHeight,
236                            [self widthForSegment:segment], kTabHeight);
237   for (NSInteger i = 0; i < segment; ++i) {
238     rect.origin.x += [self widthForSegment:i];
239   }
240   int xAdjust = segment == 0 ? kTabStripXPadding : 0;
241   rect.size.width = std::floor(
242       [self widthForSegment:segment] - kTabSpacing - xAdjust);
243   rect.origin.x = std::floor(rect.origin.x + kTabSpacing / 2 + xAdjust);
244
245   return rect;
246 }
247
248 - (void)drawSegment:(NSInteger)segment
249             inFrame:(NSRect)frame
250            withView:(NSView*)controlView {
251   // Call the superclass to draw the label, adjusting the rectangle so that
252   // the label appears centered in the tab.
253   if (segment == 0) {
254     frame.origin.x += kTabStripXPadding / 2;
255     frame.size.width -= kTabStripXPadding;
256   }
257   frame.origin.y += kTabLabelTopPadding;
258   frame.size.height -= kTabLabelTopPadding;
259   [super drawSegment:segment inFrame:frame withView:controlView];
260 }
261
262 // Overrides the default tracking behavior to only respond to clicks inside the
263 // visual borders of the tab.
264 - (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
265   NSInteger segmentCount = [self segmentCount];
266   for (NSInteger i = 0; i < segmentCount; ++i) {
267     if (NSPointInRect(startPoint, [self hitRectForSegment:i]))
268       return YES;
269   }
270   return NO;
271 }
272
273 // Overrides the default cell height to take up the full height of the
274 // segmented control. Otherwise, clicks on the lower part of a tab will be
275 // ignored.
276 - (NSSize)cellSizeForBounds:(NSRect)aRect {
277   return NSMakeSize([super cellSizeForBounds:aRect].width,
278                     [tabstripCenterImage_ size].height);
279 }
280
281 // Returns the minimum size required to display this cell.
282 // It should always be exactly as tall as the tabstrip background image.
283 - (NSSize)cellSize {
284   return NSMakeSize([super cellSize].width,
285                     [tabstripCenterImage_ size].height);
286 }
287 @end
288
289 @implementation WebsiteSettingsBubbleController
290
291 - (CGFloat)defaultWindowWidth {
292   return kDefaultWindowWidth;
293 }
294
295 - (id)initWithParentWindow:(NSWindow*)parentWindow
296    websiteSettingsUIBridge:(WebsiteSettingsUIBridge*)bridge
297                webContents:(content::WebContents*)webContents
298             isInternalPage:(BOOL)isInternalPage {
299   DCHECK(parentWindow);
300
301   webContents_ = webContents;
302
303   // Use an arbitrary height; it will be changed in performLayout.
304   NSRect contentRect = NSMakeRect(0, 0, [self defaultWindowWidth], 1);
305   // Create an empty window into which content is placed.
306   base::scoped_nsobject<InfoBubbleWindow> window(
307       [[InfoBubbleWindow alloc] initWithContentRect:contentRect
308                                           styleMask:NSBorderlessWindowMask
309                                             backing:NSBackingStoreBuffered
310                                               defer:NO]);
311
312   if ((self = [super initWithWindow:window.get()
313                        parentWindow:parentWindow
314                          anchoredAt:NSZeroPoint])) {
315     [[self bubble] setArrowLocation:info_bubble::kTopLeft];
316
317     // Create the container view that uses flipped coordinates.
318     NSRect contentFrame = NSMakeRect(0, 0, [self defaultWindowWidth], 300);
319     contentView_.reset(
320         [[FlippedView alloc] initWithFrame:contentFrame]);
321
322     // Replace the window's content.
323     [[[self window] contentView] setSubviews:
324         [NSArray arrayWithObject:contentView_.get()]];
325
326     if (isInternalPage)
327       [self initializeContentsForInternalPage];
328     else
329       [self initializeContents];
330
331     bridge_.reset(bridge);
332     bridge_->set_bubble_controller(self);
333   }
334   return self;
335 }
336
337 - (void)windowWillClose:(NSNotification*)notification {
338   if (presenter_.get())
339     presenter_->OnUIClosing();
340   [super windowWillClose:notification];
341 }
342
343 - (void)setPresenter:(WebsiteSettings*)presenter {
344   presenter_.reset(presenter);
345 }
346
347 // Create the subviews for the bubble for internal Chrome pages.
348 - (void)initializeContentsForInternalPage {
349   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
350
351   NSPoint controlOrigin = NSMakePoint(
352       kInternalPageFramePadding,
353       kInternalPageFramePadding + info_bubble::kBubbleArrowHeight);
354   NSSize imageSize = NSMakeSize(kInternalPageImageSize,
355                                 kInternalPageImageSize);
356   NSImageView* imageView = [self addImageWithSize:imageSize
357                                            toView:contentView_
358                                           atPoint:controlOrigin];
359   [imageView setImage:rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_26).ToNSImage()];
360
361   controlOrigin.x += NSWidth([imageView frame]) + kInternalPageImageSpacing;
362   string16 text = l10n_util::GetStringUTF16(IDS_PAGE_INFO_INTERNAL_PAGE);
363   NSTextField* textField = [self addText:text
364                                 withSize:[NSFont smallSystemFontSize]
365                                     bold:NO
366                                   toView:contentView_
367                                  atPoint:controlOrigin];
368   // Center the text vertically with the image.
369   NSRect textFrame = [textField frame];
370   textFrame.origin.y += (imageSize.height - NSHeight(textFrame)) / 2;
371   [textField setFrame:textFrame];
372
373   // Adjust the contentView to fit everything.
374   CGFloat maxY = std::max(NSMaxY([imageView frame]), NSMaxY(textFrame));
375   [contentView_ setFrame:NSMakeRect(
376       0, 0, [self defaultWindowWidth], maxY + kInternalPageFramePadding)];
377
378   [self sizeAndPositionWindow];
379 }
380
381 // Create the subviews for the website settings bubble.
382 - (void)initializeContents {
383   // Keeps track of the position that the next control should be drawn at.
384   NSPoint controlOrigin = NSMakePoint(
385       kFramePadding,
386       kFramePadding + info_bubble::kBubbleArrowHeight);
387
388   // Create a text field (empty for now) to show the site identity.
389   identityField_ = [self addText:string16()
390                         withSize:[NSFont systemFontSize]
391                             bold:YES
392                           toView:contentView_
393                          atPoint:controlOrigin];
394   controlOrigin.y +=
395       NSHeight([identityField_ frame]) + kConnectionHeadlineSpacing;
396
397   // Create a text field to identity status (e.g. verified, not verified).
398   identityStatusField_ = [self addText:string16()
399                               withSize:[NSFont smallSystemFontSize]
400                                   bold:NO
401                                 toView:contentView_
402                                atPoint:controlOrigin];
403
404   // Create the tab view and its two tabs.
405
406   base::scoped_nsobject<WebsiteSettingsTabSegmentedCell> cell(
407       [[WebsiteSettingsTabSegmentedCell alloc] init]);
408   CGFloat tabstripHeight = [cell cellSize].height;
409   NSRect tabstripFrame = NSMakeRect(
410       0, 0, [self defaultWindowWidth], tabstripHeight);
411   segmentedControl_.reset(
412       [[NSSegmentedControl alloc] initWithFrame:tabstripFrame]);
413   [segmentedControl_ setCell:cell];
414   [segmentedControl_ setSegmentCount:WebsiteSettingsUI::NUM_TAB_IDS];
415   [segmentedControl_ setTarget:self];
416   [segmentedControl_ setAction:@selector(tabSelected:)];
417   [segmentedControl_ setAutoresizingMask:NSViewWidthSizable];
418
419   NSFont* smallSystemFont =
420       [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
421   NSDictionary* textAttributes =
422       [NSDictionary dictionaryWithObject:smallSystemFont
423                                   forKey:NSFontAttributeName];
424
425   // Create the "Permissions" tab.
426   NSString* label = l10n_util::GetNSString(
427       IDS_WEBSITE_SETTINGS_TAB_LABEL_PERMISSIONS);
428   [segmentedControl_ setLabel:label
429                    forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
430   NSSize textSize = [label sizeWithAttributes:textAttributes];
431   CGFloat tabWidth = textSize.width + 2 * kTabLabelXPadding;
432   [segmentedControl_ setWidth:tabWidth + kTabStripXPadding
433                    forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
434
435   // Create the "Connection" tab.
436   label = l10n_util::GetNSString(IDS_WEBSITE_SETTINGS_TAB_LABEL_CONNECTION);
437   textSize = [label sizeWithAttributes:textAttributes];
438   [segmentedControl_ setLabel:label
439                    forSegment:WebsiteSettingsUI::TAB_ID_CONNECTION];
440
441   DCHECK_EQ([segmentedControl_ segmentCount], WebsiteSettingsUI::NUM_TAB_IDS);
442
443   // Make both tabs the width of the widest. The first segment has some
444   // additional padding that is not part of the tab, which is used for drawing
445   // the background of the tab strip.
446   tabWidth = std::max(tabWidth,
447                       textSize.width + 2 * kTabLabelXPadding);
448   [segmentedControl_ setWidth:tabWidth + kTabStripXPadding
449                    forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
450   [segmentedControl_ setWidth:tabWidth
451                    forSegment:WebsiteSettingsUI::TAB_ID_CONNECTION];
452
453   [segmentedControl_ setFont:smallSystemFont];
454   [contentView_ addSubview:segmentedControl_];
455
456   NSRect tabFrame = NSMakeRect(0, 0, [self defaultWindowWidth], 300);
457   tabView_.reset([[NSTabView alloc] initWithFrame:tabFrame]);
458   [tabView_ setTabViewType:NSNoTabsNoBorder];
459   [tabView_ setDrawsBackground:NO];
460   [tabView_ setControlSize:NSSmallControlSize];
461   [contentView_ addSubview:tabView_.get()];
462
463   permissionsTabContentView_ = [self addPermissionsTabToTabView:tabView_];
464   connectionTabContentView_ = [self addConnectionTabToTabView:tabView_];
465   [self setSelectedTab:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
466
467   [self performLayout];
468 }
469
470 // Create the contents of the Permissions tab and add it to the given tab view.
471 // Returns a weak reference to the tab view item's view.
472 - (NSView*)addPermissionsTabToTabView:(NSTabView*)tabView {
473   base::scoped_nsobject<NSTabViewItem> item([[NSTabViewItem alloc] init]);
474   [tabView_ insertTabViewItem:item.get()
475                       atIndex:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
476   base::scoped_nsobject<NSView> contentView(
477       [[FlippedView alloc] initWithFrame:[tabView_ contentRect]]);
478   [contentView setAutoresizingMask:NSViewWidthSizable];
479   [item setView:contentView.get()];
480
481   // Initialize the two containers that hold the controls. The initial frames
482   // are arbitrary, and will be adjusted after the controls are laid out.
483   cookiesView_ = [[[FlippedView alloc]
484       initWithFrame:[tabView_ contentRect]] autorelease];
485   [cookiesView_ setAutoresizingMask:NSViewWidthSizable];
486
487   permissionsView_ = [[[FlippedView alloc]
488       initWithFrame:[tabView_ contentRect]] autorelease];
489
490   [contentView addSubview:cookiesView_];
491   [contentView addSubview:permissionsView_];
492
493   // Create the link button to view cookies and site data.
494   // Its position will be set in performLayout.
495   NSString* cookieButtonText = l10n_util::GetNSString(
496       IDS_WEBSITE_SETTINGS_SHOW_SITE_DATA);
497   cookiesButton_ = [self addLinkButtonWithText:cookieButtonText
498                                         toView:contentView];
499   [cookiesButton_ setTarget:self];
500   [cookiesButton_ setAction:@selector(showCookiesAndSiteData:)];
501
502   return contentView.get();
503 }
504
505 // Handler for the link button below the list of cookies.
506 - (void)showCookiesAndSiteData:(id)sender {
507   DCHECK(webContents_);
508   content::RecordAction(
509       content::UserMetricsAction("WebsiteSettings_CookiesDialogOpened"));
510   chrome::ShowCollectedCookiesDialog(webContents_);
511 }
512
513 // Handler for the link button to show certificate information.
514 - (void)showCertificateInfo:(id)sender {
515   DCHECK(certificateId_);
516   ShowCertificateViewerByID(webContents_, [self parentWindow], certificateId_);
517 }
518
519 // Handler for the link to show help information about the connection tab.
520 - (void)showHelpPage:(id)sender {
521   webContents_->OpenURL(content::OpenURLParams(
522       GURL(chrome::kPageInfoHelpCenterURL), content::Referrer(),
523       NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, false));
524 }
525
526 // Create the contents of the Connection tab and add it to the given tab view.
527 // Returns a weak reference to the tab view item's view.
528 - (NSView*)addConnectionTabToTabView:(NSTabView*)tabView {
529   base::scoped_nsobject<NSTabViewItem> item([[NSTabViewItem alloc] init]);
530   base::scoped_nsobject<NSView> contentView(
531       [[FlippedView alloc] initWithFrame:[tabView_ contentRect]]);
532   [contentView setAutoresizingMask:NSViewWidthSizable];
533
534   // Place all the text and images at the same position. The positions will be
535   // adjusted in performLayout.
536   NSPoint textPosition = NSMakePoint(
537       kFramePadding + kConnectionImageSize + kConnectionImageSpacing,
538       kFramePadding);
539   NSPoint imagePosition = NSMakePoint(kFramePadding, kFramePadding);
540   NSSize imageSize = NSMakeSize(kConnectionImageSize, kConnectionImageSize);
541
542   identityStatusIcon_ = [self addImageWithSize:imageSize
543                                         toView:contentView
544                                        atPoint:imagePosition];
545   identityStatusDescriptionField_ =
546       [self addText:string16()
547            withSize:[NSFont smallSystemFontSize]
548                bold:NO
549              toView:contentView.get()
550             atPoint:textPosition];
551
552   separatorAfterIdentity_ = [self addSeparatorToView:contentView];
553   [separatorAfterIdentity_ setAutoresizingMask:NSViewWidthSizable];
554
555   connectionStatusIcon_ = [self addImageWithSize:imageSize
556                                           toView:contentView
557                                          atPoint:imagePosition];
558   connectionStatusDescriptionField_ =
559       [self addText:string16()
560            withSize:[NSFont smallSystemFontSize]
561                bold:NO
562              toView:contentView.get()
563             atPoint:textPosition];
564   certificateInfoButton_ = nil;  // This will be created only if necessary.
565   separatorAfterConnection_ = [self addSeparatorToView:contentView];
566   [separatorAfterConnection_ setAutoresizingMask:NSViewWidthSizable];
567
568   firstVisitIcon_ = [self addImageWithSize:imageSize
569                                     toView:contentView
570                                    atPoint:imagePosition];
571   firstVisitHeaderField_ =
572       [self addText:l10n_util::GetStringUTF16(IDS_PAGE_INFO_SITE_INFO_TITLE)
573            withSize:[NSFont smallSystemFontSize]
574                bold:YES
575              toView:contentView.get()
576             atPoint:textPosition];
577   firstVisitDescriptionField_ =
578       [self addText:string16()
579            withSize:[NSFont smallSystemFontSize]
580                bold:NO
581              toView:contentView.get()
582             atPoint:textPosition];
583
584   separatorAfterFirstVisit_ = [self addSeparatorToView:contentView];
585   [separatorAfterFirstVisit_ setAutoresizingMask:NSViewWidthSizable];
586
587   NSString* helpButtonText = l10n_util::GetNSString(
588       IDS_PAGE_INFO_HELP_CENTER_LINK);
589   helpButton_ = [self addLinkButtonWithText:helpButtonText
590                                      toView:contentView];
591   [helpButton_ setTarget:self];
592   [helpButton_ setAction:@selector(showHelpPage:)];
593
594   [item setView:contentView.get()];
595   [tabView_ insertTabViewItem:item.get()
596                       atIndex:WebsiteSettingsUI::TAB_ID_CONNECTION];
597
598   return contentView.get();
599 }
600
601 // Set the Y position of |view| to the given position, and return the position
602 // of its bottom edge.
603 - (CGFloat)setYPositionOfView:(NSView*)view to:(CGFloat)position {
604   NSRect frame = [view frame];
605   frame.origin.y = position;
606   [view setFrame:frame];
607   return position + NSHeight(frame);
608 }
609
610 - (void)setWidthOfView:(NSView*)view to:(CGFloat)width {
611   [view setFrameSize:NSMakeSize(width, NSHeight([view frame]))];
612 }
613
614 // Layout all of the controls in the window. This should be called whenever
615 // the content has changed.
616 - (void)performLayout {
617   // Make the content at least as wide as the permissions view.
618   CGFloat contentWidth = std::max([self defaultWindowWidth],
619                                   NSWidth([permissionsView_ frame]));
620
621   // Set the width of the content view now, so that all the text fields will
622   // be sized to fit before their heights and vertical positions are adjusted.
623   // The tab view will only resize the currently selected tab, so resize both
624   // tab content views manually.
625   [self setWidthOfView:contentView_ to:contentWidth];
626   [self setWidthOfView:permissionsTabContentView_ to:contentWidth];
627   [self setWidthOfView:connectionTabContentView_ to:contentWidth];
628
629   // Place the identity status immediately below the identity.
630   [self sizeTextFieldHeightToFit:identityField_];
631   [self sizeTextFieldHeightToFit:identityStatusField_];
632   CGFloat yPos = NSMaxY([identityField_ frame]) + kConnectionHeadlineSpacing;
633   yPos = [self setYPositionOfView:identityStatusField_ to:yPos];
634
635   // Lay out the Permissions tab.
636
637   yPos = [self setYPositionOfView:cookiesView_ to:kFramePadding];
638
639   // Put the link button for cookies and site data just below the cookie info.
640   NSRect cookiesButtonFrame = [cookiesButton_ frame];
641   cookiesButtonFrame.origin.y = yPos;
642   cookiesButtonFrame.origin.x = kFramePadding;
643   [cookiesButton_ setFrame:cookiesButtonFrame];
644
645   // Put the permission info just below the link button.
646   [self setYPositionOfView:permissionsView_
647                         to:NSMaxY(cookiesButtonFrame) + kFramePadding];
648
649   // Lay out the Connection tab.
650
651   // Lay out the identity status section.
652   [self sizeTextFieldHeightToFit:identityStatusDescriptionField_];
653   yPos = std::max(NSMaxY([identityStatusDescriptionField_ frame]),
654                   NSMaxY([identityStatusIcon_ frame]));
655   if (certificateInfoButton_) {
656     NSRect certificateButtonFrame = [certificateInfoButton_ frame];
657     certificateButtonFrame.origin.x = NSMinX(
658         [identityStatusDescriptionField_ frame]);
659     certificateButtonFrame.origin.y = yPos + kVerticalSpacing;
660     [certificateInfoButton_ setFrame:certificateButtonFrame];
661     yPos = NSMaxY(certificateButtonFrame);
662   }
663   yPos = [self setYPositionOfView:separatorAfterIdentity_
664                                to:yPos + kVerticalSpacing];
665   yPos += kVerticalSpacing;
666
667   // Lay out the connection status section.
668   [self sizeTextFieldHeightToFit:connectionStatusDescriptionField_];
669   [self setYPositionOfView:connectionStatusIcon_ to:yPos];
670   [self setYPositionOfView:connectionStatusDescriptionField_ to:yPos];
671   yPos = std::max(NSMaxY([connectionStatusDescriptionField_ frame]),
672                   NSMaxY([connectionStatusIcon_ frame]));
673   yPos = [self setYPositionOfView:separatorAfterConnection_
674                                to:yPos + kVerticalSpacing];
675   yPos += kVerticalSpacing;
676
677   // Lay out the last visit section.
678   [self setYPositionOfView:firstVisitIcon_ to:yPos];
679   [self sizeTextFieldHeightToFit:firstVisitHeaderField_];
680   yPos = [self setYPositionOfView:firstVisitHeaderField_ to:yPos];
681   yPos += kConnectionHeadlineSpacing;
682   [self sizeTextFieldHeightToFit:firstVisitDescriptionField_];
683   yPos = [self setYPositionOfView:firstVisitDescriptionField_ to:yPos];
684   yPos = [self setYPositionOfView:separatorAfterFirstVisit_
685                                to:yPos + kVerticalSpacing];
686   yPos += kVerticalSpacing;
687   [self setYPositionOfView:helpButton_ to:yPos];
688
689   // Adjust the tab view size and place it below the identity status.
690
691   yPos = NSMaxY([identityStatusField_ frame]) + kTabStripTopSpacing;
692   yPos = [self setYPositionOfView:segmentedControl_ to:yPos];
693
694   CGFloat connectionTabHeight = NSMaxY([helpButton_ frame]) + kVerticalSpacing;
695
696   NSRect tabViewFrame = [tabView_ frame];
697   tabViewFrame.origin.y = yPos;
698   tabViewFrame.size.height =
699       std::max(connectionTabHeight, NSMaxY([permissionsView_ frame]));
700   tabViewFrame.size.width = contentWidth;
701   [tabView_ setFrame:tabViewFrame];
702
703   // Adjust the contentView to fit everything.
704   [contentView_ setFrame:NSMakeRect(
705       0, 0, NSWidth(tabViewFrame), NSMaxY(tabViewFrame))];
706
707   [self sizeAndPositionWindow];
708 }
709
710 // Adjust the size of the window to match the size of the content, and position
711 // the bubble anchor appropriately.
712 - (void)sizeAndPositionWindow {
713   NSRect windowFrame = [contentView_ frame];
714   windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size
715                                                        toView:nil];
716   // Adjust the origin by the difference in height.
717   windowFrame.origin = [[self window] frame].origin;
718   windowFrame.origin.y -= NSHeight(windowFrame) -
719       NSHeight([[self window] frame]);
720
721   // Resize the window. Only animate if the window is visible, otherwise it
722   // could be "growing" while it's opening, looking awkward.
723   [[self window] setFrame:windowFrame
724                   display:YES
725                   animate:[[self window] isVisible]];
726
727   // Adjust the anchor for the bubble.
728   NSPoint anchorPoint =
729       [self anchorPointForWindowWithHeight:NSHeight(windowFrame)
730                               parentWindow:[self parentWindow]];
731   [self setAnchorPoint:anchorPoint];
732 }
733
734 // Takes in the bubble's height and the parent window, which should be a
735 // BrowserWindow, and gets the proper anchor point for the bubble. The returned
736 // point is in screen coordinates.
737 - (NSPoint)anchorPointForWindowWithHeight:(CGFloat)bubbleHeight
738                              parentWindow:(NSWindow*)parent {
739   BrowserWindowController* controller = [parent windowController];
740   NSPoint origin = NSZeroPoint;
741   if ([controller isKindOfClass:[BrowserWindowController class]]) {
742     LocationBarViewMac* locationBar = [controller locationBarBridge];
743     if (locationBar) {
744       NSPoint bubblePoint = locationBar->GetPageInfoBubblePoint();
745       origin = [parent convertBaseToScreen:bubblePoint];
746     }
747   }
748   return origin;
749 }
750
751 // Sets properties on the given |field| to act as the title or description
752 // labels in the bubble.
753 - (void)configureTextFieldAsLabel:(NSTextField*)textField {
754   [textField setEditable:NO];
755   [textField setSelectable:YES];
756   [textField setDrawsBackground:NO];
757   [textField setBezeled:NO];
758 }
759
760 // Adjust the height of the given text field to match its text.
761 - (void)sizeTextFieldHeightToFit:(NSTextField*)textField {
762   NSRect frame = [textField frame];
763   frame.size.height +=
764      [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:
765          textField];
766   [textField setFrame:frame];
767 }
768
769 // Create a new text field and add it to the given array of subviews.
770 // The array will retain a reference to the object.
771 - (NSTextField*)addText:(const string16&)text
772                withSize:(CGFloat)fontSize
773                    bold:(BOOL)bold
774                  toView:(NSView*)view
775                 atPoint:(NSPoint)point {
776   // Size the text to take up the full available width, with some padding.
777   // The height is arbitrary as it will be adjusted later.
778   CGFloat width = NSWidth([view frame]) - point.x - kFramePadding;
779   NSRect frame = NSMakeRect(point.x, point.y, width, 100);
780   base::scoped_nsobject<NSTextField> textField(
781       [[NSTextField alloc] initWithFrame:frame]);
782   [self configureTextFieldAsLabel:textField.get()];
783   [textField setStringValue:base::SysUTF16ToNSString(text)];
784   NSFont* font = bold ? [NSFont boldSystemFontOfSize:fontSize]
785                       : [NSFont systemFontOfSize:fontSize];
786   [textField setFont:font];
787   [self sizeTextFieldHeightToFit:textField];
788   [textField setAutoresizingMask:NSViewWidthSizable];
789   [view addSubview:textField.get()];
790   return textField.get();
791 }
792
793 // Add an image as a subview of the given view, placed at a pre-determined x
794 // position and the given y position. Return the new NSImageView.
795 - (NSImageView*)addImageWithSize:(NSSize)size
796                           toView:(NSView*)view
797                          atPoint:(NSPoint)point {
798   NSRect frame = NSMakeRect(point.x, point.y, size.width, size.height);
799   base::scoped_nsobject<NSImageView> imageView(
800       [[NSImageView alloc] initWithFrame:frame]);
801   [imageView setImageFrameStyle:NSImageFrameNone];
802   [view addSubview:imageView.get()];
803   return imageView.get();
804 }
805
806 // Add a separator as a subview of the given view. Return the new view.
807 - (NSView*)addSeparatorToView:(NSView*)view {
808   // Take up almost the full width of the container's frame.
809   CGFloat width = NSWidth([view frame]) - 2 * kFramePadding;
810
811   // Use an arbitrary position; it will be adjusted in performLayout.
812   NSBox* spacer =
813       [self separatorWithFrame:NSMakeRect(kFramePadding, 0, width, 0)];
814   [view addSubview:spacer];
815   return spacer;
816 }
817
818 // Add a link button with the given text to |view|.
819 - (NSButton*)addLinkButtonWithText:(NSString*)text toView:(NSView*)view {
820   // Frame size is arbitrary; it will be adjusted by the layout tweaker.
821   NSRect frame = NSMakeRect(kFramePadding, 0, 100, 10);
822   base::scoped_nsobject<NSButton> button(
823       [[NSButton alloc] initWithFrame:frame]);
824   base::scoped_nsobject<HyperlinkButtonCell> cell(
825       [[HyperlinkButtonCell alloc] initTextCell:text]);
826   [cell setControlSize:NSSmallControlSize];
827   [button setCell:cell.get()];
828   [button setButtonType:NSMomentaryPushInButton];
829   [button setBezelStyle:NSRegularSquareBezelStyle];
830   [view addSubview:button.get()];
831
832   [GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()];
833   return button.get();
834 }
835
836 // Determine the size of a popup button with the given title.
837 - (NSSize)sizeForPopUpButton:(NSPopUpButton*)button
838                    withTitle:(NSString*)title {
839   NSDictionary* textAttributes =
840       [NSDictionary dictionaryWithObject:[button font]
841                                   forKey:NSFontAttributeName];
842   NSSize titleSize = [title sizeWithAttributes:textAttributes];
843
844   NSRect buttonFrame = [button frame];
845   NSRect titleRect = [[button cell] titleRectForBounds:buttonFrame];
846   CGFloat width = titleSize.width + NSWidth(buttonFrame) - NSWidth(titleRect);
847
848   return NSMakeSize(width + kPermissionButtonTitleRightPadding,
849                     NSHeight(buttonFrame));
850 }
851
852 // Add a pop-up button for |permissionInfo| to the given view.
853 - (NSPopUpButton*)addPopUpButtonForPermission:
854     (const WebsiteSettingsUI::PermissionInfo&)permissionInfo
855                                        toView:(NSView*)view
856                                       atPoint:(NSPoint)point {
857   // Use an arbitrary width and height; it will be sized to fit.
858   NSRect frame = NSMakeRect(point.x, point.y, 1, 1);
859   base::scoped_nsobject<NSPopUpButton> button(
860       [[NSPopUpButton alloc] initWithFrame:frame pullsDown:NO]);
861   [button setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
862   [button setBordered:NO];
863   [[button cell] setControlSize:NSSmallControlSize];
864   [button setTag:permissionInfo.type];
865   [button setAction:@selector(permissionValueChanged:)];
866   [button setTarget:self];
867
868   // Create the popup menu.
869   // TODO(dubroy): Refactor this code to use PermissionMenuModel.
870
871   // Media stream permission only support "Always allow" for https.
872   if (permissionInfo.type != CONTENT_SETTINGS_TYPE_MEDIASTREAM ||
873       (webContents_ && webContents_->GetURL().SchemeIsSecure())) {
874     [button addItemWithTitle:
875         l10n_util::GetNSString(IDS_WEBSITE_SETTINGS_MENU_ITEM_ALLOW)];
876     [[button lastItem] setTag:CONTENT_SETTING_ALLOW];
877   }
878
879   // Fullscreen does not support "Always block".
880   if (permissionInfo.type != CONTENT_SETTINGS_TYPE_FULLSCREEN) {
881     [button addItemWithTitle:
882         l10n_util::GetNSString(IDS_WEBSITE_SETTINGS_MENU_ITEM_BLOCK)];
883     [[button lastItem] setTag:CONTENT_SETTING_BLOCK];
884   }
885
886   [button addItemWithTitle:l10n_util::GetNSStringF(
887       IDS_WEBSITE_SETTINGS_DEFAULT_PERMISSION_LABEL,
888       WebsiteSettingsUI::PermissionValueToUIString(
889           permissionInfo.default_setting))];
890   [[button lastItem] setTag:CONTENT_SETTING_DEFAULT];
891
892   [button selectItemWithTag:permissionInfo.setting];
893
894   // Set the button title.
895   base::scoped_nsobject<NSMenuItem> titleItem([[NSMenuItem alloc] init]);
896   string16 buttonTitle = WebsiteSettingsUI::PermissionActionToUIString(
897       permissionInfo.setting,
898       permissionInfo.default_setting,
899       permissionInfo.source);
900   [titleItem setTitle:base::SysUTF16ToNSString(buttonTitle)];
901   [[button cell] setUsesItemFromMenu:NO];
902   [[button cell] setMenuItem:titleItem.get()];
903   [button sizeToFit];
904
905   // Determine the largest possible size for this button.
906   CGFloat maxTitleWidth = 0;
907   for (NSInteger i = 0; i < [button numberOfItems]; ++i) {
908     string16 title = WebsiteSettingsUI::PermissionActionToUIString(
909         static_cast<ContentSetting>([[button itemAtIndex:i] tag]),
910         permissionInfo.default_setting,
911         content_settings::SETTING_SOURCE_USER);
912     NSSize size = [self sizeForPopUpButton:button
913                                  withTitle:base::SysUTF16ToNSString(title)];
914     maxTitleWidth = std::max(maxTitleWidth, size.width);
915   }
916   // Ensure the containing view is large enough to contain the button with its
917   // widest possible title.
918   NSRect containerFrame = [view frame];
919   containerFrame.size.width = std::max(
920       NSWidth(containerFrame), point.x + maxTitleWidth + kFramePadding);
921   [view setFrame:containerFrame];
922
923   // Size the button to just fit the title.
924   [button setFrameSize:[self sizeForPopUpButton:button
925                                       withTitle:[button title]]];
926
927   [view addSubview:button.get()];
928   return button.get();
929 }
930
931 // Called when the user changes the selected segment in the segmented control.
932 - (void)tabSelected:(id)sender {
933   [tabView_ selectTabViewItemAtIndex:[segmentedControl_ selectedSegment]];
934 }
935
936 // Handler for the permission-changing menus.
937 - (void)permissionValueChanged:(id)sender {
938   DCHECK([sender isKindOfClass:[NSPopUpButton class]]);
939   NSPopUpButton* button = static_cast<NSPopUpButton*>(sender);
940   ContentSettingsType type = static_cast<ContentSettingsType>([button tag]);
941   ContentSetting newSetting = static_cast<ContentSetting>(
942       [[button selectedItem] tag]);
943   if (presenter_.get())
944     presenter_->OnSitePermissionChanged(type, newSetting);
945 }
946
947 // Adds a new row to the UI listing the permissions. Returns the amount of
948 // vertical space that was taken up by the row.
949 - (NSPoint)addPermission:
950     (const WebsiteSettingsUI::PermissionInfo&)permissionInfo
951                   toView:(NSView*)view
952                  atPoint:(NSPoint)point {
953   // TODO(dubroy): Remove this check by refactoring GetPermissionIcon to take
954   // the PermissionInfo object as its argument.
955   ContentSetting setting = permissionInfo.setting == CONTENT_SETTING_DEFAULT ?
956       permissionInfo.default_setting : permissionInfo.setting;
957   NSImage* image = WebsiteSettingsUI::GetPermissionIcon(
958       permissionInfo.type, setting).ToNSImage();
959   NSImageView* imageView = [self addImageWithSize:[image size]
960                                            toView:view
961                                           atPoint:point];
962   [imageView setImage:image];
963   point.x += kPermissionImageSize + kPermissionImageSpacing;
964
965   string16 labelText =
966       WebsiteSettingsUI::PermissionTypeToUIString(permissionInfo.type) +
967       ASCIIToUTF16(":");
968
969   NSTextField* label = [self addText:labelText
970                             withSize:[NSFont smallSystemFontSize]
971                                 bold:NO
972                               toView:view
973                              atPoint:point];
974   [label sizeToFit];
975   NSPoint popUpPosition = NSMakePoint(NSMaxX([label frame]), point.y);
976   NSPopUpButton* button = [self addPopUpButtonForPermission:permissionInfo
977                                                      toView:view
978                                                     atPoint:popUpPosition];
979
980   // Adjust the vertical position of the button so that its title text is
981   // aligned with the label. Assumes that the text is the same size in both.
982   // Also adjust the horizontal position to remove excess space due to the
983   // invisible bezel.
984   NSRect titleRect = [[button cell] titleRectForBounds:[button bounds]];
985   popUpPosition.x -= titleRect.origin.x - kPermissionPopUpXSpacing;
986   popUpPosition.y -= titleRect.origin.y;
987   [button setFrameOrigin:popUpPosition];
988
989   // Align the icon with the text.
990   [self alignPermissionIcon:imageView withTextField:label];
991
992   // Permissions specified by policy or an extension cannot be changed.
993   if (permissionInfo.source == content_settings::SETTING_SOURCE_EXTENSION ||
994       permissionInfo.source == content_settings::SETTING_SOURCE_POLICY) {
995     [button setEnabled:NO];
996   }
997
998   NSRect buttonFrame = [button frame];
999   return NSMakePoint(NSMaxX(buttonFrame), NSMaxY(buttonFrame));
1000 }
1001
1002 // Align an image with a text field by vertically centering the image on
1003 // the cap height of the first line of text.
1004 - (void)alignPermissionIcon:(NSImageView*)imageView
1005               withTextField:(NSTextField*)textField {
1006   NSFont* font = [textField font];
1007
1008   // Calculate the offset from the top of the text field.
1009   CGFloat capHeight = [font capHeight];
1010   CGFloat offset = (kPermissionImageSize - capHeight) / 2 -
1011       ([font ascender] - capHeight) - kPermissionImageYAdjust;
1012
1013   NSRect frame = [imageView frame];
1014   frame.origin.y -= offset;
1015   [imageView setFrame:frame];
1016 }
1017
1018 - (CGFloat)addCookieInfo:
1019     (const WebsiteSettingsUI::CookieInfo&)cookieInfo
1020                   toView:(NSView*)view
1021                  atPoint:(NSPoint)point {
1022   NSImage* image = WebsiteSettingsUI::GetPermissionIcon(
1023       CONTENT_SETTINGS_TYPE_COOKIES, CONTENT_SETTING_ALLOW).ToNSImage();
1024   NSImageView* imageView = [self addImageWithSize:[image size]
1025                                            toView:view
1026                                           atPoint:point];
1027   [imageView setImage:image];
1028   point.x += kPermissionImageSize + kPermissionImageSpacing;
1029
1030   string16 labelText = l10n_util::GetStringFUTF16(
1031       IDS_WEBSITE_SETTINGS_SITE_DATA_STATS_LINE,
1032       UTF8ToUTF16(cookieInfo.cookie_source),
1033       base::IntToString16(cookieInfo.allowed),
1034       base::IntToString16(cookieInfo.blocked));
1035
1036   NSTextField* label = [self addText:labelText
1037                             withSize:[NSFont smallSystemFontSize]
1038                                 bold:NO
1039                               toView:view
1040                              atPoint:point];
1041
1042   // Align the icon with the text.
1043   [self alignPermissionIcon:imageView withTextField:label];
1044
1045   return NSHeight([label frame]);
1046 }
1047
1048 // Set the content of the identity and identity status fields.
1049 - (void)setIdentityInfo:(const WebsiteSettingsUI::IdentityInfo&)identityInfo {
1050   [identityField_ setStringValue:
1051       base::SysUTF8ToNSString(identityInfo.site_identity)];
1052   [identityStatusField_ setStringValue:
1053       base::SysUTF16ToNSString(identityInfo.GetIdentityStatusText())];
1054
1055   WebsiteSettings::SiteIdentityStatus status = identityInfo.identity_status;
1056   if (status == WebsiteSettings::SITE_IDENTITY_STATUS_CERT ||
1057       status == WebsiteSettings::SITE_IDENTITY_STATUS_EV_CERT) {
1058     [identityStatusField_ setTextColor:IdentityVerifiedTextColor()];
1059   }
1060   // If there is a certificate, add a button for viewing the certificate info.
1061   certificateId_ = identityInfo.cert_id;
1062   if (certificateId_) {
1063     if (!certificateInfoButton_) {
1064       NSString* text = l10n_util::GetNSString(IDS_PAGEINFO_CERT_INFO_BUTTON);
1065       certificateInfoButton_ = [self addLinkButtonWithText:text
1066           toView:connectionTabContentView_];
1067
1068       [certificateInfoButton_ setTarget:self];
1069       [certificateInfoButton_ setAction:@selector(showCertificateInfo:)];
1070     }
1071   } else {
1072     certificateInfoButton_ = nil;
1073   }
1074
1075   [identityStatusIcon_ setImage:WebsiteSettingsUI::GetIdentityIcon(
1076       identityInfo.identity_status).ToNSImage()];
1077   [identityStatusDescriptionField_ setStringValue:
1078       base::SysUTF8ToNSString(identityInfo.identity_status_description)];
1079
1080   [connectionStatusIcon_ setImage:WebsiteSettingsUI::GetConnectionIcon(
1081       identityInfo.connection_status).ToNSImage()];
1082   [connectionStatusDescriptionField_ setStringValue:
1083       base::SysUTF8ToNSString(identityInfo.connection_status_description)];
1084
1085   [self performLayout];
1086 }
1087
1088 - (void)setCookieInfo:(const CookieInfoList&)cookieInfoList {
1089   // The contents of the permissions view can cause the whole window to get
1090   // bigger, but currently permissions are always set before cookie info.
1091   // Check to make sure that's still the case.
1092   DCHECK_GT([[permissionsView_ subviews] count], 0U);
1093
1094   [cookiesView_ setSubviews:[NSArray array]];
1095   NSPoint controlOrigin = NSMakePoint(kFramePadding, 0);
1096
1097   string16 sectionTitle = l10n_util::GetStringUTF16(
1098       IDS_WEBSITE_SETTINGS_TITLE_SITE_DATA);
1099   NSTextField* header = [self addText:sectionTitle
1100                              withSize:[NSFont smallSystemFontSize]
1101                                  bold:YES
1102                                toView:cookiesView_
1103                               atPoint:controlOrigin];
1104   controlOrigin.y += NSHeight([header frame]) + kPermissionsHeadlineSpacing;
1105
1106   for (CookieInfoList::const_iterator it = cookieInfoList.begin();
1107        it != cookieInfoList.end();
1108        ++it) {
1109     controlOrigin.y += kPermissionsTabSpacing;
1110     CGFloat rowHeight = [self addCookieInfo:*it
1111                                      toView:cookiesView_
1112                                     atPoint:controlOrigin];
1113     controlOrigin.y += rowHeight;
1114   }
1115
1116   controlOrigin.y += kPermissionsTabSpacing;
1117   [cookiesView_ setFrameSize:
1118       NSMakeSize(NSWidth([cookiesView_ frame]), controlOrigin.y)];
1119
1120   [self performLayout];
1121 }
1122
1123 - (void)setPermissionInfo:(const PermissionInfoList&)permissionInfoList {
1124   [permissionsView_ setSubviews:[NSArray array]];
1125   NSPoint controlOrigin = NSMakePoint(kFramePadding, 0);
1126
1127   string16 sectionTitle = l10n_util::GetStringUTF16(
1128       IDS_WEBSITE_SETTINGS_TITLE_SITE_PERMISSIONS);
1129   NSTextField* header = [self addText:sectionTitle
1130                              withSize:[NSFont smallSystemFontSize]
1131                                  bold:YES
1132                                toView:permissionsView_
1133                               atPoint:controlOrigin];
1134   [self sizeTextFieldHeightToFit:header];
1135   controlOrigin.y += NSHeight([header frame]) + kPermissionsHeadlineSpacing;
1136
1137   for (PermissionInfoList::const_iterator permission =
1138            permissionInfoList.begin();
1139        permission != permissionInfoList.end();
1140        ++permission) {
1141     controlOrigin.y += kPermissionsTabSpacing;
1142     NSPoint rowBottomRight = [self addPermission:*permission
1143                                           toView:permissionsView_
1144                                          atPoint:controlOrigin];
1145     controlOrigin.y = rowBottomRight.y;
1146   }
1147   controlOrigin.y += kFramePadding;
1148   [permissionsView_ setFrameSize:
1149       NSMakeSize(NSWidth([permissionsView_ frame]), controlOrigin.y)];
1150   [self performLayout];
1151 }
1152
1153 - (void)setFirstVisit:(const string16&)firstVisit {
1154   [firstVisitIcon_ setImage:
1155       WebsiteSettingsUI::GetFirstVisitIcon(firstVisit).ToNSImage()];
1156   [firstVisitDescriptionField_ setStringValue:
1157       base::SysUTF16ToNSString(firstVisit)];
1158   [self performLayout];
1159 }
1160
1161 - (void)setSelectedTab:(WebsiteSettingsUI::TabId)tabId {
1162   NSInteger index = static_cast<NSInteger>(tabId);
1163   [segmentedControl_ setSelectedSegment:index];
1164   [tabView_ selectTabViewItemAtIndex:index];
1165 }
1166
1167 @end
1168
1169 WebsiteSettingsUIBridge::WebsiteSettingsUIBridge()
1170   : bubble_controller_(nil) {
1171 }
1172
1173 WebsiteSettingsUIBridge::~WebsiteSettingsUIBridge() {
1174 }
1175
1176 void WebsiteSettingsUIBridge::set_bubble_controller(
1177     WebsiteSettingsBubbleController* controller) {
1178   bubble_controller_ = controller;
1179 }
1180
1181 void WebsiteSettingsUIBridge::Show(gfx::NativeWindow parent,
1182                                    Profile* profile,
1183                                    content::WebContents* web_contents,
1184                                    const GURL& url,
1185                                    const content::SSLStatus& ssl) {
1186   bool is_internal_page = InternalChromePage(url);
1187
1188   // Create the bridge. This will be owned by the bubble controller.
1189   WebsiteSettingsUIBridge* bridge = new WebsiteSettingsUIBridge();
1190
1191   // Create the bubble controller. It will dealloc itself when it closes.
1192   WebsiteSettingsBubbleController* bubble_controller =
1193       [[WebsiteSettingsBubbleController alloc]
1194           initWithParentWindow:parent
1195        websiteSettingsUIBridge:bridge
1196                    webContents:web_contents
1197                 isInternalPage:is_internal_page];
1198
1199   if (!is_internal_page) {
1200     // Initialize the presenter, which holds the model and controls the UI.
1201     // This is also owned by the bubble controller.
1202     WebsiteSettings* presenter = new WebsiteSettings(
1203         bridge,
1204         profile,
1205         TabSpecificContentSettings::FromWebContents(web_contents),
1206         InfoBarService::FromWebContents(web_contents),
1207         url,
1208         ssl,
1209         content::CertStore::GetInstance());
1210     [bubble_controller setPresenter:presenter];
1211   }
1212
1213   [bubble_controller showWindow:nil];
1214 }
1215
1216 void WebsiteSettingsUIBridge::SetIdentityInfo(
1217     const WebsiteSettingsUI::IdentityInfo& identity_info) {
1218   [bubble_controller_ setIdentityInfo:identity_info];
1219 }
1220
1221 void WebsiteSettingsUIBridge::SetCookieInfo(
1222     const CookieInfoList& cookie_info_list) {
1223   [bubble_controller_ setCookieInfo:cookie_info_list];
1224 }
1225
1226 void WebsiteSettingsUIBridge::SetPermissionInfo(
1227     const PermissionInfoList& permission_info_list) {
1228   [bubble_controller_ setPermissionInfo:permission_info_list];
1229 }
1230
1231 void WebsiteSettingsUIBridge::SetFirstVisit(const string16& first_visit) {
1232   [bubble_controller_ setFirstVisit:first_visit];
1233 }
1234
1235 void WebsiteSettingsUIBridge::SetSelectedTab(TabId tab_id) {
1236   [bubble_controller_ setSelectedTab:tab_id];
1237 }