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 NSFont* font = [NSFont fontWithName:[[cell font] fontName] size:fontSize];
87 [cell setControlSize:controlSize];
90 // Returns an autoreleased NSTextField that is configured to look like a Label
91 // looks in Interface Builder.
92 NSTextField* LabelWithFrame(NSString* text, const NSRect& frame) {
93 NSTextField* label = [[NSTextField alloc] initWithFrame:frame];
94 [label setStringValue:text];
95 [label setSelectable:NO];
96 [label setBezeled:NO];
97 return [label autorelease];
100 // Sets the title for the popup button.
101 void SetTitleForPopUpButton(NSPopUpButton* button, NSString* title) {
102 base::scoped_nsobject<NSMenuItem> titleItem([[NSMenuItem alloc] init]);
103 [titleItem setTitle:title];
104 [[button cell] setUsesItemFromMenu:NO];
105 [[button cell] setMenuItem:titleItem.get()];
108 // Builds the popup button menu from the menu model and returns the width of the
109 // longgest item as the width of the popup menu.
110 CGFloat BuildPopUpMenuFromModel(NSPopUpButton* button,
111 ContentSettingMediaMenuModel* model,
112 const std::string& title,
114 [[button cell] setControlSize:NSSmallControlSize];
115 [[button cell] setArrowPosition:NSPopUpArrowAtBottom];
116 [button setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
117 [button setButtonType:NSMomentaryPushInButton];
118 [button setAlignment:NSLeftTextAlignment];
119 [button setAutoresizingMask:NSViewMinXMargin];
120 [button setAction:@selector(mediaMenuChanged:)];
123 CGFloat menuWidth = 0;
124 for (int i = 0; i < model->GetItemCount(); ++i) {
125 NSString* itemTitle =
126 base::SysUTF16ToNSString(model->GetLabelAt(i));
127 [button addItemWithTitle:itemTitle];
128 [[button lastItem] setTag:i];
130 if (UTF16ToUTF8(model->GetLabelAt(i)) == title)
131 [button selectItemWithTag:i];
133 // Determine the largest possible size for this button.
134 NSDictionary* textAttributes =
135 [NSDictionary dictionaryWithObject:[button font]
136 forKey:NSFontAttributeName];
137 NSSize size = [itemTitle sizeWithAttributes:textAttributes];
138 NSRect buttonFrame = [button frame];
139 NSRect titleRect = [[button cell] titleRectForBounds:buttonFrame];
140 CGFloat width = size.width + NSWidth(buttonFrame) - NSWidth(titleRect) +
141 kMediaMenuTitleHorizontalPadding;
142 menuWidth = std::max(menuWidth, width);
145 if (!model->GetItemCount()) {
146 // Show a "None available" title and grey out the menu when there is no
148 SetTitleForPopUpButton(
149 button, l10n_util::GetNSString(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
150 [button setEnabled:NO];
152 SetTitleForPopUpButton(button, base::SysUTF8ToNSString(title));
154 // Disable the device selection when the website is managing the devices
157 [button setEnabled:NO];
165 namespace content_setting_bubble {
167 MediaMenuParts::MediaMenuParts(content::MediaStreamType type,
171 MediaMenuParts::~MediaMenuParts() {}
173 } // namespace content_setting_bubble
175 class ContentSettingBubbleWebContentsObserverBridge
176 : public content::WebContentsObserver {
178 ContentSettingBubbleWebContentsObserverBridge(
179 content::WebContents* web_contents,
180 ContentSettingBubbleController* controller)
181 : content::WebContentsObserver(web_contents),
182 controller_(controller) {
186 // WebContentsObserver:
187 void DidNavigateMainFrame(
188 const content::LoadCommittedDetails& details,
189 const content::FrameNavigateParams& params) override {
190 // Content settings are based on the main frame, so if it switches then
192 [controller_ closeBubble:nil];
196 ContentSettingBubbleController* controller_; // weak
198 DISALLOW_COPY_AND_ASSIGN(ContentSettingBubbleWebContentsObserverBridge);
201 @interface ContentSettingBubbleController(Private)
202 - (id)initWithModel:(ContentSettingBubbleModel*)settingsBubbleModel
203 webContents:(content::WebContents*)webContents
204 parentWindow:(NSWindow*)parentWindow
205 anchoredAt:(NSPoint)anchoredAt;
206 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
207 title:(NSString*)title
209 referenceFrame:(NSRect)referenceFrame;
210 - (void)initializeBlockedPluginsList;
211 - (void)initializeTitle;
212 - (void)initializeRadioGroup;
213 - (void)initializePopupList;
214 - (void)initializeGeoLists;
215 - (void)initializeMediaMenus;
216 - (void)initializeMIDISysExLists;
217 - (void)sizeToFitLoadButton;
218 - (void)initManageDoneButtons;
219 - (void)removeInfoButton;
220 - (void)popupLinkClicked:(id)sender;
221 - (void)clearGeolocationForCurrentHost:(id)sender;
222 - (void)clearMIDISysExForCurrentHost:(id)sender;
225 @implementation ContentSettingBubbleController
227 + (ContentSettingBubbleController*)
228 showForModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
229 webContents:(content::WebContents*)webContents
230 parentWindow:(NSWindow*)parentWindow
231 anchoredAt:(NSPoint)anchor {
232 // Autoreleases itself on bubble close.
233 return [[ContentSettingBubbleController alloc]
234 initWithModel:contentSettingBubbleModel
235 webContents:webContents
236 parentWindow:parentWindow
240 - (id)initWithModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
241 webContents:(content::WebContents*)webContents
242 parentWindow:(NSWindow*)parentWindow
243 anchoredAt:(NSPoint)anchoredAt {
244 // This method takes ownership of |contentSettingBubbleModel| in all cases.
245 scoped_ptr<ContentSettingBubbleModel> model(contentSettingBubbleModel);
247 observerBridge_.reset(
248 new ContentSettingBubbleWebContentsObserverBridge(webContents, self));
250 ContentSettingsType settingsType = model->content_type();
251 NSString* nibPath = @"";
252 switch (settingsType) {
253 case CONTENT_SETTINGS_TYPE_COOKIES:
254 nibPath = @"ContentBlockedCookies"; break;
255 case CONTENT_SETTINGS_TYPE_IMAGES:
256 case CONTENT_SETTINGS_TYPE_JAVASCRIPT:
257 case CONTENT_SETTINGS_TYPE_PPAPI_BROKER:
258 nibPath = @"ContentBlockedSimple"; break;
259 case CONTENT_SETTINGS_TYPE_PLUGINS:
260 nibPath = @"ContentBlockedPlugins"; break;
261 case CONTENT_SETTINGS_TYPE_POPUPS:
262 nibPath = @"ContentBlockedPopups"; break;
263 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
264 nibPath = @"ContentBlockedGeolocation"; break;
265 case CONTENT_SETTINGS_TYPE_MIXEDSCRIPT:
266 nibPath = @"ContentBlockedMixedScript"; break;
267 case CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS:
268 nibPath = @"ContentProtocolHandlers"; break;
269 case CONTENT_SETTINGS_TYPE_MEDIASTREAM:
270 nibPath = @"ContentBlockedMedia"; break;
271 case CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS:
272 nibPath = @"ContentBlockedDownloads"; break;
273 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
274 nibPath = @"ContentBlockedMIDISysEx"; break;
275 // These content types have no bubble:
276 case CONTENT_SETTINGS_TYPE_DEFAULT:
277 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
278 case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE:
279 case CONTENT_SETTINGS_TYPE_FULLSCREEN:
280 case CONTENT_SETTINGS_TYPE_MOUSELOCK:
281 case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
282 case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
283 case CONTENT_SETTINGS_NUM_TYPES:
284 // TODO(miguelg): Remove this nib content settings support
286 case CONTENT_SETTINGS_TYPE_PUSH_MESSAGING:
287 case CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS:
290 if ((self = [super initWithWindowNibPath:nibPath
291 parentWindow:parentWindow
292 anchoredAt:anchoredAt])) {
293 contentSettingBubbleModel_.reset(model.release());
294 [self showWindow:nil];
300 STLDeleteValues(&mediaMenus_);
304 - (void)initializeTitle {
308 NSString* label = base::SysUTF8ToNSString(
309 contentSettingBubbleModel_->bubble_content().title);
310 [titleLabel_ setStringValue:label];
312 // Layout title post-localization.
313 CGFloat deltaY = [GTMUILocalizerAndLayoutTweaker
314 sizeToFitFixedWidthTextField:titleLabel_];
315 NSRect windowFrame = [[self window] frame];
316 windowFrame.size.height += deltaY;
317 [[self window] setFrame:windowFrame display:NO];
318 NSRect titleFrame = [titleLabel_ frame];
319 titleFrame.origin.y -= deltaY;
320 [titleLabel_ setFrame:titleFrame];
323 - (void)initializeRadioGroup {
324 // NOTE! Tags in the xib files must match the order of the radio buttons
325 // passed in the radio_group and be 1-based, not 0-based.
326 const ContentSettingBubbleModel::RadioGroup& radio_group =
327 contentSettingBubbleModel_->bubble_content().radio_group;
329 // Select appropriate radio button.
330 [allowBlockRadioGroup_ selectCellWithTag: radio_group.default_item + 1];
332 const ContentSettingBubbleModel::RadioItems& radio_items =
333 radio_group.radio_items;
334 for (size_t ii = 0; ii < radio_group.radio_items.size(); ++ii) {
335 NSCell* radioCell = [allowBlockRadioGroup_ cellWithTag: ii + 1];
336 [radioCell setTitle:base::SysUTF8ToNSString(radio_items[ii])];
339 // Layout radio group labels post-localization.
340 [GTMUILocalizerAndLayoutTweaker
341 wrapRadioGroupForWidth:allowBlockRadioGroup_];
342 CGFloat radioDeltaY = [GTMUILocalizerAndLayoutTweaker
343 sizeToFitView:allowBlockRadioGroup_].height;
344 NSRect windowFrame = [[self window] frame];
345 windowFrame.size.height += radioDeltaY;
346 [[self window] setFrame:windowFrame display:NO];
349 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
350 title:(NSString*)title
352 referenceFrame:(NSRect)referenceFrame {
353 base::scoped_nsobject<HyperlinkButtonCell> cell(
354 [[HyperlinkButtonCell alloc] initTextCell:title]);
355 [cell.get() setAlignment:NSNaturalTextAlignment];
357 [cell.get() setImagePosition:NSImageLeft];
358 [cell.get() setImage:icon];
360 [cell.get() setImagePosition:NSNoImage];
362 [cell.get() setControlSize:NSSmallControlSize];
364 NSButton* button = [[[NSButton alloc] initWithFrame:frame] autorelease];
365 // Cell must be set immediately after construction.
366 [button setCell:cell.get()];
368 // Size to fit the button and add a little extra padding for the small-text
369 // hyperlink button, which sizeToFit gets wrong.
370 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
371 NSRect buttonFrame = [button frame];
372 buttonFrame.size.width += 2;
374 // If the link text is too long, clamp it.
375 int maxWidth = NSWidth([[self bubble] frame]) - 2 * NSMinX(referenceFrame);
376 if (NSWidth(buttonFrame) > maxWidth)
377 buttonFrame.size.width = maxWidth;
379 [button setFrame:buttonFrame];
380 [button setTarget:self];
381 [button setAction:@selector(popupLinkClicked:)];
385 - (void)initializeBlockedPluginsList {
386 int delta = NSMinY([titleLabel_ frame]) -
387 NSMinY([blockedResourcesField_ frame]);
388 [blockedResourcesField_ removeFromSuperview];
389 NSRect frame = [[self window] frame];
390 frame.size.height -= delta;
391 [[self window] setFrame:frame display:NO];
394 - (void)initializePopupList {
395 // I didn't put the buttons into a NSMatrix because then they are only one
396 // entity in the key view loop. This way, one can tab through all of them.
397 const ContentSettingBubbleModel::PopupItems& popupItems =
398 contentSettingBubbleModel_->bubble_content().popup_items;
400 // Get the pre-resize frame of the radio group. Its origin is where the
401 // popup list should go.
402 NSRect radioFrame = [allowBlockRadioGroup_ frame];
404 // Make room for the popup list. The bubble view and its subviews autosize
405 // themselves when the window is enlarged.
406 // Heading and radio box are already 1 * kLinkOuterPadding apart in the nib,
407 // so only 1 * kLinkOuterPadding more is needed.
408 int delta = popupItems.size() * kLinkLineHeight - kLinkPadding +
410 NSSize deltaSize = NSMakeSize(0, delta);
411 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
412 NSRect windowFrame = [[self window] frame];
413 windowFrame.size.height += deltaSize.height;
414 [[self window] setFrame:windowFrame display:NO];
416 // Create popup list.
417 int topLinkY = NSMaxY(radioFrame) + delta - kLinkHeight;
419 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
420 it(popupItems.begin()); it != popupItems.end(); ++it, ++row) {
421 NSImage* image = it->image.AsNSImage();
423 std::string title(it->title);
424 // The popup may not have committed a load yet, in which case it won't
425 // have a URL or title.
427 title = l10n_util::GetStringUTF8(IDS_TAB_LOADING_TITLE);
430 NSMakeRect(NSMinX(radioFrame), topLinkY - kLinkLineHeight * row,
432 NSButton* button = [self
433 hyperlinkButtonWithFrame:linkFrame
434 title:base::SysUTF8ToNSString(title)
436 referenceFrame:radioFrame];
437 [[self bubble] addSubview:button];
438 popupLinks_[button] = row;
442 - (void)initializeGeoLists {
443 // Cocoa has its origin in the lower left corner. This means elements are
444 // added from bottom to top, which explains why loops run backwards and the
445 // order of operations is the other way than on Linux/Windows.
446 const ContentSettingBubbleModel::BubbleContent& content =
447 contentSettingBubbleModel_->bubble_content();
448 NSRect containerFrame = [contentsContainer_ frame];
449 NSRect frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
451 // "Clear" button / text field.
452 if (!content.custom_link.empty()) {
453 base::scoped_nsobject<NSControl> control;
454 if(content.custom_link_enabled) {
455 NSRect buttonFrame = NSMakeRect(0, 0,
456 NSWidth(containerFrame),
457 kGeoClearButtonHeight);
458 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
459 control.reset(button);
460 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
461 [button setTarget:self];
462 [button setAction:@selector(clearGeolocationForCurrentHost:)];
463 [button setBezelStyle:NSRoundRectBezelStyle];
464 SetControlSize(button, NSSmallControlSize);
467 // Add the notification that settings will be cleared on next reload.
468 control.reset([LabelWithFrame(
469 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
470 SetControlSize(control.get(), NSSmallControlSize);
473 // If the new control is wider than the container, widen the window.
474 CGFloat controlWidth = NSWidth([control frame]);
475 if (controlWidth > NSWidth(containerFrame)) {
476 NSRect windowFrame = [[self window] frame];
477 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
478 [[self window] setFrame:windowFrame display:NO];
479 // Fetch the updated sizes.
480 containerFrame = [contentsContainer_ frame];
481 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
485 [contentsContainer_ addSubview:control];
486 frame.origin.y = NSMaxY([control frame]) + kGeoPadding;
489 for (auto i = content.domain_lists.rbegin();
490 i != content.domain_lists.rend(); ++i) {
491 // Add all hosts in the current domain list.
492 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
493 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
494 SetControlSize(title, NSSmallControlSize);
495 [contentsContainer_ addSubview:title];
497 frame.origin.y = NSMaxY(frame) + kGeoHostPadding +
498 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
500 if (!i->hosts.empty())
501 frame.origin.y += kGeoPadding - kGeoHostPadding;
503 // Add the domain list's title.
505 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
506 SetControlSize(title, NSSmallControlSize);
507 [contentsContainer_ addSubview:title];
509 frame.origin.y = NSMaxY(frame) + kGeoPadding +
510 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
513 CGFloat containerHeight = frame.origin.y;
514 // Undo last padding.
515 if (!content.domain_lists.empty())
516 containerHeight -= kGeoPadding;
518 // Resize container to fit its subviews, and window to fit the container.
519 NSRect windowFrame = [[self window] frame];
520 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
521 [[self window] setFrame:windowFrame display:NO];
522 containerFrame.size.height = containerHeight;
523 [contentsContainer_ setFrame:containerFrame];
526 - (void)initializeMediaMenus {
527 const ContentSettingBubbleModel::MediaMenuMap& media_menus =
528 contentSettingBubbleModel_->bubble_content().media_menus;
530 // Calculate the longest width of the labels and menus menus to avoid
531 // truncation by the window's edge.
532 CGFloat maxLabelWidth = 0;
533 CGFloat maxMenuWidth = 0;
534 CGFloat maxMenuHeight = 0;
535 NSRect radioFrame = [allowBlockRadioGroup_ frame];
536 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator it(
537 media_menus.begin()); it != media_menus.end(); ++it) {
538 // |labelFrame| will be resized later on in this function.
539 NSRect labelFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
541 LabelWithFrame(base::SysUTF8ToNSString(it->second.label), labelFrame);
542 SetControlSize(label, NSSmallControlSize);
543 NSCell* cell = [label cell];
544 [cell setAlignment:NSRightTextAlignment];
545 [GTMUILocalizerAndLayoutTweaker sizeToFitView:label];
546 maxLabelWidth = std::max(maxLabelWidth, [label frame].size.width);
547 [[self bubble] addSubview:label];
549 // |buttonFrame| will be resized and repositioned later on.
550 NSRect buttonFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
551 base::scoped_nsobject<NSPopUpButton> button(
552 [[NSPopUpButton alloc] initWithFrame:buttonFrame]);
553 [button setTarget:self];
555 // Store the |label| and |button| into MediaMenuParts struct and build
556 // the popup menu from the menu model.
557 content_setting_bubble::MediaMenuParts* menuParts =
558 new content_setting_bubble::MediaMenuParts(it->first, label);
559 menuParts->model.reset(new ContentSettingMediaMenuModel(
560 it->first, contentSettingBubbleModel_.get(),
561 ContentSettingMediaMenuModel::MenuLabelChangedCallback()));
562 mediaMenus_[button] = menuParts;
563 CGFloat width = BuildPopUpMenuFromModel(button,
564 menuParts->model.get(),
565 it->second.selected_device.name,
566 it->second.disabled);
567 maxMenuWidth = std::max(maxMenuWidth, width);
569 [[self bubble] addSubview:button
570 positioned:NSWindowBelow
573 maxMenuHeight = std::max(maxMenuHeight, [button frame].size.height);
576 // Make room for the media menu(s) and enlarge the windows to fit the views.
577 // The bubble view and its subviews autosize themselves when the window is
579 int delta = media_menus.size() * maxMenuHeight +
580 (media_menus.size() - 1) * kMediaMenuElementVerticalPadding;
581 NSSize deltaSize = NSMakeSize(0, delta);
582 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
583 NSRect windowFrame = [[self window] frame];
584 windowFrame.size.height += deltaSize.height;
585 // If the media menus are wider than the window, widen the window.
586 CGFloat widthNeeded = maxLabelWidth + maxMenuWidth + 2 * NSMinX(radioFrame);
587 if (widthNeeded > windowFrame.size.width)
588 windowFrame.size.width = widthNeeded;
589 [[self window] setFrame:windowFrame display:NO];
591 // The radio group lies above the media menus, move the radio group up.
592 radioFrame.origin.y += delta;
593 [allowBlockRadioGroup_ setFrame:radioFrame];
595 // Resize and reposition the media menus layout.
596 CGFloat topMenuY = NSMinY(radioFrame) - kMediaMenuVerticalPadding;
597 maxMenuWidth = std::max(maxMenuWidth, kMinMediaMenuButtonWidth);
598 for (content_setting_bubble::MediaMenuPartsMap::const_iterator i =
599 mediaMenus_.begin(); i != mediaMenus_.end(); ++i) {
600 NSRect labelFrame = [i->second->label frame];
601 // Align the label text with the button text.
602 labelFrame.origin.y =
603 topMenuY + (maxMenuHeight - labelFrame.size.height) / 2 + 1;
604 labelFrame.size.width = maxLabelWidth;
605 [i->second->label setFrame:labelFrame];
606 NSRect menuFrame = [i->first frame];
607 menuFrame.origin.y = topMenuY;
608 menuFrame.origin.x = NSMinX(radioFrame) + maxLabelWidth;
609 menuFrame.size.width = maxMenuWidth;
610 menuFrame.size.height = maxMenuHeight;
611 [i->first setFrame:menuFrame];
612 topMenuY -= (maxMenuHeight + kMediaMenuElementVerticalPadding);
616 - (void)initializeMIDISysExLists {
617 const ContentSettingBubbleModel::BubbleContent& content =
618 contentSettingBubbleModel_->bubble_content();
619 NSRect containerFrame = [contentsContainer_ frame];
621 NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
623 // "Clear" button / text field.
624 if (!content.custom_link.empty()) {
625 base::scoped_nsobject<NSControl> control;
626 if (content.custom_link_enabled) {
627 NSRect buttonFrame = NSMakeRect(0, 0,
628 NSWidth(containerFrame),
629 kMIDISysExClearButtonHeight);
630 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
631 control.reset(button);
632 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
633 [button setTarget:self];
634 [button setAction:@selector(clearMIDISysExForCurrentHost:)];
635 [button setBezelStyle:NSRoundRectBezelStyle];
636 SetControlSize(button, NSSmallControlSize);
639 // Add the notification that settings will be cleared on next reload.
640 control.reset([LabelWithFrame(
641 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
642 SetControlSize(control.get(), NSSmallControlSize);
645 // If the new control is wider than the container, widen the window.
646 CGFloat controlWidth = NSWidth([control frame]);
647 if (controlWidth > NSWidth(containerFrame)) {
648 NSRect windowFrame = [[self window] frame];
649 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
650 [[self window] setFrame:windowFrame display:NO];
651 // Fetch the updated sizes.
652 containerFrame = [contentsContainer_ frame];
653 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
657 [contentsContainer_ addSubview:control];
658 frame.origin.y = NSMaxY([control frame]) + kMIDISysExPadding;
661 for (auto i = content.domain_lists.rbegin();
662 i != content.domain_lists.rend(); ++i) {
663 // Add all hosts in the current domain list.
664 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
665 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
666 SetControlSize(title, NSSmallControlSize);
667 [contentsContainer_ addSubview:title];
669 frame.origin.y = NSMaxY(frame) + kMIDISysExHostPadding +
670 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
672 if (!i->hosts.empty())
673 frame.origin.y += kMIDISysExPadding - kMIDISysExHostPadding;
675 // Add the domain list's title.
677 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
678 SetControlSize(title, NSSmallControlSize);
679 [contentsContainer_ addSubview:title];
681 frame.origin.y = NSMaxY(frame) + kMIDISysExPadding +
682 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
685 CGFloat containerHeight = frame.origin.y;
686 // Undo last padding.
687 if (!content.domain_lists.empty())
688 containerHeight -= kMIDISysExPadding;
690 // Resize container to fit its subviews, and window to fit the container.
691 NSRect windowFrame = [[self window] frame];
692 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
693 [[self window] setFrame:windowFrame display:NO];
694 containerFrame.size.height = containerHeight;
695 [contentsContainer_ setFrame:containerFrame];
698 - (void)sizeToFitLoadButton {
699 const ContentSettingBubbleModel::BubbleContent& content =
700 contentSettingBubbleModel_->bubble_content();
701 [loadButton_ setEnabled:content.custom_link_enabled];
703 // Resize horizontally to fit button if necessary.
704 NSRect windowFrame = [[self window] frame];
705 int widthNeeded = NSWidth([loadButton_ frame]) +
706 2 * NSMinX([loadButton_ frame]);
707 if (NSWidth(windowFrame) < widthNeeded) {
708 windowFrame.size.width = widthNeeded;
709 [[self window] setFrame:windowFrame display:NO];
713 - (void)initManageDoneButtons {
714 const ContentSettingBubbleModel::BubbleContent& content =
715 contentSettingBubbleModel_->bubble_content();
716 [manageButton_ setTitle:base::SysUTF8ToNSString(content.manage_link)];
717 [GTMUILocalizerAndLayoutTweaker sizeToFitView:manageButton_];
719 CGFloat actualWidth = NSWidth([[[self window] contentView] frame]);
720 CGFloat requiredWidth = NSMaxX([manageButton_ frame]) + kManageDonePadding +
721 NSWidth([[doneButton_ superview] frame]) - NSMinX([doneButton_ frame]);
722 if (requiredWidth <= actualWidth || !doneButton_ || !manageButton_)
725 // Resize window, autoresizing takes care of the rest.
726 NSSize size = NSMakeSize(requiredWidth - actualWidth, 0);
727 size = [[[self window] contentView] convertSize:size toView:nil];
728 NSRect frame = [[self window] frame];
729 frame.origin.x -= size.width;
730 frame.size.width += size.width;
731 [[self window] setFrame:frame display:NO];
734 - (void)awakeFromNib {
735 [super awakeFromNib];
737 [[self bubble] setArrowLocation:info_bubble::kTopRight];
739 // Adapt window size to bottom buttons. Do this before all other layouting.
740 [self initManageDoneButtons];
742 [self initializeTitle];
744 ContentSettingsType type = contentSettingBubbleModel_->content_type();
745 if (type == CONTENT_SETTINGS_TYPE_PLUGINS) {
746 [self sizeToFitLoadButton];
747 [self initializeBlockedPluginsList];
750 if (allowBlockRadioGroup_) // not bound in cookie bubble xib
751 [self initializeRadioGroup];
753 if (type == CONTENT_SETTINGS_TYPE_POPUPS)
754 [self initializePopupList];
755 if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION)
756 [self initializeGeoLists];
757 if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM)
758 [self initializeMediaMenus];
759 if (type == CONTENT_SETTINGS_TYPE_MIDI_SYSEX)
760 [self initializeMIDISysExLists];
763 ///////////////////////////////////////////////////////////////////////////////
764 // Actual application logic
766 - (IBAction)allowBlockToggled:(id)sender {
767 NSButtonCell *selectedCell = [sender selectedCell];
768 contentSettingBubbleModel_->OnRadioClicked([selectedCell tag] - 1);
771 - (void)popupLinkClicked:(id)sender {
772 content_setting_bubble::PopupLinks::iterator i(popupLinks_.find(sender));
773 DCHECK(i != popupLinks_.end());
774 contentSettingBubbleModel_->OnPopupClicked(i->second);
777 - (void)clearGeolocationForCurrentHost:(id)sender {
778 contentSettingBubbleModel_->OnCustomLinkClicked();
782 - (void)clearMIDISysExForCurrentHost:(id)sender {
783 contentSettingBubbleModel_->OnCustomLinkClicked();
787 - (IBAction)showMoreInfo:(id)sender {
788 contentSettingBubbleModel_->OnCustomLinkClicked();
792 - (IBAction)load:(id)sender {
793 contentSettingBubbleModel_->OnCustomLinkClicked();
797 - (IBAction)learnMoreLinkClicked:(id)sender {
798 contentSettingBubbleModel_->OnManageLinkClicked();
801 - (IBAction)manageBlocking:(id)sender {
802 contentSettingBubbleModel_->OnManageLinkClicked();
805 - (IBAction)closeBubble:(id)sender {
806 contentSettingBubbleModel_->OnDoneClicked();
810 - (IBAction)mediaMenuChanged:(id)sender {
811 NSPopUpButton* button = static_cast<NSPopUpButton*>(sender);
812 content_setting_bubble::MediaMenuPartsMap::const_iterator it(
813 mediaMenus_.find(sender));
814 DCHECK(it != mediaMenus_.end());
815 NSInteger index = [[button selectedItem] tag];
817 SetTitleForPopUpButton(
818 button, base::SysUTF16ToNSString(it->second->model->GetLabelAt(index)));
820 it->second->model->ExecuteCommand(index, 0);
823 - (content_setting_bubble::MediaMenuPartsMap*)mediaMenus {
827 @end // ContentSettingBubbleController