Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / website_settings / website_settings_bubble_controller.mm
blob7d28065a32bf2b5fdc9d32a9c75af610506d3bba
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.h"
7 #include <cmath>
9 #import <AppKit/AppKit.h>
11 #include "base/i18n/rtl.h"
12 #include "base/mac/bind_objc_block.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/sys_string_conversions.h"
15 #import "chrome/browser/certificate_viewer.h"
16 #include "chrome/browser/infobars/infobar_service.h"
17 #include "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 #import "chrome/browser/ui/tab_dialogs.h"
24 #include "chrome/browser/ui/website_settings/permission_menu_model.h"
25 #include "chrome/browser/ui/website_settings/website_settings_utils.h"
26 #include "chrome/common/url_constants.h"
27 #include "chrome/grit/chromium_strings.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "content/public/browser/cert_store.h"
30 #include "content/public/browser/page_navigator.h"
31 #include "content/public/browser/ssl_host_state_delegate.h"
32 #include "content/public/browser/user_metrics.h"
33 #include "content/public/browser/web_contents.h"
34 #include "grit/theme_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 #import "ui/gfx/mac/coordinate_conversion.h"
41 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
43 namespace {
45 // The default width of the window, in view coordinates. It may be larger to
46 // fit the content.
47 const CGFloat kDefaultWindowWidth = 310;
49 // Spacing in between sections.
50 const CGFloat kVerticalSpacing = 10;
52 // Padding between the window frame and content.
53 const CGFloat kFramePadding = 20;
55 // Padding between the window frame and content for the internal page bubble.
56 const CGFloat kInternalPageFramePadding = 10;
58 // Spacing between the headlines and description text on the Connection tab.
59 const CGFloat kConnectionHeadlineSpacing = 2;
61 // Spacing between images on the Connection tab and the text.
62 const CGFloat kConnectionImageSpacing = 10;
64 // Spacing between the image and text for internal pages.
65 const CGFloat kInternalPageImageSpacing = 10;
67 // Square size of the images on the Connections tab.
68 const CGFloat kConnectionImageSize = 30;
70 // Square size of the image that is shown for internal pages.
71 const CGFloat kInternalPageImageSize = 26;
73 // Square size of the permission images.
74 const CGFloat kPermissionImageSize = 19;
76 // Vertical adjustment for the permission images. They have an extra pixel of
77 // padding on the bottom edge.
78 const CGFloat kPermissionImageYAdjust = 1;
80 // Spacing between a permission image and the text.
81 const CGFloat kPermissionImageSpacing = 3;
83 // The spacing between individual items in the Permissions tab.
84 const CGFloat kPermissionsTabSpacing = 12;
86 // Extra spacing after a headline on the Permissions tab.
87 const CGFloat kPermissionsHeadlineSpacing = 2;
89 // The amount of horizontal space between a permission label and the popup.
90 const CGFloat kPermissionPopUpXSpacing = 3;
92 // The extra space to the left of the first tab in the tab strip.
93 const CGFloat kTabStripXPadding = kFramePadding;
95 // The amount of space between the visual borders of adjacent tabs.
96 const CGFloat kTabSpacing = 4;
98 // The amount of space above the tab strip.
99 const CGFloat kTabStripTopSpacing = 14;
101 // The height of the clickable area of the tab.
102 const CGFloat kTabHeight = 28;
104 // The amount of space above tab labels.
105 const CGFloat kTabLabelTopPadding = 6;
107 // The amount of padding to leave on either side of the tab label.
108 const CGFloat kTabLabelXPadding = 12;
110 // The amount of padding to *remove* when placing
111 // |IDS_WEBSITE_SETTINGS_{FIRST,THIRD}_PARTY_SITE_DATA| next to each other.
112 const CGFloat kTextLabelXPadding = 5;
114 // Takes in the parent window, which should be a BrowserWindow, and gets the
115 // proper anchor point for the bubble. The returned point is in screen
116 // coordinates.
117 NSPoint AnchorPointForWindow(NSWindow* parent) {
118   BrowserWindowController* controller = [parent windowController];
119   NSPoint origin = NSZeroPoint;
120   if ([controller isKindOfClass:[BrowserWindowController class]]) {
121     LocationBarViewMac* location_bar = [controller locationBarBridge];
122     if (location_bar) {
123       NSPoint bubble_point = location_bar->GetPageInfoBubblePoint();
124       origin = [parent convertBaseToScreen:bubble_point];
125     }
126   }
127   return origin;
130 }  // namespace
132 @interface WebsiteSettingsTabSegmentedCell : NSSegmentedCell {
133  @private
134   base::scoped_nsobject<NSImage> tabstripCenterImage_;
135   base::scoped_nsobject<NSImage> tabstripLeftImage_;
136   base::scoped_nsobject<NSImage> tabstripRightImage_;
138   base::scoped_nsobject<NSImage> tabCenterImage_;
139   base::scoped_nsobject<NSImage> tabLeftImage_;
140   base::scoped_nsobject<NSImage> tabRightImage_;
142   // Key track of the index of segment which has keyboard focus. This is not
143   // the same as the currently selected segment.
144   NSInteger keySegment_;
147 // The text attributes to use for the tab labels.
148 + (NSDictionary*)textAttributes;
150 @end
152 @implementation WebsiteSettingsTabSegmentedCell
154 + (NSDictionary*)textAttributes {
155   NSFont* smallSystemFont =
156       [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
157   return @{ NSFontAttributeName : smallSystemFont };
160 - (id)init {
161   if ((self = [super init])) {
162     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
163     tabstripCenterImage_.reset(rb.GetNativeImageNamed(
164         IDR_WEBSITE_SETTINGS_TABSTRIP_CENTER).CopyNSImage());
165     tabstripLeftImage_.reset(rb.GetNativeImageNamed(
166         IDR_WEBSITE_SETTINGS_TABSTRIP_LEFT).CopyNSImage());
167     tabstripRightImage_.reset(rb.GetNativeImageNamed(
168         IDR_WEBSITE_SETTINGS_TABSTRIP_RIGHT).CopyNSImage());
170     tabCenterImage_.reset(
171         rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_CENTER2).CopyNSImage());
172     tabLeftImage_.reset(
173         rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_LEFT2).CopyNSImage());
174     tabRightImage_.reset(
175         rb.GetNativeImageNamed(IDR_WEBSITE_SETTINGS_TAB_RIGHT2).CopyNSImage());
176   }
177   return self;
180 // Called when keyboard focus in the segmented control is moved forward.
181 - (void)makeNextSegmentKey {
182   [super makeNextSegmentKey];
183   keySegment_ = (keySegment_ + 1) % [self segmentCount];
186 // Called when keyboard focus in the segmented control is moved backwards.
187 - (void)makePreviousSegmentKey {
188   [super makePreviousSegmentKey];
189   if (--keySegment_ < 0)
190     keySegment_ += [self segmentCount];
193 - (void)setSelectedSegment:(NSInteger)selectedSegment {
194   keySegment_ = selectedSegment;
195   [super setSelectedSegment:selectedSegment];
198 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
199   CGFloat tabstripHeight = [tabCenterImage_ size].height;
201   // Draw the tab for the selected segment.
202   NSRect tabRect = [self hitRectForSegment:[self selectedSegment]];
203   tabRect.origin.y = 0;
204   tabRect.size.height = tabstripHeight;
206   NSDrawThreePartImage(tabRect,
207                        tabLeftImage_,
208                        tabCenterImage_,
209                        tabRightImage_,
210                        /*vertical=*/ NO,
211                        NSCompositeSourceOver,
212                        1,
213                        /*flipped=*/ YES);
215   // Draw the background to the left of the selected tab.
216   NSRect backgroundRect = NSMakeRect(0, 0, NSMinX(tabRect), tabstripHeight);
217   NSDrawThreePartImage(backgroundRect,
218                        nil,
219                        tabstripCenterImage_,
220                        tabstripLeftImage_,
221                        /*vertical=*/ NO,
222                        NSCompositeSourceOver,
223                        1,
224                        /*flipped=*/ YES);
226   // Draw the background to the right of the selected tab.
227   backgroundRect.origin.x = NSMaxX(tabRect);
228   backgroundRect.size.width = NSMaxX(cellFrame) - NSMaxX(tabRect);
229   NSDrawThreePartImage(backgroundRect,
230                        tabstripRightImage_,
231                        tabstripCenterImage_,
232                        nil,
233                        /*vertical=*/ NO,
234                        NSCompositeSourceOver,
235                        1,
236                        /*flipped=*/ YES);
238   // Call the superclass method to trigger drawing of the tab labels.
239   NSRect interiorFrame = cellFrame;
240   interiorFrame.size.width = 0;
241   for (NSInteger i = 0; i < [self segmentCount]; ++i)
242     interiorFrame.size.width += [self widthForSegment:i];
243   [self drawInteriorWithFrame:interiorFrame inView:controlView];
245   if ([[controlView window] firstResponder] == controlView)
246     [self drawFocusRect];
249 - (void)drawFocusRect {
250   gfx::ScopedNSGraphicsContextSaveGState scoped_state;
251   NSSetFocusRingStyle(NSFocusRingOnly);
252   [[NSColor keyboardFocusIndicatorColor] set];
253   NSFrameRect([self hitRectForSegment:keySegment_]);
256 // Returns the segment number for the left-most positioned segment.
257 // On Right-to-Left languages, segment 0 is on the right.
258 - (NSInteger)leftSegment {
259   BOOL isRTL = [self userInterfaceLayoutDirection] ==
260                    NSUserInterfaceLayoutDirectionRightToLeft;
261   return isRTL ? [self segmentCount] - 1 : 0;
264 // Return the hit rect (i.e., the visual bounds of the tab) for
265 // the given segment.
266 - (NSRect)hitRectForSegment:(NSInteger)segment {
267   CGFloat tabstripHeight = [tabCenterImage_ size].height;
268   DCHECK_GT(tabstripHeight, kTabHeight);
269   DCHECK([self segmentCount] == 2);  // Assume 2 segments to keep things simple.
270   NSInteger leftSegment = [self leftSegment];
271   CGFloat xOrigin = segment == leftSegment ? kTabStripXPadding
272                                            : [self widthForSegment:leftSegment];
273   NSRect rect = NSMakeRect(xOrigin, tabstripHeight - kTabHeight,
274                            [self widthForSegment:segment] - kTabStripXPadding,
275                            kTabHeight);
276   return NSInsetRect(rect, kTabSpacing / 2, 0);
279 - (void)drawSegment:(NSInteger)segment
280             inFrame:(NSRect)tabFrame
281            withView:(NSView*)controlView {
282   // Adjust the tab's frame so that the label appears centered in the tab.
283   if (segment == [self leftSegment])
284     tabFrame.origin.x += kTabStripXPadding;
285   tabFrame.size.width -= kTabStripXPadding;
286   tabFrame.origin.y += kTabLabelTopPadding;
287   tabFrame.size.height -= kTabLabelTopPadding;
289   // Center the label's frame in the tab's frame.
290   NSString* label = [self labelForSegment:segment];
291   NSDictionary* textAttributes =
292       [WebsiteSettingsTabSegmentedCell textAttributes];
293   NSSize textSize = [label sizeWithAttributes:textAttributes];
294   NSRect labelFrame;
295   labelFrame.size = textSize;
296   labelFrame.origin.x =
297       tabFrame.origin.x + (NSWidth(tabFrame) - textSize.width) / 2.0;
298   labelFrame.origin.y =
299       tabFrame.origin.y + (NSHeight(tabFrame) - textSize.height) / 2.0;
301   [label drawInRect:labelFrame withAttributes:textAttributes];
304 // Overrides the default tracking behavior to only respond to clicks inside the
305 // visual borders of the tab.
306 - (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
307   NSInteger segmentCount = [self segmentCount];
308   for (NSInteger i = 0; i < segmentCount; ++i) {
309     if (NSPointInRect(startPoint, [self hitRectForSegment:i]))
310       return YES;
311   }
312   return NO;
315 // Overrides the default cell height to take up the full height of the
316 // segmented control. Otherwise, clicks on the lower part of a tab will be
317 // ignored.
318 - (NSSize)cellSizeForBounds:(NSRect)aRect {
319   return NSMakeSize([super cellSizeForBounds:aRect].width,
320                     [tabstripCenterImage_ size].height);
323 // Returns the minimum size required to display this cell.
324 // It should always be exactly as tall as the tabstrip background image.
325 - (NSSize)cellSize {
326   return NSMakeSize([super cellSize].width,
327                     [tabstripCenterImage_ size].height);
329 @end
331 @implementation WebsiteSettingsBubbleController
333 - (CGFloat)defaultWindowWidth {
334   return kDefaultWindowWidth;
337 - (id)initWithParentWindow:(NSWindow*)parentWindow
338    websiteSettingsUIBridge:(WebsiteSettingsUIBridge*)bridge
339                webContents:(content::WebContents*)webContents
340             isInternalPage:(BOOL)isInternalPage {
341   DCHECK(parentWindow);
343   webContents_ = webContents;
345   // Use an arbitrary height; it will be changed in performLayout.
346   NSRect contentRect = NSMakeRect(0, 0, [self defaultWindowWidth], 1);
347   // Create an empty window into which content is placed.
348   base::scoped_nsobject<InfoBubbleWindow> window(
349       [[InfoBubbleWindow alloc] initWithContentRect:contentRect
350                                           styleMask:NSBorderlessWindowMask
351                                             backing:NSBackingStoreBuffered
352                                               defer:NO]);
354   if ((self = [super initWithWindow:window.get()
355                        parentWindow:parentWindow
356                          anchoredAt:NSZeroPoint])) {
357     [[self bubble] setArrowLocation:info_bubble::kTopLeft];
359     // Create the container view that uses flipped coordinates.
360     NSRect contentFrame = NSMakeRect(0, 0, [self defaultWindowWidth], 300);
361     contentView_.reset(
362         [[FlippedView alloc] initWithFrame:contentFrame]);
364     // Replace the window's content.
365     [[[self window] contentView] setSubviews:
366         [NSArray arrayWithObject:contentView_.get()]];
368     if (isInternalPage)
369       [self initializeContentsForInternalPage];
370     else
371       [self initializeContents];
373     bridge_.reset(bridge);
374     bridge_->set_bubble_controller(self);
375   }
376   return self;
379 - (void)windowWillClose:(NSNotification*)notification {
380   if (presenter_.get())
381     presenter_->OnUIClosing();
382   presenter_.reset();
383   [super windowWillClose:notification];
386 - (void)setPresenter:(WebsiteSettings*)presenter {
387   presenter_.reset(presenter);
390 // Create the subviews for the bubble for internal Chrome pages.
391 - (void)initializeContentsForInternalPage {
392   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
394   NSPoint controlOrigin = NSMakePoint(
395       kInternalPageFramePadding,
396       kInternalPageFramePadding + info_bubble::kBubbleArrowHeight);
397   NSSize imageSize = NSMakeSize(kInternalPageImageSize,
398                                 kInternalPageImageSize);
399   NSImageView* imageView = [self addImageWithSize:imageSize
400                                            toView:contentView_
401                                           atPoint:controlOrigin];
402   [imageView setImage:rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_26).ToNSImage()];
404   controlOrigin.x += NSWidth([imageView frame]) + kInternalPageImageSpacing;
405   base::string16 text = l10n_util::GetStringUTF16(IDS_PAGE_INFO_INTERNAL_PAGE);
406   NSTextField* textField = [self addText:text
407                                 withSize:[NSFont smallSystemFontSize]
408                                     bold:NO
409                                   toView:contentView_
410                                  atPoint:controlOrigin];
411   // Center the text vertically with the image.
412   NSRect textFrame = [textField frame];
413   textFrame.origin.y += (imageSize.height - NSHeight(textFrame)) / 2;
414   [textField setFrame:textFrame];
416   // Adjust the contentView to fit everything.
417   CGFloat maxY = std::max(NSMaxY([imageView frame]), NSMaxY(textFrame));
418   [contentView_ setFrame:NSMakeRect(
419       0, 0, [self defaultWindowWidth], maxY + kInternalPageFramePadding)];
421   [self sizeAndPositionWindow];
424 // Create the subviews for the website settings bubble.
425 - (void)initializeContents {
426   // Keeps track of the position that the next control should be drawn at.
427   NSPoint controlOrigin = NSMakePoint(
428       kFramePadding,
429       kFramePadding + info_bubble::kBubbleArrowHeight);
431   // Create a text field (empty for now) to show the site identity.
432   identityField_ = [self addText:base::string16()
433                         withSize:[NSFont systemFontSize]
434                             bold:YES
435                           toView:contentView_
436                          atPoint:controlOrigin];
437   controlOrigin.y +=
438       NSHeight([identityField_ frame]) + kConnectionHeadlineSpacing;
440   // Create a text field to identity status (e.g. verified, not verified).
441   identityStatusField_ = [self addText:base::string16()
442                               withSize:[NSFont smallSystemFontSize]
443                                   bold:NO
444                                 toView:contentView_
445                                atPoint:controlOrigin];
447   // Create the tab view and its two tabs.
449   base::scoped_nsobject<WebsiteSettingsTabSegmentedCell> cell(
450       [[WebsiteSettingsTabSegmentedCell alloc] init]);
451   CGFloat tabstripHeight = [cell cellSize].height;
452   NSRect tabstripFrame = NSMakeRect(
453       0, 0, [self defaultWindowWidth], tabstripHeight);
454   segmentedControl_.reset(
455       [[NSSegmentedControl alloc] initWithFrame:tabstripFrame]);
456   [segmentedControl_ setCell:cell];
457   [segmentedControl_ setSegmentCount:WebsiteSettingsUI::NUM_TAB_IDS];
458   [segmentedControl_ setTarget:self];
459   [segmentedControl_ setAction:@selector(tabSelected:)];
460   [segmentedControl_ setAutoresizingMask:NSViewWidthSizable];
462   NSDictionary* textAttributes =
463       [WebsiteSettingsTabSegmentedCell textAttributes];
465   // Create the "Permissions" tab.
466   NSString* label = l10n_util::GetNSString(
467       IDS_WEBSITE_SETTINGS_TAB_LABEL_PERMISSIONS);
468   [segmentedControl_ setLabel:label
469                    forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
470   NSSize textSize = [label sizeWithAttributes:textAttributes];
471   CGFloat tabWidth = textSize.width + 2 * kTabLabelXPadding;
473   // Create the "Connection" tab.
474   label = l10n_util::GetNSString(IDS_WEBSITE_SETTINGS_TAB_LABEL_CONNECTION);
475   textSize = [label sizeWithAttributes:textAttributes];
476   [segmentedControl_ setLabel:label
477                    forSegment:WebsiteSettingsUI::TAB_ID_CONNECTION];
479   DCHECK_EQ([segmentedControl_ segmentCount], WebsiteSettingsUI::NUM_TAB_IDS);
481   // Make both tabs the width of the widest. The first segment has some
482   // additional padding that is not part of the tab, which is used for drawing
483   // the background of the tab strip.
484   tabWidth = std::max(tabWidth,
485                       textSize.width + 2 * kTabLabelXPadding);
486   [segmentedControl_ setWidth:tabWidth + kTabStripXPadding
487                    forSegment:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
488   [segmentedControl_ setWidth:tabWidth + kTabStripXPadding
489                    forSegment:WebsiteSettingsUI::TAB_ID_CONNECTION];
491   [segmentedControl_ setFont:[textAttributes objectForKey:NSFontAttributeName]];
492   [contentView_ addSubview:segmentedControl_];
494   NSRect tabFrame = NSMakeRect(0, 0, [self defaultWindowWidth], 300);
495   tabView_.reset([[NSTabView alloc] initWithFrame:tabFrame]);
496   [tabView_ setTabViewType:NSNoTabsNoBorder];
497   [tabView_ setDrawsBackground:NO];
498   [tabView_ setControlSize:NSSmallControlSize];
499   [contentView_ addSubview:tabView_.get()];
501   permissionsTabContentView_ = [self addPermissionsTabToTabView:tabView_];
502   connectionTabContentView_ = [self addConnectionTabToTabView:tabView_];
503   [self setSelectedTab:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
505   [self performLayout];
508 // Create the contents of the Permissions tab and add it to the given tab view.
509 // Returns a weak reference to the tab view item's view.
510 - (NSView*)addPermissionsTabToTabView:(NSTabView*)tabView {
511   base::scoped_nsobject<NSTabViewItem> item([[NSTabViewItem alloc] init]);
512   [tabView_ insertTabViewItem:item.get()
513                       atIndex:WebsiteSettingsUI::TAB_ID_PERMISSIONS];
514   base::scoped_nsobject<NSView> contentView(
515       [[FlippedView alloc] initWithFrame:[tabView_ contentRect]]);
516   [contentView setAutoresizingMask:NSViewWidthSizable];
517   [item setView:contentView.get()];
519   // Initialize the two containers that hold the controls. The initial frames
520   // are arbitrary, and will be adjusted after the controls are laid out.
521   cookiesView_ = [[[FlippedView alloc]
522       initWithFrame:[tabView_ contentRect]] autorelease];
523   [cookiesView_ setAutoresizingMask:NSViewWidthSizable];
525   permissionsView_ = [[[FlippedView alloc]
526       initWithFrame:[tabView_ contentRect]] autorelease];
528   [contentView addSubview:cookiesView_];
529   [contentView addSubview:permissionsView_];
531   // Create the link button to view site settings. Its position will be set in
532   // performLayout.
533   NSString* siteSettingsButtonText =
534       l10n_util::GetNSString(IDS_PAGE_INFO_SITE_SETTINGS_LINK);
535   siteSettingsButton_ =
536       [self addLinkButtonWithText:siteSettingsButtonText toView:contentView];
537   [siteSettingsButton_ setTarget:self];
538   [siteSettingsButton_ setAction:@selector(showSiteSettingsData:)];
540   return contentView.get();
543 // Handler for the link button below the list of cookies.
544 - (void)showCookiesAndSiteData:(id)sender {
545   DCHECK(webContents_);
546   DCHECK(presenter_);
547   presenter_->RecordWebsiteSettingsAction(
548       WebsiteSettings::WEBSITE_SETTINGS_COOKIES_DIALOG_OPENED);
549   TabDialogs::FromWebContents(webContents_)->ShowCollectedCookies();
552 // Handler for the site settings button below the list of permissions.
553 - (void)showSiteSettingsData:(id)sender {
554   DCHECK(webContents_);
555   DCHECK(presenter_);
556   presenter_->RecordWebsiteSettingsAction(
557       WebsiteSettings::WEBSITE_SETTINGS_SITE_SETTINGS_OPENED);
558   webContents_->OpenURL(content::OpenURLParams(
559       GURL(chrome::kChromeUIContentSettingsURL), content::Referrer(),
560       NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, false));
563 // Handler for the link button to show certificate information.
564 - (void)showCertificateInfo:(id)sender {
565   DCHECK(certificateId_);
566   presenter_->RecordWebsiteSettingsAction(
567       WebsiteSettings::WEBSITE_SETTINGS_CERTIFICATE_DIALOG_OPENED);
568   ShowCertificateViewerByID(webContents_, [self parentWindow], certificateId_);
571 // Handler for the link button to revoke user certificate decisions.
572 - (void)resetCertificateDecisions:(id)sender {
573   DCHECK(resetDecisionsButton_);
574   presenter_->OnRevokeSSLErrorBypassButtonPressed();
575   [self close];
578 // Handler for the link to show help information about the connection tab.
579 - (void)showHelpPage:(id)sender {
580   presenter_->RecordWebsiteSettingsAction(
581       WebsiteSettings::WEBSITE_SETTINGS_CONNECTION_HELP_OPENED);
582   webContents_->OpenURL(content::OpenURLParams(
583       GURL(chrome::kPageInfoHelpCenterURL), content::Referrer(),
584       NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, false));
587 // Create the contents of the Connection tab and add it to the given tab view.
588 // Returns a weak reference to the tab view item's view.
589 - (NSView*)addConnectionTabToTabView:(NSTabView*)tabView {
590   base::scoped_nsobject<NSTabViewItem> item([[NSTabViewItem alloc] init]);
591   base::scoped_nsobject<NSView> contentView(
592       [[FlippedView alloc] initWithFrame:[tabView_ contentRect]]);
593   [contentView setAutoresizingMask:NSViewWidthSizable];
595   // Place all the text and images at the same position. The positions will be
596   // adjusted in performLayout.
597   NSPoint textPosition = NSMakePoint(
598       kFramePadding + kConnectionImageSize + kConnectionImageSpacing,
599       kFramePadding);
600   NSPoint imagePosition = NSMakePoint(kFramePadding, kFramePadding);
601   NSSize imageSize = NSMakeSize(kConnectionImageSize, kConnectionImageSize);
603   identityStatusIcon_ = [self addImageWithSize:imageSize
604                                         toView:contentView
605                                        atPoint:imagePosition];
606   identityStatusDescriptionField_ =
607       [self addText:base::string16()
608            withSize:[NSFont smallSystemFontSize]
609                bold:NO
610              toView:contentView.get()
611             atPoint:textPosition];
613   separatorAfterIdentity_ = [self addSeparatorToView:contentView];
614   [separatorAfterIdentity_ setAutoresizingMask:NSViewWidthSizable];
616   connectionStatusIcon_ = [self addImageWithSize:imageSize
617                                           toView:contentView
618                                          atPoint:imagePosition];
619   connectionStatusDescriptionField_ =
620       [self addText:base::string16()
621            withSize:[NSFont smallSystemFontSize]
622                bold:NO
623              toView:contentView.get()
624             atPoint:textPosition];
625   certificateInfoButton_ = nil;  // This will be created only if necessary.
626   resetDecisionsButton_ = nil;   // This will be created only if necessary.
627   separatorAfterConnection_ = [self addSeparatorToView:contentView];
628   [separatorAfterConnection_ setAutoresizingMask:NSViewWidthSizable];
630   NSString* helpButtonText = l10n_util::GetNSString(
631       IDS_PAGE_INFO_HELP_CENTER_LINK);
632   helpButton_ = [self addLinkButtonWithText:helpButtonText
633                                      toView:contentView];
634   [helpButton_ setTarget:self];
635   [helpButton_ setAction:@selector(showHelpPage:)];
637   [item setView:contentView.get()];
638   [tabView_ insertTabViewItem:item.get()
639                       atIndex:WebsiteSettingsUI::TAB_ID_CONNECTION];
641   return contentView.get();
644 // Set the Y position of |view| to the given position, and return the position
645 // of its bottom edge.
646 - (CGFloat)setYPositionOfView:(NSView*)view to:(CGFloat)position {
647   NSRect frame = [view frame];
648   frame.origin.y = position;
649   [view setFrame:frame];
650   return position + NSHeight(frame);
653 - (void)setWidthOfView:(NSView*)view to:(CGFloat)width {
654   [view setFrameSize:NSMakeSize(width, NSHeight([view frame]))];
657 // Layout all of the controls in the window. This should be called whenever
658 // the content has changed.
659 - (void)performLayout {
660   // Make the content at least as wide as the permissions view.
661   CGFloat contentWidth = std::max([self defaultWindowWidth],
662                                   NSWidth([permissionsView_ frame]));
664   // Set the width of the content view now, so that all the text fields will
665   // be sized to fit before their heights and vertical positions are adjusted.
666   // The tab view will only resize the currently selected tab, so resize both
667   // tab content views manually.
668   [self setWidthOfView:contentView_ to:contentWidth];
669   [self setWidthOfView:permissionsTabContentView_ to:contentWidth];
670   [self setWidthOfView:connectionTabContentView_ to:contentWidth];
672   // Place the identity status immediately below the identity.
673   [self sizeTextFieldHeightToFit:identityField_];
674   [self sizeTextFieldHeightToFit:identityStatusField_];
675   CGFloat yPos = NSMaxY([identityField_ frame]) + kConnectionHeadlineSpacing;
676   yPos = [self setYPositionOfView:identityStatusField_ to:yPos];
678   // Lay out the Permissions tab.
680   yPos = [self setYPositionOfView:cookiesView_ to:kFramePadding];
682   // Put the permission info just below the link button.
683   yPos = [self setYPositionOfView:permissionsView_
684                                to:NSMaxY([cookiesView_ frame]) + kFramePadding];
686   // Put the link button for site settings just below the permissions.
687   // TODO(palmer): set the position of this based on RTL/LTR.
688   // http://code.google.com/p/chromium/issues/detail?id=525304
689   [siteSettingsButton_ setFrameOrigin:NSMakePoint(kFramePadding, yPos)];
691   // Lay out the Connection tab.
693   // Lay out the identity status section.
694   [self sizeTextFieldHeightToFit:identityStatusDescriptionField_];
695   yPos = std::max(NSMaxY([identityStatusDescriptionField_ frame]),
696                   NSMaxY([identityStatusIcon_ frame]));
697   if (certificateInfoButton_) {
698     NSRect certificateButtonFrame = [certificateInfoButton_ frame];
699     certificateButtonFrame.origin.x = NSMinX(
700         [identityStatusDescriptionField_ frame]);
701     certificateButtonFrame.origin.y = yPos + kVerticalSpacing;
702     [certificateInfoButton_ setFrame:certificateButtonFrame];
703     yPos = NSMaxY(certificateButtonFrame);
704   }
705   if (resetDecisionsButton_) {
706     NSRect resetDecisionsButtonFrame = [resetDecisionsButton_ frame];
707     resetDecisionsButtonFrame.origin.x =
708         NSMinX([identityStatusDescriptionField_ frame]);
709     resetDecisionsButtonFrame.origin.y = yPos + kVerticalSpacing;
710     [resetDecisionsButton_ setFrame:resetDecisionsButtonFrame];
711     yPos = NSMaxY(resetDecisionsButtonFrame);
712   }
713   yPos = [self setYPositionOfView:separatorAfterIdentity_
714                                to:yPos + kVerticalSpacing];
715   yPos += kVerticalSpacing;
717   // Lay out the connection status section.
718   [self sizeTextFieldHeightToFit:connectionStatusDescriptionField_];
719   [self setYPositionOfView:connectionStatusIcon_ to:yPos];
720   [self setYPositionOfView:connectionStatusDescriptionField_ to:yPos];
721   yPos = std::max(NSMaxY([connectionStatusDescriptionField_ frame]),
722                   NSMaxY([connectionStatusIcon_ frame]));
723   yPos = [self setYPositionOfView:separatorAfterConnection_
724                                to:yPos + kVerticalSpacing];
725   yPos += kVerticalSpacing;
727   [self setYPositionOfView:helpButton_ to:yPos];
729   // Adjust the tab view size and place it below the identity status.
731   yPos = NSMaxY([identityStatusField_ frame]) + kTabStripTopSpacing;
732   yPos = [self setYPositionOfView:segmentedControl_ to:yPos];
734   CGFloat connectionTabHeight = NSMaxY([helpButton_ frame]) + kVerticalSpacing;
736   NSRect tabViewFrame = [tabView_ frame];
737   tabViewFrame.origin.y = yPos;
738   tabViewFrame.size.height = std::max(
739       connectionTabHeight, NSMaxY([siteSettingsButton_ frame]) + kFramePadding);
740   tabViewFrame.size.width = contentWidth;
741   [tabView_ setFrame:tabViewFrame];
743   // Adjust the contentView to fit everything.
744   [contentView_ setFrame:NSMakeRect(
745       0, 0, NSWidth(tabViewFrame), NSMaxY(tabViewFrame))];
747   [self sizeAndPositionWindow];
750 // Adjust the size of the window to match the size of the content, and position
751 // the bubble anchor appropriately.
752 - (void)sizeAndPositionWindow {
753   NSRect windowFrame = [contentView_ frame];
754   windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size
755                                                        toView:nil];
756   // Adjust the origin by the difference in height.
757   windowFrame.origin = [[self window] frame].origin;
758   windowFrame.origin.y -= NSHeight(windowFrame) -
759       NSHeight([[self window] frame]);
761   // Resize the window. Only animate if the window is visible, otherwise it
762   // could be "growing" while it's opening, looking awkward.
763   [[self window] setFrame:windowFrame
764                   display:YES
765                   animate:[[self window] isVisible]];
767   // Adjust the anchor for the bubble.
768   [self setAnchorPoint:AnchorPointForWindow([self parentWindow])];
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];
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];
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();
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();
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;
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;
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()];
852   [GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()];
853   return button.get();
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]);
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;
872   base::scoped_nsobject<NSButtonCell> cell(
873       [[NSButtonCell alloc] initTextCell:text]);
874   [button setCell:cell.get()];
875   [GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()];
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;
882   [button setFrame:buttonFrame];
883   [button setButtonType:NSMomentaryPushInButton];
884   [button setBezelStyle:NSRegularSquareBezelStyle];
885   [view addSubview:button.get()];
887   return button.get();
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 {
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 = [button
908       maxTitleWidthForContentSettingsType:permissionInfo.type
909                        withDefaultSetting:permissionInfo.default_setting];
911   // Ensure the containing view is large enough to contain the button with its
912   // widest possible title.
913   NSRect containerFrame = [view frame];
914   containerFrame.size.width = std::max(
915       NSWidth(containerFrame), point.x + maxTitleWidth + kFramePadding);
916   [view setFrame:containerFrame];
917   [view addSubview:button.get()];
918   return button.get();
921 // Called when the user changes the setting of a permission.
922 - (void)onPermissionChanged:(ContentSettingsType)permissionType
923                          to:(ContentSetting)newSetting {
924   if (presenter_)
925     presenter_->OnSitePermissionChanged(permissionType, newSetting);
928 // Called when the user changes the selected segment in the segmented control.
929 - (void)tabSelected:(id)sender {
930   NSInteger index = [segmentedControl_ selectedSegment];
931   switch (index) {
932     case WebsiteSettingsUI::TAB_ID_PERMISSIONS:
933       presenter_->RecordWebsiteSettingsAction(
934           WebsiteSettings::WEBSITE_SETTINGS_PERMISSIONS_TAB_SELECTED);
935       break;
936     case WebsiteSettingsUI::TAB_ID_CONNECTION:
937       presenter_->RecordWebsiteSettingsAction(
938           WebsiteSettings::WEBSITE_SETTINGS_CONNECTION_TAB_SELECTED);
939       break;
940     default:
941       NOTREACHED();
942   }
943   [tabView_ selectTabViewItemAtIndex:index];
946 // Adds a new row to the UI listing the permissions. Returns the NSPoint of the
947 // last UI element added (either the permission button, in LTR, or the text
948 // label, in RTL).
949 - (NSPoint)addPermission:
950     (const WebsiteSettingsUI::PermissionInfo&)permissionInfo
951                   toView:(NSView*)view
952                  atPoint:(NSPoint)point {
953   base::string16 labelText =
954       WebsiteSettingsUI::PermissionTypeToUIString(permissionInfo.type) +
955       base::ASCIIToUTF16(":");
956   bool isRTL =
957       base::i18n::RIGHT_TO_LEFT == base::i18n::GetStringDirection(labelText);
958   base::scoped_nsobject<NSImage> image(
959       [WebsiteSettingsUI::GetPermissionIcon(permissionInfo).ToNSImage()
960           retain]);
962   NSPoint position;
963   NSImageView* imageView;
964   NSPopUpButton* button;
965   NSTextField* label;
967   CGFloat viewWidth = NSWidth([view frame]);
969   if (isRTL) {
970     point.x = NSWidth([view frame]) - kPermissionImageSize -
971               kPermissionImageSpacing - kFramePadding;
972     imageView = [self addImageWithSize:[image size] toView:view atPoint:point];
973     [imageView setImage:image];
974     point.x -= kPermissionImageSpacing;
976     label = [self addText:labelText
977                  withSize:[NSFont smallSystemFontSize]
978                      bold:NO
979                    toView:view
980                   atPoint:point];
981     [label sizeToFit];
982     point.x -= NSWidth([label frame]);
983     [label setFrameOrigin:point];
985     position = NSMakePoint(point.x, point.y);
986     button = [self addPopUpButtonForPermission:permissionInfo
987                                         toView:view
988                                        atPoint:position];
989     position.x -= NSWidth([button frame]);
990     [button setFrameOrigin:position];
991   } else {
992     imageView = [self addImageWithSize:[image size] toView:view atPoint:point];
993     [imageView setImage:image];
994     point.x += kPermissionImageSize + kPermissionImageSpacing;
996     label = [self addText:labelText
997                  withSize:[NSFont smallSystemFontSize]
998                      bold:NO
999                    toView:view
1000                   atPoint:point];
1001     [label sizeToFit];
1003     position = NSMakePoint(NSMaxX([label frame]), point.y);
1004     button = [self addPopUpButtonForPermission:permissionInfo
1005                                         toView:view
1006                                        atPoint:position];
1007   }
1009   [view setFrameSize:NSMakeSize(viewWidth, NSHeight([view frame]))];
1011   // Adjust the vertical position of the button so that its title text is
1012   // aligned with the label. Assumes that the text is the same size in both.
1013   // Also adjust the horizontal position to remove excess space due to the
1014   // invisible bezel.
1015   NSRect titleRect = [[button cell] titleRectForBounds:[button bounds]];
1016   if (isRTL) {
1017     position.x += kPermissionPopUpXSpacing;
1018   } else {
1019     position.x -= titleRect.origin.x - kPermissionPopUpXSpacing;
1020   }
1021   position.y -= titleRect.origin.y;
1022   [button setFrameOrigin:position];
1024   // Align the icon with the text.
1025   [self alignPermissionIcon:imageView withTextField:label];
1027   // Permissions specified by policy or an extension cannot be changed.
1028   if (permissionInfo.source == content_settings::SETTING_SOURCE_EXTENSION ||
1029       permissionInfo.source == content_settings::SETTING_SOURCE_POLICY) {
1030     [button setEnabled:NO];
1031   }
1033   NSRect buttonFrame = [button frame];
1034   return NSMakePoint(NSMaxX(buttonFrame), NSMaxY(buttonFrame));
1037 // Align an image with a text field by vertically centering the image on
1038 // the cap height of the first line of text.
1039 - (void)alignPermissionIcon:(NSImageView*)imageView
1040               withTextField:(NSTextField*)textField {
1041   NSFont* font = [textField font];
1043   // Calculate the offset from the top of the text field.
1044   CGFloat capHeight = [font capHeight];
1045   CGFloat offset = (kPermissionImageSize - capHeight) / 2 -
1046       ([font ascender] - capHeight) - kPermissionImageYAdjust;
1048   NSRect frame = [imageView frame];
1049   frame.origin.y -= offset;
1050   [imageView setFrame:frame];
1053 // Set the content of the identity and identity status fields.
1054 - (void)setIdentityInfo:(const WebsiteSettingsUI::IdentityInfo&)identityInfo {
1055   [identityField_ setStringValue:
1056       base::SysUTF8ToNSString(identityInfo.site_identity)];
1057   [identityStatusField_ setStringValue:base::SysUTF16ToNSString(
1058                                            identityInfo.GetSecuritySummary())];
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_];
1068       [certificateInfoButton_ setTarget:self];
1069       [certificateInfoButton_ setAction:@selector(showCertificateInfo:)];
1070     }
1072     // Check if a security decision has been made, and if so, add a button to
1073     // allow the user to retract their decision.
1074     if (identityInfo.show_ssl_decision_revoke_button) {
1075       NSString* text = l10n_util::GetNSString(
1076           IDS_PAGEINFO_RESET_INVALID_CERTIFICATE_DECISIONS_BUTTON);
1077       resetDecisionsButton_ =
1078           [self addButtonWithTextToConnectionSection:text
1079                                               toView:connectionTabContentView_];
1080       [resetDecisionsButton_ setTarget:self];
1081       [resetDecisionsButton_ setAction:@selector(resetCertificateDecisions:)];
1082     }
1083   } else {
1084     certificateInfoButton_ = nil;
1085   }
1087   [identityStatusIcon_ setImage:WebsiteSettingsUI::GetIdentityIcon(
1088       identityInfo.identity_status).ToNSImage()];
1089   [identityStatusDescriptionField_ setStringValue:
1090       base::SysUTF8ToNSString(identityInfo.identity_status_description)];
1092   [connectionStatusIcon_ setImage:WebsiteSettingsUI::GetConnectionIcon(
1093       identityInfo.connection_status).ToNSImage()];
1094   [connectionStatusDescriptionField_ setStringValue:
1095       base::SysUTF8ToNSString(identityInfo.connection_status_description)];
1097   [self performLayout];
1100 - (void)setCookieInfo:(const CookieInfoList&)cookieInfoList {
1101   // A result of re-ordering of the permissions (crbug.com/444244) is
1102   // that sometimes permissions may not be displayed at all, so it's
1103   // incorrect to check they are set before the cookie info.
1105   // |cookieInfoList| should only ever have 2 items: first- and third-party
1106   // cookies.
1107   DCHECK_EQ(cookieInfoList.size(), 2u);
1108   base::string16 firstPartyLabelText;
1109   base::string16 thirdPartyLabelText;
1110   for (const auto& i : cookieInfoList) {
1111     if (i.is_first_party) {
1112       firstPartyLabelText =
1113           l10n_util::GetStringFUTF16(IDS_WEBSITE_SETTINGS_FIRST_PARTY_SITE_DATA,
1114                                      base::IntToString16(i.allowed));
1115     } else {
1116       thirdPartyLabelText =
1117           l10n_util::GetStringFUTF16(IDS_WEBSITE_SETTINGS_THIRD_PARTY_SITE_DATA,
1118                                      base::IntToString16(i.allowed));
1119     }
1120   }
1122   base::string16 sectionTitle =
1123       l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_TITLE_SITE_DATA);
1124   bool isRTL = base::i18n::RIGHT_TO_LEFT ==
1125                base::i18n::GetStringDirection(firstPartyLabelText);
1127   [cookiesView_ setSubviews:[NSArray array]];
1128   NSPoint controlOrigin = NSMakePoint(kFramePadding, 0);
1130   NSTextField* label;
1132   CGFloat viewWidth = NSWidth([cookiesView_ frame]);
1134   NSTextField* header = [self addText:sectionTitle
1135                              withSize:[NSFont smallSystemFontSize]
1136                                  bold:YES
1137                                toView:cookiesView_
1138                               atPoint:controlOrigin];
1139   [header sizeToFit];
1141   if (isRTL) {
1142     controlOrigin.x = viewWidth - kFramePadding - NSWidth([header frame]);
1143     [header setFrameOrigin:controlOrigin];
1144   }
1145   controlOrigin.y += NSHeight([header frame]) + kPermissionsHeadlineSpacing;
1146   controlOrigin.y += kPermissionsTabSpacing;
1148   // Reset X for the cookie image.
1149   if (isRTL) {
1150     controlOrigin.x = viewWidth - kPermissionImageSize -
1151                       kPermissionImageSpacing - kFramePadding;
1152   }
1154   WebsiteSettingsUI::PermissionInfo info;
1155   info.type = CONTENT_SETTINGS_TYPE_COOKIES;
1156   info.setting = CONTENT_SETTING_ALLOW;
1157   NSImage* image = WebsiteSettingsUI::GetPermissionIcon(info).ToNSImage();
1158   NSImageView* imageView = [self addImageWithSize:[image size]
1159                                            toView:cookiesView_
1160                                           atPoint:controlOrigin];
1161   [imageView setImage:image];
1163   base::string16 comma = base::ASCIIToUTF16(", ");
1164   NSString* cookieButtonText = base::SysUTF16ToNSString(firstPartyLabelText);
1166   if (isRTL) {
1167     NSButton* cookiesButton =
1168         [self addLinkButtonWithText:cookieButtonText toView:cookiesView_];
1169     [cookiesButton setTarget:self];
1170     [cookiesButton setAction:@selector(showCookiesAndSiteData:)];
1171     controlOrigin.x -= NSWidth([cookiesButton frame]);
1172     [cookiesButton setFrameOrigin:controlOrigin];
1174     label = [self addText:comma + thirdPartyLabelText
1175                  withSize:[NSFont smallSystemFontSize]
1176                      bold:NO
1177                    toView:cookiesView_
1178                   atPoint:controlOrigin];
1179     [label sizeToFit];
1180     controlOrigin.x -= NSWidth([label frame]) - kTextLabelXPadding;
1181     [label setFrameOrigin:controlOrigin];
1182   } else {
1183     controlOrigin.x += kPermissionImageSize + kPermissionImageSpacing;
1185     NSButton* cookiesButton =
1186         [self addLinkButtonWithText:cookieButtonText toView:cookiesView_];
1187     [cookiesButton setTarget:self];
1188     [cookiesButton setAction:@selector(showCookiesAndSiteData:)];
1189     [cookiesButton setFrameOrigin:controlOrigin];
1191     controlOrigin.x += NSWidth([cookiesButton frame]) - kTextLabelXPadding;
1193     label = [self addText:comma + thirdPartyLabelText
1194                  withSize:[NSFont smallSystemFontSize]
1195                      bold:NO
1196                    toView:cookiesView_
1197                   atPoint:controlOrigin];
1198     [label sizeToFit];
1199   }
1201   // Align the icon with the text.
1202   [self alignPermissionIcon:imageView withTextField:label];
1204   controlOrigin.y += NSHeight([label frame]) + kPermissionsTabSpacing;
1206   [cookiesView_ setFrameSize:
1207       NSMakeSize(NSWidth([cookiesView_ frame]), controlOrigin.y)];
1209   [self performLayout];
1212 - (void)setPermissionInfo:(const PermissionInfoList&)permissionInfoList {
1213   [permissionsView_ setSubviews:[NSArray array]];
1214   NSPoint controlOrigin = NSMakePoint(kFramePadding, 0);
1216   if (permissionInfoList.size() > 0) {
1217     base::string16 sectionTitle = l10n_util::GetStringUTF16(
1218         IDS_WEBSITE_SETTINGS_TITLE_SITE_PERMISSIONS);
1219     bool isRTL = base::i18n::RIGHT_TO_LEFT ==
1220                  base::i18n::GetStringDirection(sectionTitle);
1221     NSTextField* header = [self addText:sectionTitle
1222                                withSize:[NSFont smallSystemFontSize]
1223                                    bold:YES
1224                                  toView:permissionsView_
1225                                 atPoint:controlOrigin];
1226     [header sizeToFit];
1227     if (isRTL) {
1228       controlOrigin.x = NSWidth([permissionsView_ frame]) - kFramePadding -
1229                         NSWidth([header frame]);
1230       [header setFrameOrigin:controlOrigin];
1231     }
1232     controlOrigin.y += NSHeight([header frame]) + kPermissionsHeadlineSpacing;
1234     for (PermissionInfoList::const_iterator permission =
1235              permissionInfoList.begin();
1236          permission != permissionInfoList.end();
1237          ++permission) {
1238       controlOrigin.y += kPermissionsTabSpacing;
1239       NSPoint rowBottomRight = [self addPermission:*permission
1240                                             toView:permissionsView_
1241                                            atPoint:controlOrigin];
1242       controlOrigin.y = rowBottomRight.y;
1243     }
1244     controlOrigin.y += kFramePadding;
1245   }
1247   [permissionsView_ setFrameSize:
1248       NSMakeSize(NSWidth([permissionsView_ frame]), controlOrigin.y)];
1249   [self performLayout];
1252 - (void)setSelectedTab:(WebsiteSettingsUI::TabId)tabId {
1253   NSInteger index = static_cast<NSInteger>(tabId);
1254   [segmentedControl_ setSelectedSegment:index];
1255   [tabView_ selectTabViewItemAtIndex:index];
1258 @end
1260 WebsiteSettingsUIBridge::WebsiteSettingsUIBridge(
1261     content::WebContents* web_contents)
1262     : content::WebContentsObserver(web_contents),
1263       web_contents_(web_contents),
1264       bubble_controller_(nil) {}
1266 WebsiteSettingsUIBridge::~WebsiteSettingsUIBridge() {
1269 void WebsiteSettingsUIBridge::set_bubble_controller(
1270     WebsiteSettingsBubbleController* controller) {
1271   bubble_controller_ = controller;
1274 void WebsiteSettingsUIBridge::Show(gfx::NativeWindow parent,
1275                                    Profile* profile,
1276                                    content::WebContents* web_contents,
1277                                    const GURL& url,
1278                                    const content::SSLStatus& ssl) {
1279   if (chrome::ToolkitViewsDialogsEnabled()) {
1280     chrome::ShowWebsiteSettingsBubbleViewsAtPoint(
1281         gfx::ScreenPointFromNSPoint(AnchorPointForWindow(parent)), profile,
1282         web_contents, url, ssl);
1283     return;
1284   }
1286   bool is_internal_page = InternalChromePage(url);
1288   // Create the bridge. This will be owned by the bubble controller.
1289   WebsiteSettingsUIBridge* bridge = new WebsiteSettingsUIBridge(web_contents);
1291   // Create the bubble controller. It will dealloc itself when it closes.
1292   WebsiteSettingsBubbleController* bubble_controller =
1293       [[WebsiteSettingsBubbleController alloc]
1294           initWithParentWindow:parent
1295        websiteSettingsUIBridge:bridge
1296                    webContents:web_contents
1297                 isInternalPage:is_internal_page];
1299   if (!is_internal_page) {
1300     // Initialize the presenter, which holds the model and controls the UI.
1301     // This is also owned by the bubble controller.
1302     WebsiteSettings* presenter = new WebsiteSettings(
1303         bridge, profile,
1304         TabSpecificContentSettings::FromWebContents(web_contents), web_contents,
1305         url, ssl, content::CertStore::GetInstance());
1306     [bubble_controller setPresenter:presenter];
1307   }
1309   [bubble_controller showWindow:nil];
1312 void WebsiteSettingsUIBridge::SetIdentityInfo(
1313     const WebsiteSettingsUI::IdentityInfo& identity_info) {
1314   [bubble_controller_ setIdentityInfo:identity_info];
1317 void WebsiteSettingsUIBridge::RenderFrameDeleted(
1318     content::RenderFrameHost* render_frame_host) {
1319   if (render_frame_host == web_contents_->GetMainFrame()) {
1320     [bubble_controller_ close];
1321   }
1324 void WebsiteSettingsUIBridge::SetCookieInfo(
1325     const CookieInfoList& cookie_info_list) {
1326   [bubble_controller_ setCookieInfo:cookie_info_list];
1329 void WebsiteSettingsUIBridge::SetPermissionInfo(
1330     const PermissionInfoList& permission_info_list) {
1331   [bubble_controller_ setPermissionInfo:permission_info_list];
1334 void WebsiteSettingsUIBridge::SetSelectedTab(TabId tab_id) {
1335   [bubble_controller_ setSelectedTab:tab_id];