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