[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / content_settings / content_setting_bubble_cocoa.mm
blobb9ec1a36c2a08864cf304c81937233a0b2f58356
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/stl_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/content_settings/host_content_settings_map.h"
13 #include "chrome/browser/plugins/plugin_finder.h"
14 #include "chrome/browser/plugins/plugin_metadata.h"
15 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
16 #import "chrome/browser/ui/cocoa/l10n_util.h"
17 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
18 #include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
19 #include "content/public/browser/plugin_service.h"
20 #include "grit/generated_resources.h"
21 #include "skia/ext/skia_utils_mac.h"
22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
24 #include "ui/base/l10n/l10n_util.h"
26 using content::PluginService;
28 namespace {
30 // Height of one link in the popup list.
31 const int kLinkHeight = 16;
33 // Space between two popup links.
34 const int kLinkPadding = 4;
36 // Space taken in total by one popup link.
37 const int kLinkLineHeight = kLinkHeight + kLinkPadding;
39 // Space between popup list and surrounding UI elements.
40 const int kLinkOuterPadding = 8;
42 // Height of each of the labels in the geolocation bubble.
43 const int kGeoLabelHeight = 14;
45 // Height of the "Clear" button in the geolocation bubble.
46 const int kGeoClearButtonHeight = 17;
48 // General padding between elements in the geolocation bubble.
49 const int kGeoPadding = 8;
51 // Padding between host names in the geolocation bubble.
52 const int kGeoHostPadding = 4;
54 // Minimal padding between "Manage" and "Done" buttons.
55 const int kManageDonePadding = 8;
57 // Padding between radio buttons and media menus buttons in the media bubble.
58 const int kMediaMenuVerticalPadding = 25;
60 // Padding between media menu elements in the media bubble.
61 const int kMediaMenuElementVerticalPadding = 5;
63 // The amount of horizontal space between the media menu title and the border.
64 const int kMediaMenuTitleHorizontalPadding = 10;
66 // The minimum width of the media menu buttons.
67 const CGFloat kMinMediaMenuButtonWidth = 100;
69 // Height of each of the labels in the MIDI bubble.
70 const int kMIDISysExLabelHeight = 14;
72 // Height of the "Clear" button in the MIDI bubble.
73 const int kMIDISysExClearButtonHeight = 17;
75 // General padding between elements in the MIDI bubble.
76 const int kMIDISysExPadding = 8;
78 // Padding between host names in the MIDI bubble.
79 const int kMIDISysExHostPadding = 4;
81 void SetControlSize(NSControl* control, NSControlSize controlSize) {
82   CGFloat fontSize = [NSFont systemFontSizeForControlSize:controlSize];
83   NSCell* cell = [control cell];
84   NSFont* font = [NSFont fontWithName:[[cell font] fontName] size:fontSize];
85   [cell setFont:font];
86   [cell setControlSize:controlSize];
89 // Returns an autoreleased NSTextField that is configured to look like a Label
90 // looks in Interface Builder.
91 NSTextField* LabelWithFrame(NSString* text, const NSRect& frame) {
92   NSTextField* label = [[NSTextField alloc] initWithFrame:frame];
93   [label setStringValue:text];
94   [label setSelectable:NO];
95   [label setBezeled:NO];
96   return [label autorelease];
99 // Sets the title for the popup button.
100 void SetTitleForPopUpButton(NSPopUpButton* button, NSString* title) {
101   base::scoped_nsobject<NSMenuItem> titleItem([[NSMenuItem alloc] init]);
102   [titleItem setTitle:title];
103   [[button cell] setUsesItemFromMenu:NO];
104   [[button cell] setMenuItem:titleItem.get()];
107 // Builds the popup button menu from the menu model and returns the width of the
108 // longgest item as the width of the popup menu.
109 CGFloat BuildPopUpMenuFromModel(NSPopUpButton* button,
110                                 ContentSettingMediaMenuModel* model,
111                                 const std::string& title,
112                                 bool disabled) {
113   [[button cell] setControlSize:NSSmallControlSize];
114   [[button cell] setArrowPosition:NSPopUpArrowAtBottom];
115   [button setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
116   [button setButtonType:NSMomentaryPushInButton];
117   [button setAlignment:NSLeftTextAlignment];
118   [button setAutoresizingMask:NSViewMinXMargin];
119   [button setAction:@selector(mediaMenuChanged:)];
120   [button sizeToFit];
122   CGFloat menuWidth = 0;
123   for (int i = 0; i < model->GetItemCount(); ++i) {
124     NSString* itemTitle =
125         base::SysUTF16ToNSString(model->GetLabelAt(i));
126     [button addItemWithTitle:itemTitle];
127     [[button lastItem] setTag:i];
129     if (UTF16ToUTF8(model->GetLabelAt(i)) == title)
130       [button selectItemWithTag:i];
132     // Determine the largest possible size for this button.
133     NSDictionary* textAttributes =
134         [NSDictionary dictionaryWithObject:[button font]
135                                     forKey:NSFontAttributeName];
136     NSSize size = [itemTitle sizeWithAttributes:textAttributes];
137     NSRect buttonFrame = [button frame];
138     NSRect titleRect = [[button cell] titleRectForBounds:buttonFrame];
139     CGFloat width = size.width + NSWidth(buttonFrame) - NSWidth(titleRect) +
140         kMediaMenuTitleHorizontalPadding;
141     menuWidth = std::max(menuWidth, width);
142   }
144   if (!model->GetItemCount()) {
145     // Show a "None available" title and grey out the menu when there is no
146     // available device.
147     SetTitleForPopUpButton(
148         button, l10n_util::GetNSString(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
149     [button setEnabled:NO];
150   } else {
151     SetTitleForPopUpButton(button, base::SysUTF8ToNSString(title));
153     // Disable the device selection when the website is managing the devices
154     // itself.
155     if (disabled)
156       [button setEnabled:NO];
157   }
159   return menuWidth;
162 }  // namespace
164 namespace content_setting_bubble {
166 MediaMenuParts::MediaMenuParts(content::MediaStreamType type,
167                                NSTextField* label)
168     : type(type),
169       label(label) {}
170 MediaMenuParts::~MediaMenuParts() {}
172 }  // namespace content_setting_bubble
174 @interface ContentSettingBubbleController(Private)
175 - (id)initWithModel:(ContentSettingBubbleModel*)settingsBubbleModel
176        parentWindow:(NSWindow*)parentWindow
177          anchoredAt:(NSPoint)anchoredAt;
178 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
179                                 title:(NSString*)title
180                                  icon:(NSImage*)icon
181                        referenceFrame:(NSRect)referenceFrame;
182 - (void)initializeBlockedPluginsList;
183 - (void)initializeTitle;
184 - (void)initializeRadioGroup;
185 - (void)initializePopupList;
186 - (void)initializeGeoLists;
187 - (void)initializeMediaMenus;
188 - (void)initializeMIDISysExLists;
189 - (void)sizeToFitLoadButton;
190 - (void)initManageDoneButtons;
191 - (void)removeInfoButton;
192 - (void)popupLinkClicked:(id)sender;
193 - (void)clearGeolocationForCurrentHost:(id)sender;
194 - (void)clearMIDISysExForCurrentHost:(id)sender;
195 @end
197 @implementation ContentSettingBubbleController
199 + (ContentSettingBubbleController*)
200     showForModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
201     parentWindow:(NSWindow*)parentWindow
202       anchoredAt:(NSPoint)anchor {
203   // Autoreleases itself on bubble close.
204   return [[ContentSettingBubbleController alloc]
205              initWithModel:contentSettingBubbleModel
206               parentWindow:parentWindow
207                 anchoredAt:anchor];
210 - (id)initWithModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
211        parentWindow:(NSWindow*)parentWindow
212          anchoredAt:(NSPoint)anchoredAt {
213   // This method takes ownership of |contentSettingBubbleModel| in all cases.
214   scoped_ptr<ContentSettingBubbleModel> model(contentSettingBubbleModel);
215   DCHECK(model.get());
217   ContentSettingsType settingsType = model->content_type();
218   NSString* nibPath = @"";
219   switch (settingsType) {
220     case CONTENT_SETTINGS_TYPE_COOKIES:
221       nibPath = @"ContentBlockedCookies"; break;
222     case CONTENT_SETTINGS_TYPE_IMAGES:
223     case CONTENT_SETTINGS_TYPE_JAVASCRIPT:
224     case CONTENT_SETTINGS_TYPE_PPAPI_BROKER:
225       nibPath = @"ContentBlockedSimple"; break;
226     case CONTENT_SETTINGS_TYPE_PLUGINS:
227       nibPath = @"ContentBlockedPlugins"; break;
228     case CONTENT_SETTINGS_TYPE_POPUPS:
229       nibPath = @"ContentBlockedPopups"; break;
230     case CONTENT_SETTINGS_TYPE_GEOLOCATION:
231       nibPath = @"ContentBlockedGeolocation"; break;
232     case CONTENT_SETTINGS_TYPE_MIXEDSCRIPT:
233       nibPath = @"ContentBlockedMixedScript"; break;
234     case CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS:
235       nibPath = @"ContentProtocolHandlers"; break;
236     case CONTENT_SETTINGS_TYPE_MEDIASTREAM:
237       nibPath = @"ContentBlockedMedia"; break;
238     case CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS:
239       nibPath = @"ContentBlockedDownloads"; break;
240     case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
241       nibPath = @"ContentBlockedMIDISysEx"; break;
242     // These content types have no bubble:
243     case CONTENT_SETTINGS_TYPE_DEFAULT:
244     case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
245     case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE:
246     case CONTENT_SETTINGS_TYPE_FULLSCREEN:
247     case CONTENT_SETTINGS_TYPE_MOUSELOCK:
248     case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
249     case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
250     case CONTENT_SETTINGS_NUM_TYPES:
251       NOTREACHED();
252   }
253   if ((self = [super initWithWindowNibPath:nibPath
254                               parentWindow:parentWindow
255                                 anchoredAt:anchoredAt])) {
256     contentSettingBubbleModel_.reset(model.release());
257     [self showWindow:nil];
258   }
259   return self;
262 - (void)dealloc {
263   STLDeleteValues(&mediaMenus_);
264   [super dealloc];
267 - (void)initializeTitle {
268   if (!titleLabel_)
269     return;
271   NSString* label = base::SysUTF8ToNSString(
272       contentSettingBubbleModel_->bubble_content().title);
273   [titleLabel_ setStringValue:label];
275   // Layout title post-localization.
276   CGFloat deltaY = [GTMUILocalizerAndLayoutTweaker
277       sizeToFitFixedWidthTextField:titleLabel_];
278   NSRect windowFrame = [[self window] frame];
279   windowFrame.size.height += deltaY;
280   [[self window] setFrame:windowFrame display:NO];
281   NSRect titleFrame = [titleLabel_ frame];
282   titleFrame.origin.y -= deltaY;
283   [titleLabel_ setFrame:titleFrame];
286 - (void)initializeRadioGroup {
287   // NOTE! Tags in the xib files must match the order of the radio buttons
288   // passed in the radio_group and be 1-based, not 0-based.
289   const ContentSettingBubbleModel::RadioGroup& radio_group =
290       contentSettingBubbleModel_->bubble_content().radio_group;
292   // Select appropriate radio button.
293   [allowBlockRadioGroup_ selectCellWithTag: radio_group.default_item + 1];
295   const ContentSettingBubbleModel::RadioItems& radio_items =
296       radio_group.radio_items;
297   for (size_t ii = 0; ii < radio_group.radio_items.size(); ++ii) {
298     NSCell* radioCell = [allowBlockRadioGroup_ cellWithTag: ii + 1];
299     [radioCell setTitle:base::SysUTF8ToNSString(radio_items[ii])];
300   }
302   // Layout radio group labels post-localization.
303   [GTMUILocalizerAndLayoutTweaker
304       wrapRadioGroupForWidth:allowBlockRadioGroup_];
305   CGFloat radioDeltaY = [GTMUILocalizerAndLayoutTweaker
306       sizeToFitView:allowBlockRadioGroup_].height;
307   NSRect windowFrame = [[self window] frame];
308   windowFrame.size.height += radioDeltaY;
309   [[self window] setFrame:windowFrame display:NO];
312 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
313                                 title:(NSString*)title
314                                  icon:(NSImage*)icon
315                        referenceFrame:(NSRect)referenceFrame {
316   base::scoped_nsobject<HyperlinkButtonCell> cell(
317       [[HyperlinkButtonCell alloc] initTextCell:title]);
318   [cell.get() setAlignment:NSNaturalTextAlignment];
319   if (icon) {
320     [cell.get() setImagePosition:NSImageLeft];
321     [cell.get() setImage:icon];
322   } else {
323     [cell.get() setImagePosition:NSNoImage];
324   }
325   [cell.get() setControlSize:NSSmallControlSize];
327   NSButton* button = [[[NSButton alloc] initWithFrame:frame] autorelease];
328   // Cell must be set immediately after construction.
329   [button setCell:cell.get()];
331   // Size to fit the button and add a little extra padding for the small-text
332   // hyperlink button, which sizeToFit gets wrong.
333   [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
334   NSRect buttonFrame = [button frame];
335   buttonFrame.size.width += 2;
337   // If the link text is too long, clamp it.
338   int maxWidth = NSWidth([[self bubble] frame]) - 2 * NSMinX(referenceFrame);
339   if (NSWidth(buttonFrame) > maxWidth)
340     buttonFrame.size.width = maxWidth;
342   [button setFrame:buttonFrame];
343   [button setTarget:self];
344   [button setAction:@selector(popupLinkClicked:)];
345   return button;
348 - (void)initializeBlockedPluginsList {
349   int delta = NSMinY([titleLabel_ frame]) -
350               NSMinY([blockedResourcesField_ frame]);
351   [blockedResourcesField_ removeFromSuperview];
352   NSRect frame = [[self window] frame];
353   frame.size.height -= delta;
354   [[self window] setFrame:frame display:NO];
357 - (void)initializePopupList {
358   // I didn't put the buttons into a NSMatrix because then they are only one
359   // entity in the key view loop. This way, one can tab through all of them.
360   const ContentSettingBubbleModel::PopupItems& popupItems =
361       contentSettingBubbleModel_->bubble_content().popup_items;
363   // Get the pre-resize frame of the radio group. Its origin is where the
364   // popup list should go.
365   NSRect radioFrame = [allowBlockRadioGroup_ frame];
367   // Make room for the popup list. The bubble view and its subviews autosize
368   // themselves when the window is enlarged.
369   // Heading and radio box are already 1 * kLinkOuterPadding apart in the nib,
370   // so only 1 * kLinkOuterPadding more is needed.
371   int delta = popupItems.size() * kLinkLineHeight - kLinkPadding +
372               kLinkOuterPadding;
373   NSSize deltaSize = NSMakeSize(0, delta);
374   deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
375   NSRect windowFrame = [[self window] frame];
376   windowFrame.size.height += deltaSize.height;
377   [[self window] setFrame:windowFrame display:NO];
379   // Create popup list.
380   int topLinkY = NSMaxY(radioFrame) + delta - kLinkHeight;
381   int row = 0;
382   for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
383        it(popupItems.begin()); it != popupItems.end(); ++it, ++row) {
384     NSImage* image = it->image.AsNSImage();
386     std::string title(it->title);
387     // The popup may not have committed a load yet, in which case it won't
388     // have a URL or title.
389     if (title.empty())
390       title = l10n_util::GetStringUTF8(IDS_TAB_LOADING_TITLE);
392     NSRect linkFrame =
393         NSMakeRect(NSMinX(radioFrame), topLinkY - kLinkLineHeight * row,
394                    200, kLinkHeight);
395     NSButton* button = [self
396         hyperlinkButtonWithFrame:linkFrame
397                            title:base::SysUTF8ToNSString(title)
398                             icon:image
399                   referenceFrame:radioFrame];
400     [[self bubble] addSubview:button];
401     popupLinks_[button] = row;
402   }
405 - (void)initializeGeoLists {
406   // Cocoa has its origin in the lower left corner. This means elements are
407   // added from bottom to top, which explains why loops run backwards and the
408   // order of operations is the other way than on Linux/Windows.
409   const ContentSettingBubbleModel::BubbleContent& content =
410       contentSettingBubbleModel_->bubble_content();
411   NSRect containerFrame = [contentsContainer_ frame];
412   NSRect frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
414   // "Clear" button / text field.
415   if (!content.custom_link.empty()) {
416     base::scoped_nsobject<NSControl> control;
417     if(content.custom_link_enabled) {
418       NSRect buttonFrame = NSMakeRect(0, 0,
419                                       NSWidth(containerFrame),
420                                       kGeoClearButtonHeight);
421       NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
422       control.reset(button);
423       [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
424       [button setTarget:self];
425       [button setAction:@selector(clearGeolocationForCurrentHost:)];
426       [button setBezelStyle:NSRoundRectBezelStyle];
427       SetControlSize(button, NSSmallControlSize);
428       [button sizeToFit];
429     } else {
430       // Add the notification that settings will be cleared on next reload.
431       control.reset([LabelWithFrame(
432           base::SysUTF8ToNSString(content.custom_link), frame) retain]);
433       SetControlSize(control.get(), NSSmallControlSize);
434     }
436     // If the new control is wider than the container, widen the window.
437     CGFloat controlWidth = NSWidth([control frame]);
438     if (controlWidth > NSWidth(containerFrame)) {
439       NSRect windowFrame = [[self window] frame];
440       windowFrame.size.width += controlWidth - NSWidth(containerFrame);
441       [[self window] setFrame:windowFrame display:NO];
442       // Fetch the updated sizes.
443       containerFrame = [contentsContainer_ frame];
444       frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
445     }
447     DCHECK(control);
448     [contentsContainer_ addSubview:control];
449     frame.origin.y = NSMaxY([control frame]) + kGeoPadding;
450   }
452   for (auto i = content.domain_lists.rbegin();
453        i != content.domain_lists.rend(); ++i) {
454     // Add all hosts in the current domain list.
455     for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
456       NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
457       SetControlSize(title, NSSmallControlSize);
458       [contentsContainer_ addSubview:title];
460       frame.origin.y = NSMaxY(frame) + kGeoHostPadding +
461           [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
462     }
463     if (!i->hosts.empty())
464       frame.origin.y += kGeoPadding - kGeoHostPadding;
466     // Add the domain list's title.
467     NSTextField* title =
468         LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
469     SetControlSize(title, NSSmallControlSize);
470     [contentsContainer_ addSubview:title];
472     frame.origin.y = NSMaxY(frame) + kGeoPadding +
473         [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
474   }
476   CGFloat containerHeight = frame.origin.y;
477   // Undo last padding.
478   if (!content.domain_lists.empty())
479     containerHeight -= kGeoPadding;
481   // Resize container to fit its subviews, and window to fit the container.
482   NSRect windowFrame = [[self window] frame];
483   windowFrame.size.height += containerHeight - NSHeight(containerFrame);
484   [[self window] setFrame:windowFrame display:NO];
485   containerFrame.size.height = containerHeight;
486   [contentsContainer_ setFrame:containerFrame];
489 - (void)initializeMediaMenus {
490   const ContentSettingBubbleModel::MediaMenuMap& media_menus =
491       contentSettingBubbleModel_->bubble_content().media_menus;
493   // Calculate the longest width of the labels and menus menus to avoid
494   // truncation by the window's edge.
495   CGFloat maxLabelWidth = 0;
496   CGFloat maxMenuWidth = 0;
497   CGFloat maxMenuHeight = 0;
498   NSRect radioFrame = [allowBlockRadioGroup_ frame];
499   for (ContentSettingBubbleModel::MediaMenuMap::const_iterator it(
500        media_menus.begin()); it != media_menus.end(); ++it) {
501     // |labelFrame| will be resized later on in this function.
502     NSRect labelFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
503     NSTextField* label =
504         LabelWithFrame(base::SysUTF8ToNSString(it->second.label), labelFrame);
505     SetControlSize(label, NSSmallControlSize);
506     NSCell* cell = [label cell];
507     [cell setAlignment:NSRightTextAlignment];
508     [GTMUILocalizerAndLayoutTweaker sizeToFitView:label];
509     maxLabelWidth = std::max(maxLabelWidth, [label frame].size.width);
510     [[self bubble] addSubview:label];
512     // |buttonFrame| will be resized and repositioned later on.
513     NSRect buttonFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
514     base::scoped_nsobject<NSPopUpButton> button(
515         [[NSPopUpButton alloc] initWithFrame:buttonFrame]);
516     [button setTarget:self];
518     // Store the |label| and |button| into MediaMenuParts struct and build
519     // the popup menu from the menu model.
520     content_setting_bubble::MediaMenuParts* menuParts =
521         new content_setting_bubble::MediaMenuParts(it->first, label);
522     menuParts->model.reset(new ContentSettingMediaMenuModel(
523         it->first, contentSettingBubbleModel_.get(),
524         ContentSettingMediaMenuModel::MenuLabelChangedCallback()));
525     mediaMenus_[button] = menuParts;
526     CGFloat width = BuildPopUpMenuFromModel(button,
527                                             menuParts->model.get(),
528                                             it->second.selected_device.name,
529                                             it->second.disabled);
530     maxMenuWidth = std::max(maxMenuWidth, width);
532     [[self bubble] addSubview:button
533                    positioned:NSWindowBelow
534                    relativeTo:nil];
536     maxMenuHeight = std::max(maxMenuHeight, [button frame].size.height);
537   }
539   // Make room for the media menu(s) and enlarge the windows to fit the views.
540   // The bubble view and its subviews autosize themselves when the window is
541   // enlarged.
542   int delta = media_menus.size() * maxMenuHeight +
543       (media_menus.size() - 1) * kMediaMenuElementVerticalPadding;
544   NSSize deltaSize = NSMakeSize(0, delta);
545   deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
546   NSRect windowFrame = [[self window] frame];
547   windowFrame.size.height += deltaSize.height;
548   // If the media menus are wider than the window, widen the window.
549   CGFloat widthNeeded = maxLabelWidth + maxMenuWidth + 2 * NSMinX(radioFrame);
550   if (widthNeeded > windowFrame.size.width)
551     windowFrame.size.width = widthNeeded;
552   [[self window] setFrame:windowFrame display:NO];
554   // The radio group lies above the media menus, move the radio group up.
555   radioFrame.origin.y += delta;
556   [allowBlockRadioGroup_ setFrame:radioFrame];
558   // Resize and reposition the media menus layout.
559   CGFloat topMenuY = NSMinY(radioFrame) - kMediaMenuVerticalPadding;
560   maxMenuWidth = std::max(maxMenuWidth, kMinMediaMenuButtonWidth);
561   for (content_setting_bubble::MediaMenuPartsMap::const_iterator i =
562        mediaMenus_.begin(); i != mediaMenus_.end(); ++i) {
563     NSRect labelFrame = [i->second->label frame];
564     // Align the label text with the button text.
565     labelFrame.origin.y =
566         topMenuY + (maxMenuHeight - labelFrame.size.height) / 2 + 1;
567     labelFrame.size.width = maxLabelWidth;
568     [i->second->label setFrame:labelFrame];
569     NSRect menuFrame = [i->first frame];
570     menuFrame.origin.y = topMenuY;
571     menuFrame.origin.x = NSMinX(radioFrame) + maxLabelWidth;
572     menuFrame.size.width = maxMenuWidth;
573     menuFrame.size.height = maxMenuHeight;
574     [i->first setFrame:menuFrame];
575     topMenuY -= (maxMenuHeight + kMediaMenuElementVerticalPadding);
576   }
579 - (void)initializeMIDISysExLists {
580   const ContentSettingBubbleModel::BubbleContent& content =
581       contentSettingBubbleModel_->bubble_content();
582   NSRect containerFrame = [contentsContainer_ frame];
583   NSRect frame =
584       NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
586   // "Clear" button / text field.
587   if (!content.custom_link.empty()) {
588     base::scoped_nsobject<NSControl> control;
589     if (content.custom_link_enabled) {
590       NSRect buttonFrame = NSMakeRect(0, 0,
591                                       NSWidth(containerFrame),
592                                       kMIDISysExClearButtonHeight);
593       NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
594       control.reset(button);
595       [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
596       [button setTarget:self];
597       [button setAction:@selector(clearMIDISysExForCurrentHost:)];
598       [button setBezelStyle:NSRoundRectBezelStyle];
599       SetControlSize(button, NSSmallControlSize);
600       [button sizeToFit];
601     } else {
602       // Add the notification that settings will be cleared on next reload.
603       control.reset([LabelWithFrame(
604           base::SysUTF8ToNSString(content.custom_link), frame) retain]);
605       SetControlSize(control.get(), NSSmallControlSize);
606     }
608     // If the new control is wider than the container, widen the window.
609     CGFloat controlWidth = NSWidth([control frame]);
610     if (controlWidth > NSWidth(containerFrame)) {
611       NSRect windowFrame = [[self window] frame];
612       windowFrame.size.width += controlWidth - NSWidth(containerFrame);
613       [[self window] setFrame:windowFrame display:NO];
614       // Fetch the updated sizes.
615       containerFrame = [contentsContainer_ frame];
616       frame = NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
617     }
619     DCHECK(control);
620     [contentsContainer_ addSubview:control];
621     frame.origin.y = NSMaxY([control frame]) + kMIDISysExPadding;
622   }
624   for (auto i = content.domain_lists.rbegin();
625        i != content.domain_lists.rend(); ++i) {
626     // Add all hosts in the current domain list.
627     for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
628       NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
629       SetControlSize(title, NSSmallControlSize);
630       [contentsContainer_ addSubview:title];
632       frame.origin.y = NSMaxY(frame) + kMIDISysExHostPadding +
633           [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
634     }
635     if (!i->hosts.empty())
636       frame.origin.y += kMIDISysExPadding - kMIDISysExHostPadding;
638     // Add the domain list's title.
639     NSTextField* title =
640         LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
641     SetControlSize(title, NSSmallControlSize);
642     [contentsContainer_ addSubview:title];
644     frame.origin.y = NSMaxY(frame) + kMIDISysExPadding +
645         [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
646   }
648   CGFloat containerHeight = frame.origin.y;
649   // Undo last padding.
650   if (!content.domain_lists.empty())
651     containerHeight -= kMIDISysExPadding;
653   // Resize container to fit its subviews, and window to fit the container.
654   NSRect windowFrame = [[self window] frame];
655   windowFrame.size.height += containerHeight - NSHeight(containerFrame);
656   [[self window] setFrame:windowFrame display:NO];
657   containerFrame.size.height = containerHeight;
658   [contentsContainer_ setFrame:containerFrame];
661 - (void)sizeToFitLoadButton {
662   const ContentSettingBubbleModel::BubbleContent& content =
663       contentSettingBubbleModel_->bubble_content();
664   [loadButton_ setEnabled:content.custom_link_enabled];
666   // Resize horizontally to fit button if necessary.
667   NSRect windowFrame = [[self window] frame];
668   int widthNeeded = NSWidth([loadButton_ frame]) +
669       2 * NSMinX([loadButton_ frame]);
670   if (NSWidth(windowFrame) < widthNeeded) {
671     windowFrame.size.width = widthNeeded;
672     [[self window] setFrame:windowFrame display:NO];
673   }
676 - (void)initManageDoneButtons {
677   const ContentSettingBubbleModel::BubbleContent& content =
678       contentSettingBubbleModel_->bubble_content();
679   [manageButton_ setTitle:base::SysUTF8ToNSString(content.manage_link)];
680   [GTMUILocalizerAndLayoutTweaker sizeToFitView:manageButton_];
682   CGFloat actualWidth = NSWidth([[[self window] contentView] frame]);
683   CGFloat requiredWidth = NSMaxX([manageButton_ frame]) + kManageDonePadding +
684       NSWidth([[doneButton_ superview] frame]) - NSMinX([doneButton_ frame]);
685   if (requiredWidth <= actualWidth || !doneButton_ || !manageButton_)
686     return;
688   // Resize window, autoresizing takes care of the rest.
689   NSSize size = NSMakeSize(requiredWidth - actualWidth, 0);
690   size = [[[self window] contentView] convertSize:size toView:nil];
691   NSRect frame = [[self window] frame];
692   frame.origin.x -= size.width;
693   frame.size.width += size.width;
694   [[self window] setFrame:frame display:NO];
697 - (void)awakeFromNib {
698   [super awakeFromNib];
700   [[self bubble] setArrowLocation:info_bubble::kTopRight];
702   // Adapt window size to bottom buttons. Do this before all other layouting.
703   [self initManageDoneButtons];
705   [self initializeTitle];
707   ContentSettingsType type = contentSettingBubbleModel_->content_type();
708   if (type == CONTENT_SETTINGS_TYPE_PLUGINS) {
709     [self sizeToFitLoadButton];
710     [self initializeBlockedPluginsList];
711   }
713   if (allowBlockRadioGroup_)  // not bound in cookie bubble xib
714     [self initializeRadioGroup];
716   if (type == CONTENT_SETTINGS_TYPE_POPUPS)
717     [self initializePopupList];
718   if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION)
719     [self initializeGeoLists];
720   if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM)
721     [self initializeMediaMenus];
722   if (type == CONTENT_SETTINGS_TYPE_MIDI_SYSEX)
723     [self initializeMIDISysExLists];
726 ///////////////////////////////////////////////////////////////////////////////
727 // Actual application logic
729 - (IBAction)allowBlockToggled:(id)sender {
730   NSButtonCell *selectedCell = [sender selectedCell];
731   contentSettingBubbleModel_->OnRadioClicked([selectedCell tag] - 1);
734 - (void)popupLinkClicked:(id)sender {
735   content_setting_bubble::PopupLinks::iterator i(popupLinks_.find(sender));
736   DCHECK(i != popupLinks_.end());
737   contentSettingBubbleModel_->OnPopupClicked(i->second);
740 - (void)clearGeolocationForCurrentHost:(id)sender {
741   contentSettingBubbleModel_->OnCustomLinkClicked();
742   [self close];
745 - (void)clearMIDISysExForCurrentHost:(id)sender {
746   contentSettingBubbleModel_->OnCustomLinkClicked();
747   [self close];
750 - (IBAction)showMoreInfo:(id)sender {
751   contentSettingBubbleModel_->OnCustomLinkClicked();
752   [self close];
755 - (IBAction)load:(id)sender {
756   contentSettingBubbleModel_->OnCustomLinkClicked();
757   [self close];
760 - (IBAction)learnMoreLinkClicked:(id)sender {
761   contentSettingBubbleModel_->OnManageLinkClicked();
764 - (IBAction)manageBlocking:(id)sender {
765   contentSettingBubbleModel_->OnManageLinkClicked();
768 - (IBAction)closeBubble:(id)sender {
769   contentSettingBubbleModel_->OnDoneClicked();
770   [self close];
773 - (IBAction)mediaMenuChanged:(id)sender {
774   NSPopUpButton* button = static_cast<NSPopUpButton*>(sender);
775   content_setting_bubble::MediaMenuPartsMap::const_iterator it(
776       mediaMenus_.find(sender));
777   DCHECK(it != mediaMenus_.end());
778   NSInteger index = [[button selectedItem] tag];
780   SetTitleForPopUpButton(
781       button, base::SysUTF16ToNSString(it->second->model->GetLabelAt(index)));
783   it->second->model->ExecuteCommand(index, 0);
786 - (content_setting_bubble::MediaMenuPartsMap*)mediaMenus {
787   return &mediaMenus_;
790 @end  // ContentSettingBubbleController