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/plugins/plugin_finder.h"
13 #include "chrome/browser/plugins/plugin_metadata.h"
14 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
15 #import "chrome/browser/ui/cocoa/l10n_util.h"
16 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
17 #include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "components/content_settings/core/browser/host_content_settings_map.h"
20 #include "content/public/browser/plugin_service.h"
21 #include "content/public/browser/web_contents_observer.h"
22 #include "skia/ext/skia_utils_mac.h"
23 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
24 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
25 #include "ui/base/l10n/l10n_util.h"
27 using content::PluginService;
31 // Height of one link in the popup list.
32 const int kLinkHeight = 16;
34 // Space between two popup links.
35 const int kLinkPadding = 4;
37 // Space taken in total by one popup link.
38 const int kLinkLineHeight = kLinkHeight + kLinkPadding;
40 // Space between popup list and surrounding UI elements.
41 const int kLinkOuterPadding = 8;
43 // Height of each of the labels in the geolocation bubble.
44 const int kGeoLabelHeight = 14;
46 // Height of the "Clear" button in the geolocation bubble.
47 const int kGeoClearButtonHeight = 17;
49 // General padding between elements in the geolocation bubble.
50 const int kGeoPadding = 8;
52 // Padding between host names in the geolocation bubble.
53 const int kGeoHostPadding = 4;
55 // Minimal padding between "Manage" and "Done" buttons.
56 const int kManageDonePadding = 8;
58 // Padding between radio buttons and media menus buttons in the media bubble.
59 const int kMediaMenuVerticalPadding = 25;
61 // Padding between media menu elements in the media bubble.
62 const int kMediaMenuElementVerticalPadding = 5;
64 // The amount of horizontal space between the media menu title and the border.
65 const int kMediaMenuTitleHorizontalPadding = 10;
67 // The minimum width of the media menu buttons.
68 const CGFloat kMinMediaMenuButtonWidth = 100;
70 // Height of each of the labels in the MIDI bubble.
71 const int kMIDISysExLabelHeight = 14;
73 // Height of the "Clear" button in the MIDI bubble.
74 const int kMIDISysExClearButtonHeight = 17;
76 // General padding between elements in the MIDI bubble.
77 const int kMIDISysExPadding = 8;
79 // Padding between host names in the MIDI bubble.
80 const int kMIDISysExHostPadding = 4;
82 void SetControlSize(NSControl* control, NSControlSize controlSize) {
83 CGFloat fontSize = [NSFont systemFontSizeForControlSize:controlSize];
84 NSCell* cell = [control cell];
85 [cell setFont:[NSFont systemFontOfSize:fontSize]];
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,
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:)];
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);
144 if (!model->GetItemCount()) {
145 // Show a "None available" title and grey out the menu when there is no
147 SetTitleForPopUpButton(
148 button, l10n_util::GetNSString(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
149 [button setEnabled:NO];
151 SetTitleForPopUpButton(button, base::SysUTF8ToNSString(title));
153 // Disable the device selection when the website is managing the devices
156 [button setEnabled:NO];
164 namespace content_setting_bubble {
166 MediaMenuParts::MediaMenuParts(content::MediaStreamType type,
170 MediaMenuParts::~MediaMenuParts() {}
172 } // namespace content_setting_bubble
174 class ContentSettingBubbleWebContentsObserverBridge
175 : public content::WebContentsObserver {
177 ContentSettingBubbleWebContentsObserverBridge(
178 content::WebContents* web_contents,
179 ContentSettingBubbleController* controller)
180 : content::WebContentsObserver(web_contents),
181 controller_(controller) {
185 // WebContentsObserver:
186 void DidNavigateMainFrame(
187 const content::LoadCommittedDetails& details,
188 const content::FrameNavigateParams& params) override {
189 // Content settings are based on the main frame, so if it switches then
191 [controller_ closeBubble:nil];
195 ContentSettingBubbleController* controller_; // weak
197 DISALLOW_COPY_AND_ASSIGN(ContentSettingBubbleWebContentsObserverBridge);
200 @interface ContentSettingBubbleController(Private)
201 - (id)initWithModel:(ContentSettingBubbleModel*)settingsBubbleModel
202 webContents:(content::WebContents*)webContents
203 parentWindow:(NSWindow*)parentWindow
204 anchoredAt:(NSPoint)anchoredAt;
205 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
206 title:(NSString*)title
208 referenceFrame:(NSRect)referenceFrame;
209 - (void)initializeBlockedPluginsList;
210 - (void)initializeTitle;
211 - (void)initializeRadioGroup;
212 - (void)initializeItemList;
213 - (void)initializeGeoLists;
214 - (void)initializeMediaMenus;
215 - (void)initializeMIDISysExLists;
216 - (void)sizeToFitLoadButton;
217 - (void)initManageDoneButtons;
218 - (void)removeInfoButton;
219 - (void)popupLinkClicked:(id)sender;
220 - (void)clearGeolocationForCurrentHost:(id)sender;
221 - (void)clearMIDISysExForCurrentHost:(id)sender;
224 @implementation ContentSettingBubbleController
226 + (ContentSettingBubbleController*)
227 showForModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
228 webContents:(content::WebContents*)webContents
229 parentWindow:(NSWindow*)parentWindow
230 anchoredAt:(NSPoint)anchor {
231 // Autoreleases itself on bubble close.
232 return [[ContentSettingBubbleController alloc]
233 initWithModel:contentSettingBubbleModel
234 webContents:webContents
235 parentWindow:parentWindow
239 struct ContentTypeToNibPath {
240 ContentSettingsType type;
244 const ContentTypeToNibPath kNibPaths[] = {
245 {CONTENT_SETTINGS_TYPE_COOKIES, @"ContentBlockedCookies"},
246 {CONTENT_SETTINGS_TYPE_IMAGES, @"ContentBlockedSimple"},
247 {CONTENT_SETTINGS_TYPE_JAVASCRIPT, @"ContentBlockedSimple"},
248 {CONTENT_SETTINGS_TYPE_PPAPI_BROKER, @"ContentBlockedSimple"},
249 {CONTENT_SETTINGS_TYPE_PLUGINS, @"ContentBlockedPlugins"},
250 {CONTENT_SETTINGS_TYPE_POPUPS, @"ContentBlockedPopups"},
251 {CONTENT_SETTINGS_TYPE_GEOLOCATION, @"ContentBlockedGeolocation"},
252 {CONTENT_SETTINGS_TYPE_MIXEDSCRIPT, @"ContentBlockedMixedScript"},
253 {CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS, @"ContentProtocolHandlers"},
254 {CONTENT_SETTINGS_TYPE_MEDIASTREAM, @"ContentBlockedMedia"},
255 {CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS, @"ContentBlockedDownloads"},
256 {CONTENT_SETTINGS_TYPE_MIDI_SYSEX, @"ContentBlockedMIDISysEx"},
259 - (id)initWithModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
260 webContents:(content::WebContents*)webContents
261 parentWindow:(NSWindow*)parentWindow
262 anchoredAt:(NSPoint)anchoredAt {
263 // This method takes ownership of |contentSettingBubbleModel| in all cases.
264 scoped_ptr<ContentSettingBubbleModel> model(contentSettingBubbleModel);
266 observerBridge_.reset(
267 new ContentSettingBubbleWebContentsObserverBridge(webContents, self));
269 ContentSettingsType settingsType = model->content_type();
270 DCHECK(ContainsKey(ContentSettingBubbleModel::GetSupportedBubbleTypes(),
272 DCHECK_EQ(ContentSettingBubbleModel::GetSupportedBubbleTypes().size(),
273 arraysize(kNibPaths));
274 NSString* nibPath = @"";
275 for (const ContentTypeToNibPath& type_to_path : kNibPaths) {
276 if (settingsType == type_to_path.type) {
277 nibPath = type_to_path.path;
281 DCHECK_NE(0u, [nibPath length]);
283 if ((self = [super initWithWindowNibPath:nibPath
284 parentWindow:parentWindow
285 anchoredAt:anchoredAt])) {
286 contentSettingBubbleModel_.reset(model.release());
287 [self showWindow:nil];
293 STLDeleteValues(&mediaMenus_);
297 - (void)initializeTitle {
301 NSString* label = base::SysUTF8ToNSString(
302 contentSettingBubbleModel_->bubble_content().title);
303 [titleLabel_ setStringValue:label];
305 // Layout title post-localization.
306 CGFloat deltaY = [GTMUILocalizerAndLayoutTweaker
307 sizeToFitFixedWidthTextField:titleLabel_];
308 NSRect windowFrame = [[self window] frame];
309 windowFrame.size.height += deltaY;
310 [[self window] setFrame:windowFrame display:NO];
311 NSRect titleFrame = [titleLabel_ frame];
312 titleFrame.origin.y -= deltaY;
313 [titleLabel_ setFrame:titleFrame];
316 - (void)initializeRadioGroup {
317 // NOTE! Tags in the xib files must match the order of the radio buttons
318 // passed in the radio_group and be 1-based, not 0-based.
319 const ContentSettingBubbleModel::RadioGroup& radio_group =
320 contentSettingBubbleModel_->bubble_content().radio_group;
322 // Xcode 5.1 Interface Builder doesn't allow a font property to be set for
323 // NSMatrix. The implementation of GTMUILocalizerAndLayoutTweaker assumes that
324 // the font for each of the cells in a NSMatrix is identical, and is the font
325 // of the NSMatrix. This logic sets the font of NSMatrix to be that of its
328 for (NSCell* cell in [allowBlockRadioGroup_ cells]) {
331 DCHECK([font isEqual:[cell font]]);
333 [allowBlockRadioGroup_ setFont:font];
335 // Select appropriate radio button.
336 [allowBlockRadioGroup_ selectCellWithTag: radio_group.default_item + 1];
338 const ContentSettingBubbleModel::RadioItems& radio_items =
339 radio_group.radio_items;
340 for (size_t ii = 0; ii < radio_group.radio_items.size(); ++ii) {
341 NSCell* radioCell = [allowBlockRadioGroup_ cellWithTag: ii + 1];
342 [radioCell setTitle:base::SysUTF8ToNSString(radio_items[ii])];
345 // Layout radio group labels post-localization.
346 [GTMUILocalizerAndLayoutTweaker
347 wrapRadioGroupForWidth:allowBlockRadioGroup_];
348 CGFloat radioDeltaY = [GTMUILocalizerAndLayoutTweaker
349 sizeToFitView:allowBlockRadioGroup_].height;
350 NSRect windowFrame = [[self window] frame];
351 windowFrame.size.height += radioDeltaY;
352 [[self window] setFrame:windowFrame display:NO];
355 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
356 title:(NSString*)title
358 referenceFrame:(NSRect)referenceFrame {
359 base::scoped_nsobject<HyperlinkButtonCell> cell(
360 [[HyperlinkButtonCell alloc] initTextCell:title]);
361 [cell.get() setAlignment:NSNaturalTextAlignment];
363 [cell.get() setImagePosition:NSImageLeft];
364 [cell.get() setImage:icon];
366 [cell.get() setImagePosition:NSNoImage];
368 [cell.get() setControlSize:NSSmallControlSize];
370 NSButton* button = [[[NSButton alloc] initWithFrame:frame] autorelease];
371 // Cell must be set immediately after construction.
372 [button setCell:cell.get()];
374 // Size to fit the button and add a little extra padding for the small-text
375 // hyperlink button, which sizeToFit gets wrong.
376 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
377 NSRect buttonFrame = [button frame];
378 buttonFrame.size.width += 2;
380 // If the link text is too long, clamp it.
381 int maxWidth = NSWidth([[self bubble] frame]) - 2 * NSMinX(referenceFrame);
382 if (NSWidth(buttonFrame) > maxWidth)
383 buttonFrame.size.width = maxWidth;
385 [button setFrame:buttonFrame];
386 [button setTarget:self];
387 [button setAction:@selector(popupLinkClicked:)];
391 - (void)initializeBlockedPluginsList {
392 // Hide the empty label at the top of the dialog.
394 NSMinY([titleLabel_ frame]) - NSMinY([blockedResourcesField_ frame]);
395 [blockedResourcesField_ removeFromSuperview];
396 NSRect frame = [[self window] frame];
397 frame.size.height -= delta;
398 [[self window] setFrame:frame display:NO];
401 - (void)initializeItemList {
402 // I didn't put the buttons into a NSMatrix because then they are only one
403 // entity in the key view loop. This way, one can tab through all of them.
404 const ContentSettingBubbleModel::ListItems& listItems =
405 contentSettingBubbleModel_->bubble_content().list_items;
407 // Get the pre-resize frame of the radio group. Its origin is where the
408 // popup list should go.
409 NSRect radioFrame = [allowBlockRadioGroup_ frame];
411 // Make room for the popup list. The bubble view and its subviews autosize
412 // themselves when the window is enlarged.
413 // Heading and radio box are already 1 * kLinkOuterPadding apart in the nib,
414 // so only 1 * kLinkOuterPadding more is needed.
416 listItems.size() * kLinkLineHeight - kLinkPadding + kLinkOuterPadding;
417 NSSize deltaSize = NSMakeSize(0, delta);
418 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
419 NSRect windowFrame = [[self window] frame];
420 windowFrame.size.height += deltaSize.height;
421 [[self window] setFrame:windowFrame display:NO];
424 int topLinkY = NSMaxY(radioFrame) + delta - kLinkHeight;
426 for (const ContentSettingBubbleModel::ListItem& listItem : listItems) {
427 NSImage* image = listItem.image.AsNSImage();
428 NSRect frame = NSMakeRect(
429 NSMinX(radioFrame), topLinkY - kLinkLineHeight * row, 200, kLinkHeight);
430 if (listItem.has_link) {
432 [self hyperlinkButtonWithFrame:frame
433 title:base::SysUTF8ToNSString(listItem.title)
435 referenceFrame:radioFrame];
436 [[self bubble] addSubview:button];
437 popupLinks_[button] = row++;
440 LabelWithFrame(base::SysUTF8ToNSString(listItem.title), frame);
441 SetControlSize(label, NSSmallControlSize);
442 [[self bubble] addSubview:label];
448 - (void)initializeGeoLists {
449 // Cocoa has its origin in the lower left corner. This means elements are
450 // added from bottom to top, which explains why loops run backwards and the
451 // order of operations is the other way than on Linux/Windows.
452 const ContentSettingBubbleModel::BubbleContent& content =
453 contentSettingBubbleModel_->bubble_content();
454 NSRect containerFrame = [contentsContainer_ frame];
455 NSRect frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
457 // "Clear" button / text field.
458 if (!content.custom_link.empty()) {
459 base::scoped_nsobject<NSControl> control;
460 if(content.custom_link_enabled) {
461 NSRect buttonFrame = NSMakeRect(0, 0,
462 NSWidth(containerFrame),
463 kGeoClearButtonHeight);
464 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
465 control.reset(button);
466 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
467 [button setTarget:self];
468 [button setAction:@selector(clearGeolocationForCurrentHost:)];
469 [button setBezelStyle:NSRoundRectBezelStyle];
470 SetControlSize(button, NSSmallControlSize);
473 // Add the notification that settings will be cleared on next reload.
474 control.reset([LabelWithFrame(
475 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
476 SetControlSize(control.get(), NSSmallControlSize);
479 // If the new control is wider than the container, widen the window.
480 CGFloat controlWidth = NSWidth([control frame]);
481 if (controlWidth > NSWidth(containerFrame)) {
482 NSRect windowFrame = [[self window] frame];
483 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
484 [[self window] setFrame:windowFrame display:NO];
485 // Fetch the updated sizes.
486 containerFrame = [contentsContainer_ frame];
487 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
491 [contentsContainer_ addSubview:control];
492 frame.origin.y = NSMaxY([control frame]) + kGeoPadding;
495 for (auto i = content.domain_lists.rbegin();
496 i != content.domain_lists.rend(); ++i) {
497 // Add all hosts in the current domain list.
498 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
499 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
500 SetControlSize(title, NSSmallControlSize);
501 [contentsContainer_ addSubview:title];
503 frame.origin.y = NSMaxY(frame) + kGeoHostPadding +
504 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
506 if (!i->hosts.empty())
507 frame.origin.y += kGeoPadding - kGeoHostPadding;
509 // Add the domain list's title.
511 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
512 SetControlSize(title, NSSmallControlSize);
513 [contentsContainer_ addSubview:title];
515 frame.origin.y = NSMaxY(frame) + kGeoPadding +
516 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
519 CGFloat containerHeight = frame.origin.y;
520 // Undo last padding.
521 if (!content.domain_lists.empty())
522 containerHeight -= kGeoPadding;
524 // Resize container to fit its subviews, and window to fit the container.
525 NSRect windowFrame = [[self window] frame];
526 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
527 [[self window] setFrame:windowFrame display:NO];
528 containerFrame.size.height = containerHeight;
529 [contentsContainer_ setFrame:containerFrame];
532 - (void)initializeMediaMenus {
533 const ContentSettingBubbleModel::MediaMenuMap& media_menus =
534 contentSettingBubbleModel_->bubble_content().media_menus;
536 // Calculate the longest width of the labels and menus menus to avoid
537 // truncation by the window's edge.
538 CGFloat maxLabelWidth = 0;
539 CGFloat maxMenuWidth = 0;
540 CGFloat maxMenuHeight = 0;
541 NSRect radioFrame = [allowBlockRadioGroup_ frame];
542 for (const std::pair<content::MediaStreamType,
543 ContentSettingBubbleModel::MediaMenu>& map_entry :
545 // |labelFrame| will be resized later on in this function.
546 NSRect labelFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
547 NSTextField* label = LabelWithFrame(
548 base::SysUTF8ToNSString(map_entry.second.label), labelFrame);
549 SetControlSize(label, NSSmallControlSize);
550 NSCell* cell = [label cell];
551 [cell setAlignment:NSRightTextAlignment];
552 [GTMUILocalizerAndLayoutTweaker sizeToFitView:label];
553 maxLabelWidth = std::max(maxLabelWidth, [label frame].size.width);
554 [[self bubble] addSubview:label];
556 // |buttonFrame| will be resized and repositioned later on.
557 NSRect buttonFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
558 base::scoped_nsobject<NSPopUpButton> button(
559 [[NSPopUpButton alloc] initWithFrame:buttonFrame]);
560 [button setTarget:self];
562 // Set the map_entry's key value to |button| tag.
563 // MediaMenuPartsMap uses this value to order its elements.
564 [button setTag:static_cast<NSInteger>(map_entry.first)];
566 // Store the |label| and |button| into MediaMenuParts struct and build
567 // the popup menu from the menu model.
568 content_setting_bubble::MediaMenuParts* menuParts =
569 new content_setting_bubble::MediaMenuParts(map_entry.first, label);
570 menuParts->model.reset(new ContentSettingMediaMenuModel(
571 map_entry.first, contentSettingBubbleModel_.get(),
572 ContentSettingMediaMenuModel::MenuLabelChangedCallback()));
573 mediaMenus_[button] = menuParts;
574 CGFloat width = BuildPopUpMenuFromModel(
575 button, menuParts->model.get(), map_entry.second.selected_device.name,
576 map_entry.second.disabled);
577 maxMenuWidth = std::max(maxMenuWidth, width);
579 [[self bubble] addSubview:button
580 positioned:NSWindowBelow
583 maxMenuHeight = std::max(maxMenuHeight, [button frame].size.height);
586 // Make room for the media menu(s) and enlarge the windows to fit the views.
587 // The bubble view and its subviews autosize themselves when the window is
589 int delta = media_menus.size() * maxMenuHeight +
590 (media_menus.size() - 1) * kMediaMenuElementVerticalPadding;
591 NSSize deltaSize = NSMakeSize(0, delta);
592 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
593 NSRect windowFrame = [[self window] frame];
594 windowFrame.size.height += deltaSize.height;
595 // If the media menus are wider than the window, widen the window.
596 CGFloat widthNeeded = maxLabelWidth + maxMenuWidth + 2 * NSMinX(radioFrame);
597 if (widthNeeded > windowFrame.size.width)
598 windowFrame.size.width = widthNeeded;
599 [[self window] setFrame:windowFrame display:NO];
601 // The radio group lies above the media menus, move the radio group up.
602 radioFrame.origin.y += delta;
603 [allowBlockRadioGroup_ setFrame:radioFrame];
605 // Resize and reposition the media menus layout.
606 CGFloat topMenuY = NSMinY(radioFrame) - kMediaMenuVerticalPadding;
607 maxMenuWidth = std::max(maxMenuWidth, kMinMediaMenuButtonWidth);
608 for (const std::pair<NSPopUpButton*, content_setting_bubble::MediaMenuParts*>&
609 map_entry : mediaMenus_) {
610 NSRect labelFrame = [map_entry.second->label frame];
611 // Align the label text with the button text.
612 labelFrame.origin.y =
613 topMenuY + (maxMenuHeight - labelFrame.size.height) / 2 + 1;
614 labelFrame.size.width = maxLabelWidth;
615 [map_entry.second->label setFrame:labelFrame];
616 NSRect menuFrame = [map_entry.first frame];
617 menuFrame.origin.y = topMenuY;
618 menuFrame.origin.x = NSMinX(radioFrame) + maxLabelWidth;
619 menuFrame.size.width = maxMenuWidth;
620 menuFrame.size.height = maxMenuHeight;
621 [map_entry.first setFrame:menuFrame];
622 topMenuY -= (maxMenuHeight + kMediaMenuElementVerticalPadding);
626 - (void)initializeMIDISysExLists {
627 const ContentSettingBubbleModel::BubbleContent& content =
628 contentSettingBubbleModel_->bubble_content();
629 NSRect containerFrame = [contentsContainer_ frame];
631 NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
633 // "Clear" button / text field.
634 if (!content.custom_link.empty()) {
635 base::scoped_nsobject<NSControl> control;
636 if (content.custom_link_enabled) {
637 NSRect buttonFrame = NSMakeRect(0, 0,
638 NSWidth(containerFrame),
639 kMIDISysExClearButtonHeight);
640 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
641 control.reset(button);
642 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
643 [button setTarget:self];
644 [button setAction:@selector(clearMIDISysExForCurrentHost:)];
645 [button setBezelStyle:NSRoundRectBezelStyle];
646 SetControlSize(button, NSSmallControlSize);
649 // Add the notification that settings will be cleared on next reload.
650 control.reset([LabelWithFrame(
651 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
652 SetControlSize(control.get(), NSSmallControlSize);
655 // If the new control is wider than the container, widen the window.
656 CGFloat controlWidth = NSWidth([control frame]);
657 if (controlWidth > NSWidth(containerFrame)) {
658 NSRect windowFrame = [[self window] frame];
659 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
660 [[self window] setFrame:windowFrame display:NO];
661 // Fetch the updated sizes.
662 containerFrame = [contentsContainer_ frame];
663 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
667 [contentsContainer_ addSubview:control];
668 frame.origin.y = NSMaxY([control frame]) + kMIDISysExPadding;
671 for (auto i = content.domain_lists.rbegin();
672 i != content.domain_lists.rend(); ++i) {
673 // Add all hosts in the current domain list.
674 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
675 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
676 SetControlSize(title, NSSmallControlSize);
677 [contentsContainer_ addSubview:title];
679 frame.origin.y = NSMaxY(frame) + kMIDISysExHostPadding +
680 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
682 if (!i->hosts.empty())
683 frame.origin.y += kMIDISysExPadding - kMIDISysExHostPadding;
685 // Add the domain list's title.
687 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
688 SetControlSize(title, NSSmallControlSize);
689 [contentsContainer_ addSubview:title];
691 frame.origin.y = NSMaxY(frame) + kMIDISysExPadding +
692 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
695 CGFloat containerHeight = frame.origin.y;
696 // Undo last padding.
697 if (!content.domain_lists.empty())
698 containerHeight -= kMIDISysExPadding;
700 // Resize container to fit its subviews, and window to fit the container.
701 NSRect windowFrame = [[self window] frame];
702 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
703 [[self window] setFrame:windowFrame display:NO];
704 containerFrame.size.height = containerHeight;
705 [contentsContainer_ setFrame:containerFrame];
708 - (void)sizeToFitLoadButton {
709 const ContentSettingBubbleModel::BubbleContent& content =
710 contentSettingBubbleModel_->bubble_content();
711 [loadButton_ setEnabled:content.custom_link_enabled];
713 // Resize horizontally to fit button if necessary.
714 NSRect windowFrame = [[self window] frame];
715 int widthNeeded = NSWidth([loadButton_ frame]) +
716 2 * NSMinX([loadButton_ frame]);
717 if (NSWidth(windowFrame) < widthNeeded) {
718 windowFrame.size.width = widthNeeded;
719 [[self window] setFrame:windowFrame display:NO];
723 - (void)initManageDoneButtons {
724 const ContentSettingBubbleModel::BubbleContent& content =
725 contentSettingBubbleModel_->bubble_content();
726 [manageButton_ setTitle:base::SysUTF8ToNSString(content.manage_link)];
727 [GTMUILocalizerAndLayoutTweaker sizeToFitView:[manageButton_ superview]];
729 CGFloat actualWidth = NSWidth([[[self window] contentView] frame]);
730 CGFloat requiredWidth = NSMaxX([manageButton_ frame]) + kManageDonePadding +
731 NSWidth([[doneButton_ superview] frame]) - NSMinX([doneButton_ frame]);
732 if (requiredWidth <= actualWidth || !doneButton_ || !manageButton_)
735 // Resize window, autoresizing takes care of the rest.
736 NSSize size = NSMakeSize(requiredWidth - actualWidth, 0);
737 size = [[[self window] contentView] convertSize:size toView:nil];
738 NSRect frame = [[self window] frame];
739 frame.origin.x -= size.width;
740 frame.size.width += size.width;
741 [[self window] setFrame:frame display:NO];
744 - (void)awakeFromNib {
745 [super awakeFromNib];
747 [[self bubble] setArrowLocation:info_bubble::kTopRight];
749 // Adapt window size to bottom buttons. Do this before all other layouting.
750 [self initManageDoneButtons];
752 [self initializeTitle];
754 ContentSettingsType type = contentSettingBubbleModel_->content_type();
755 if (type == CONTENT_SETTINGS_TYPE_PLUGINS) {
756 [self sizeToFitLoadButton];
757 [self initializeBlockedPluginsList];
760 if (allowBlockRadioGroup_) // not bound in cookie bubble xib
761 [self initializeRadioGroup];
763 if (type == CONTENT_SETTINGS_TYPE_POPUPS ||
764 type == CONTENT_SETTINGS_TYPE_PLUGINS)
765 [self initializeItemList];
766 if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION)
767 [self initializeGeoLists];
768 if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM)
769 [self initializeMediaMenus];
770 if (type == CONTENT_SETTINGS_TYPE_MIDI_SYSEX)
771 [self initializeMIDISysExLists];
774 ///////////////////////////////////////////////////////////////////////////////
775 // Actual application logic
777 - (IBAction)allowBlockToggled:(id)sender {
778 NSButtonCell *selectedCell = [sender selectedCell];
779 contentSettingBubbleModel_->OnRadioClicked([selectedCell tag] - 1);
782 - (void)popupLinkClicked:(id)sender {
783 content_setting_bubble::PopupLinks::iterator i(popupLinks_.find(sender));
784 DCHECK(i != popupLinks_.end());
785 contentSettingBubbleModel_->OnListItemClicked(i->second);
788 - (void)clearGeolocationForCurrentHost:(id)sender {
789 contentSettingBubbleModel_->OnCustomLinkClicked();
793 - (void)clearMIDISysExForCurrentHost:(id)sender {
794 contentSettingBubbleModel_->OnCustomLinkClicked();
798 - (IBAction)showMoreInfo:(id)sender {
799 contentSettingBubbleModel_->OnCustomLinkClicked();
803 - (IBAction)load:(id)sender {
804 contentSettingBubbleModel_->OnCustomLinkClicked();
808 - (IBAction)learnMoreLinkClicked:(id)sender {
809 contentSettingBubbleModel_->OnManageLinkClicked();
812 - (IBAction)manageBlocking:(id)sender {
813 contentSettingBubbleModel_->OnManageLinkClicked();
816 - (IBAction)closeBubble:(id)sender {
817 contentSettingBubbleModel_->OnDoneClicked();
821 - (IBAction)mediaMenuChanged:(id)sender {
822 NSPopUpButton* button = static_cast<NSPopUpButton*>(sender);
823 content_setting_bubble::MediaMenuPartsMap::const_iterator it(
824 mediaMenus_.find(sender));
825 DCHECK(it != mediaMenus_.end());
826 NSInteger index = [[button selectedItem] tag];
828 SetTitleForPopUpButton(
829 button, base::SysUTF16ToNSString(it->second->model->GetLabelAt(index)));
831 it->second->model->ExecuteCommand(index, 0);
834 - (content_setting_bubble::MediaMenuPartsMap*)mediaMenus {
838 @end // ContentSettingBubbleController