Commit 405b5e35 authored by Elly Fong-Jones's avatar Elly Fong-Jones Committed by Commit Bot

mac: remove Cocoa PageInfo code

This code is dead since SecondaryUiMd has been on by default for several
releases now.

Bug: 832676
Change-Id: I5cfcdf8d1147dd3a788f0e8b57cf96a98d86f39e
Reviewed-on: https://chromium-review.googlesource.com/1148010Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Elly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577493}
parent 1a5ca118
......@@ -425,14 +425,6 @@ jumbo_split_static_library("ui") {
"cocoa/one_click_signin_dialog_controller.mm",
"cocoa/one_click_signin_view_controller.h",
"cocoa/one_click_signin_view_controller.mm",
"cocoa/page_info/page_info_bubble_controller.h",
"cocoa/page_info/page_info_bubble_controller.mm",
"cocoa/page_info/page_info_utils_cocoa.h",
"cocoa/page_info/page_info_utils_cocoa.mm",
"cocoa/page_info/permission_selector_button.h",
"cocoa/page_info/permission_selector_button.mm",
"cocoa/page_info/split_block_button.h",
"cocoa/page_info/split_block_button.mm",
"cocoa/password_reuse_warning_dialog_cocoa.h",
"cocoa/password_reuse_warning_dialog_cocoa.mm",
"cocoa/password_reuse_warning_view_controller.h",
......@@ -2622,8 +2614,6 @@ jumbo_split_static_library("ui") {
"cocoa/importer/import_lock_dialog_cocoa.mm",
"cocoa/login_handler_cocoa.h",
"cocoa/login_handler_cocoa.mm",
"cocoa/page_info/page_info_bubble_controller.h",
"cocoa/page_info/page_info_bubble_controller.mm",
"cocoa/password_reuse_warning_dialog_cocoa.h",
"cocoa/password_reuse_warning_dialog_cocoa.mm",
"cocoa/password_reuse_warning_view_controller.h",
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_PAGE_INFO_PAGE_INFO_BUBBLE_CONTROLLER_H_
#define CHROME_BROWSER_UI_COCOA_PAGE_INFO_PAGE_INFO_BUBBLE_CONTROLLER_H_
#import <Cocoa/Cocoa.h>
#include <memory>
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#import "chrome/browser/ui/cocoa/base_bubble_controller.h"
#include "chrome/browser/ui/page_info/page_info_ui.h"
#include "content/public/browser/web_contents_observer.h"
class LocationBarDecoration;
class PageInfoUIBridge;
@class InspectLinkView;
namespace content {
class WebContents;
}
namespace net {
class X509Certificate;
}
// This NSWindowController subclass manages the InfoBubbleWindow and view that
// are displayed when the user clicks the omnibox security indicator icon.
@interface PageInfoBubbleController : BaseBubbleController {
@private
content::WebContents* webContents_;
base::scoped_nsobject<NSView> contentView_;
// The main content view for the Permissions tab.
NSView* securitySectionView_;
// Displays the short security summary for the page
// (private/not private/etc.).
NSTextField* securitySummaryField_;
// Displays a longer explanation of the page's security state, and how the
// user should treat it.
NSTextField* securityDetailsField_;
// The link button for opening a Chrome Help Center page explaining connection
// security.
NSButton* connectionHelpButton_;
// URL of the page for which the bubble is shown.
GURL url_;
// Displays a paragraph to accompany the reset decisions button, explaining
// that the user has made a decision to trust an invalid security certificate
// for the current site.
// This field only shows when there is an acrive certificate exception.
NSTextField* resetDecisionsField_;
// The link button for revoking certificate decisions.
// This link only shows when there is an active certificate exception.
NSButton* resetDecisionsButton_;
// The server certificate from the identity info. This should always be
// non-null on a cryptographic connection, and null otherwise.
scoped_refptr<net::X509Certificate> certificate_;
// Separator line.
NSView* separatorAfterSecuritySection_;
// Container for the site settings section.
NSView* siteSettingsSectionView_;
// Container for certificate info in the site settings section.
InspectLinkView* certificateView_;
// Container for cookies info in the site settings section.
InspectLinkView* cookiesView_;
// Container for permission info in the site settings section.
NSView* permissionsView_;
// The link button for showing site settings.
NSButton* siteSettingsButton_;
// The UI translates user actions to specific events and forwards them to the
// |presenter_|. The |presenter_| handles these events and updates the UI.
std::unique_ptr<PageInfo> presenter_;
// Bridge which implements the PageInfoUI interface and forwards
// methods on to this class.
std::unique_ptr<PageInfoUIBridge> bridge_;
// The omnibox icon the bubble is anchored to. The icon is set as active
// when the bubble is opened, and inactive when the bubble is closed.
// Usually we would override OmniboxDecorationBubbleController but the page
// info icon has a race condition where it might switch between
// LocationIconDecoration and SecurityStateBubbleDecoration.
LocationBarDecoration* decoration_; // Weak.
// The button for changing password decisions.
// This button only shows when there is an password reuse event.
NSButton* changePasswordButton_;
// The button for whitelisting password reuse decisions.
// This button only shows when there is an password reuse event.
NSButton* whitelistPasswordReuseButton_;
}
// Designated initializer. The controller will release itself when the bubble
// is closed. |parentWindow| cannot be nil. |webContents| may be nil for
// testing purposes.
- (id)initWithParentWindow:(NSWindow*)parentWindow
pageInfoUIBridge:(PageInfoUIBridge*)bridge
webContents:(content::WebContents*)webContents
url:(const GURL&)url;
// Return the default width of the window. It may be wider to fit the content.
// This may be overriden by a subclass for testing purposes.
- (CGFloat)defaultWindowWidth;
@end
// Provides a bridge between the PageInfoUI C++ interface and the Cocoa
// implementation in PageInfoBubbleController.
class PageInfoUIBridge : public content::WebContentsObserver,
public PageInfoUI {
public:
explicit PageInfoUIBridge(content::WebContents* web_contents);
~PageInfoUIBridge() override;
void set_bubble_controller(PageInfoBubbleController* bubble_controller);
// WebContentsObserver implementation.
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
// PageInfoUI implementations.
void SetCookieInfo(const CookieInfoList& cookie_info_list) override;
void SetPermissionInfo(const PermissionInfoList& permission_info_list,
ChosenObjectInfoList chosen_object_info_list) override;
void SetIdentityInfo(const IdentityInfo& identity_info) override;
protected:
// WebContentsObserver implementation.
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
private:
// The WebContents the bubble UI is attached to.
content::WebContents* web_contents_;
// The Cocoa controller for the bubble UI.
PageInfoBubbleController* bubble_controller_;
DISALLOW_COPY_AND_ASSIGN(PageInfoUIBridge);
};
#endif // CHROME_BROWSER_UI_COCOA_PAGE_INFO_PAGE_INFO_BUBBLE_CONTROLLER_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/page_info/page_info_bubble_controller.h"
#import <AppKit/AppKit.h>
#include <cmath>
#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#import "chrome/browser/certificate_viewer.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/cocoa/browser_dialogs_views_mac.h"
#import "chrome/browser/ui/cocoa/browser_window_controller.h"
#include "chrome/browser/ui/cocoa/bubble_anchor_helper.h"
#import "chrome/browser/ui/cocoa/info_bubble_view.h"
#import "chrome/browser/ui/cocoa/info_bubble_window.h"
#include "chrome/browser/ui/cocoa/key_equivalent_constants.h"
#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
#import "chrome/browser/ui/cocoa/location_bar/page_info_bubble_decoration.h"
#import "chrome/browser/ui/cocoa/page_info/permission_selector_button.h"
#include "chrome/browser/ui/page_info/page_info_dialog.h"
#include "chrome/browser/ui/page_info/permission_menu_model.h"
#import "chrome/browser/ui/tab_dialogs.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/theme_resources.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/strings/grit/components_chromium_strings.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/ssl_host_state_delegate.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "skia/ext/skia_utils_mac.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
#import "ui/base/cocoa/a11y_util.h"
#include "ui/base/cocoa/cocoa_base_utils.h"
#import "ui/base/cocoa/controls/button_utils.h"
#import "ui/base/cocoa/controls/hyperlink_button_cell.h"
#import "ui/base/cocoa/flipped_view.h"
#import "ui/base/cocoa/hover_image_button.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/image/image_skia_util_mac.h"
#import "ui/gfx/mac/coordinate_conversion.h"
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
#include "ui/resources/grit/ui_resources.h"
using ChosenObjectInfoPtr = std::unique_ptr<PageInfoUI::ChosenObjectInfo>;
using ChosenObjectDeleteCallback =
base::Callback<void(const PageInfoUI::ChosenObjectInfo&)>;
namespace {
// General ---------------------------------------------------------------------
// The default width of the window, in view coordinates. It may be larger to
// fit the content.
constexpr CGFloat kDefaultWindowWidth = 320;
// Padding around each section
constexpr CGFloat kSectionVerticalPadding = 20;
constexpr CGFloat kSectionHorizontalPadding = 16;
// Links are buttons with invisible padding, so we need to move them back to
// align with other text.
constexpr CGFloat kLinkButtonXAdjustment = 1;
// Built-in margin for NSButton to take into account.
constexpr CGFloat kNSButtonBuiltinMargin = 4;
// Security Section ------------------------------------------------------------
// Spacing between security summary, security details, and cert decisions text.
constexpr CGFloat kSecurityParagraphSpacing = 12;
// Site Settings Section -------------------------------------------------------
// Square size of the permission images.
constexpr CGFloat kPermissionImageSize = 16;
// Spacing between a permission image and the text.
constexpr CGFloat kPermissionImageSpacing = 6;
// Minimum distance between the label and its corresponding menu.
constexpr CGFloat kMinSeparationBetweenLabelAndMenu = 16;
// Square size of the permission delete button image.
constexpr CGFloat kPermissionDeleteImageSize = 16;
// The spacing between individual permissions.
constexpr CGFloat kPermissionsVerticalSpacing = 16;
// Spacing to add after a permission label, either directly on top of
// kPermissionsVerticalSpacing, or before additional text (e.g. "X in use" for
// cookies).
constexpr CGFloat kPermissionLabelBottomPadding = 4;
// Amount to lower each permission icon to align the icon baseline with the
// label text.
constexpr CGFloat kPermissionIconYAdjustment = 1;
// Amount to lower each permission popup button to make its text align with the
// permission label.
constexpr CGFloat kPermissionPopupButtonYAdjustment = 3;
// Internal Page Bubble --------------------------------------------------------
// Padding between the window frame and content for the internal page bubble.
constexpr CGFloat kInternalPageFramePadding = 10;
// Spacing between the image and text for internal pages.
constexpr CGFloat kInternalPageImageSpacing = 10;
// -----------------------------------------------------------------------------
// A unique tag given to chosen object views (e.g. to show a site has access to
// a USB/Bluetooth device) in order to repopulate them on permissions updates.
// This number must not be the same as any permission in ContentSettingsType.
constexpr int kChosenObjectTag = CONTENT_SETTINGS_NUM_TYPES;
// NOTE: This assumes that there will never be more than one page info
// bubble shown, and that the one that is shown is associated with the current
// window. This matches the behaviour in Views: see PageInfoBubbleView.
PageInfoBubbleController* g_page_info_bubble = nullptr;
// Takes in the parent window, which should be a BrowserWindow, and gets the
// proper anchor point for the bubble. The returned point is in screen
// coordinates.
NSPoint AnchorPointForWindow(NSWindow* parent) {
Browser* browser = chrome::FindBrowserWithWindow(parent);
DCHECK(browser);
return GetPageInfoAnchorPointForBrowser(browser);
}
NSImage* GetNSImageFromImageSkia(const gfx::ImageSkia& image) {
return NSImageFromImageSkiaWithColorSpace(image,
base::mac::GetSRGBColorSpace());
}
SkColor GetRelatedTextColor() {
return skia::NSDeviceColorToSkColor(
[[NSColor textColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
}
} // namespace
// The |InspectLinkView| objects are used to show the Cookie and Certificate
// status and a link to inspect the underlying data.
@interface InspectLinkView : FlippedView
@end
@implementation InspectLinkView {
NSButton* actionLink_;
}
- (id)initWithFrame:(NSRect)frame {
if (self = [super initWithFrame:frame]) {
[self setAutoresizingMask:NSViewWidthSizable];
}
return self;
}
- (void)setActionLink:(NSButton*)actionLink {
actionLink_ = actionLink;
}
- (void)setLinkText:(NSString*)linkText {
[actionLink_ setTitle:linkText];
[GTMUILocalizerAndLayoutTweaker sizeToFitView:actionLink_];
}
- (void)setLinkToolTip:(NSString*)linkToolTip {
[actionLink_ setToolTip:linkToolTip];
}
- (void)setLinkTarget:(NSObject*)target withAction:(SEL)action {
[actionLink_ setTarget:target];
[actionLink_ setAction:action];
}
@end
@interface ChosenObjectDeleteButton : HoverImageButton {
@private
ChosenObjectInfoPtr objectInfo_;
ChosenObjectDeleteCallback callback_;
}
// Designated initializer. Takes ownership of |objectInfo|.
- (instancetype)initWithChosenObject:(ChosenObjectInfoPtr)objectInfo
atPoint:(NSPoint)point
withCallback:(ChosenObjectDeleteCallback)callback;
// Action when the button is clicked.
- (void)deleteClicked:(id)sender;
@end
@implementation ChosenObjectDeleteButton
- (instancetype)initWithChosenObject:(ChosenObjectInfoPtr)objectInfo
atPoint:(NSPoint)point
withCallback:(ChosenObjectDeleteCallback)callback {
NSRect frame = NSMakeRect(point.x, point.y, kPermissionDeleteImageSize,
kPermissionDeleteImageSize);
if (self = [super initWithFrame:frame]) {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
[self setDefaultImage:rb.GetNativeImageNamed(IDR_CLOSE_2).ToNSImage()];
[self setHoverImage:rb.GetNativeImageNamed(IDR_CLOSE_2_H).ToNSImage()];
[self setPressedImage:rb.GetNativeImageNamed(IDR_CLOSE_2_P).ToNSImage()];
[self setBordered:NO];
[self setToolTip:l10n_util::GetNSString(
objectInfo->ui_info.delete_tooltip_string_id)];
[self setTarget:self];
[self setAction:@selector(deleteClicked:)];
objectInfo_ = std::move(objectInfo);
callback_ = callback;
}
return self;
}
- (void)deleteClicked:(id)sender {
callback_.Run(*objectInfo_);
}
@end
@implementation PageInfoBubbleController
+ (PageInfoBubbleController*)getPageInfoBubbleForTest {
return g_page_info_bubble;
}
- (CGFloat)defaultWindowWidth {
return kDefaultWindowWidth;
}
bool IsInternalURL(const GURL& url) {
return url.SchemeIs(content::kChromeUIScheme) ||
url.SchemeIs(content::kChromeDevToolsScheme) ||
url.SchemeIs(extensions::kExtensionScheme) ||
url.SchemeIs(content::kViewSourceScheme);
}
- (id)initWithParentWindow:(NSWindow*)parentWindow
pageInfoUIBridge:(PageInfoUIBridge*)bridge
webContents:(content::WebContents*)webContents
url:(const GURL&)url {
DCHECK(parentWindow);
webContents_ = webContents;
url_ = url;
// Use an arbitrary height; it will be changed in performLayout.
NSRect contentRect = NSMakeRect(0, 0, [self defaultWindowWidth], 1);
// Create an empty window into which content is placed.
base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]);
if ((self = [super initWithWindow:window.get()
parentWindow:parentWindow
anchoredAt:NSZeroPoint])) {
[[self bubble] setArrowLocation:info_bubble::kTopLeading];
// Create the container view that uses flipped coordinates.
NSRect contentFrame = NSMakeRect(0, 0, [self defaultWindowWidth], 300);
contentView_.reset([[FlippedView alloc] initWithFrame:contentFrame]);
// Replace the window's content.
[[[self window] contentView]
setSubviews:[NSArray arrayWithObject:contentView_.get()]];
if (IsInternalURL(url_)) {
[self initializeContentsForInternalPage:url_];
} else {
[self initializeContents];
}
bridge_.reset(bridge);
bridge_->set_bubble_controller(self);
}
return self;
}
- (void)showWindow:(id)sender {
BrowserWindowController* controller = [BrowserWindowController
browserWindowControllerForWindow:[self parentWindow]];
LocationBarViewMac* locationBar = [controller locationBarBridge];
if (locationBar) {
decoration_ = locationBar->page_info_decoration();
decoration_->SetActive(true);
}
[super showWindow:sender];
}
- (void)close {
if (decoration_) {
decoration_->SetActive(false);
decoration_ = nullptr;
}
[super close];
}
- (Profile*)profile {
return Profile::FromBrowserContext(webContents_->GetBrowserContext());
}
- (void)windowWillClose:(NSNotification*)notification {
if (presenter_.get())
presenter_->OnUIClosing();
presenter_.reset();
[super windowWillClose:notification];
}
- (void)setPresenter:(PageInfo*)presenter {
presenter_.reset(presenter);
}
// Create the subviews for the bubble for internal Chrome pages.
- (void)initializeContentsForInternalPage:(const GURL&)url {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
int text = IDS_PAGE_INFO_INTERNAL_PAGE;
int icon = IDR_PRODUCT_LOGO_16;
if (url.SchemeIs(extensions::kExtensionScheme)) {
text = IDS_PAGE_INFO_EXTENSION_PAGE;
icon = IDR_PLUGINS_FAVICON;
} else if (url.SchemeIs(content::kViewSourceScheme)) {
text = IDS_PAGE_INFO_VIEW_SOURCE_PAGE;
// view-source scheme uses the same icon as chrome:// pages.
icon = IDR_PRODUCT_LOGO_16;
} else if (!url.SchemeIs(content::kChromeUIScheme) &&
!url.SchemeIs(content::kChromeDevToolsScheme)) {
NOTREACHED();
}
NSPoint controlOrigin =
NSMakePoint(kInternalPageFramePadding,
kInternalPageFramePadding + info_bubble::kBubbleArrowHeight);
NSImage* productLogoImage = rb.GetNativeImageNamed(icon).ToNSImage();
NSImageView* imageView = [self addImageWithSize:[productLogoImage size]
toView:contentView_
atPoint:controlOrigin];
[imageView setImage:productLogoImage];
NSRect imageFrame = [imageView frame];
controlOrigin.x += NSWidth(imageFrame) + kInternalPageImageSpacing;
NSTextField* textField = [self addText:l10n_util::GetStringUTF16(text)
withSize:[NSFont smallSystemFontSize]
bold:NO
toView:contentView_
atPoint:controlOrigin];
// Center the image vertically with the text. Previously this code centered
// the text vertically while holding the image in place. That produced correct
// results when the image, at 26x26, was taller than (or just slightly
// shorter) than the text, but produced incorrect results once the icon
// shrank to 16x16. The icon should now always be shorter than the text.
// See crbug.com/572044 .
NSRect textFrame = [textField frame];
imageFrame.origin.y += (NSHeight(textFrame) - NSHeight(imageFrame)) / 2;
[imageView setFrame:imageFrame];
// Adjust the contentView to fit everything.
CGFloat maxY = std::max(NSMaxY(imageFrame), NSMaxY(textFrame));
[contentView_ setFrame:NSMakeRect(0, 0, [self defaultWindowWidth],
maxY + kInternalPageFramePadding)];
[self sizeAndPositionWindow];
}
// Create the subviews for the page info bubble.
- (void)initializeContents {
securitySectionView_ = [self addSecuritySectionToView:contentView_];
separatorAfterSecuritySection_ = [self addSeparatorToView:contentView_];
siteSettingsSectionView_ = [self addSiteSettingsSectionToView:contentView_];
[self performLayout];
}
// Create and return a subview for the security section and add it to the given
// |superview|. |superview| retains the new view.
- (NSView*)addSecuritySectionToView:(NSView*)superview {
base::scoped_nsobject<NSView> securitySectionView(
[[FlippedView alloc] initWithFrame:[superview frame]]);
[superview addSubview:securitySectionView];
// Create a controlOrigin to place the text fields. The y value doesn't
// matter, because the correct value is calculated in -performLayout.
NSPoint controlOrigin = NSMakePoint(kSectionHorizontalPadding, 0);
// Create a text field for the security summary (private/not private/etc.).
securitySummaryField_ = [self addText:base::string16()
withSize:[NSFont systemFontSize]
bold:NO
toView:securitySectionView
atPoint:controlOrigin];
securityDetailsField_ = [self addText:base::string16()
withSize:[NSFont smallSystemFontSize]
bold:NO
toView:securitySectionView
atPoint:controlOrigin];
// These will be created only if necessary.
resetDecisionsField_ = nil;
resetDecisionsButton_ = nil;
changePasswordButton_ = nil;
whitelistPasswordReuseButton_ = nil;
NSString* connectionHelpButtonText = l10n_util::GetNSString(IDS_LEARN_MORE);
connectionHelpButton_ = [self addLinkButtonWithText:connectionHelpButtonText
toView:securitySectionView];
[connectionHelpButton_ setTarget:self];
[connectionHelpButton_ setAction:@selector(openConnectionHelp:)];
if (base::i18n::IsRTL()) {
securitySummaryField_.alignment = NSRightTextAlignment;
securityDetailsField_.alignment = NSRightTextAlignment;
}
return securitySectionView.get();
}
// Create and return a subview for the site settings and add it to the given
// |superview|. |superview| retains the new view.
- (NSView*)addSiteSettingsSectionToView:(NSView*)superview {
base::scoped_nsobject<NSView> siteSettingsSectionView(
[[FlippedView alloc] initWithFrame:[superview frame]]);
[superview addSubview:siteSettingsSectionView];
permissionsView_ =
[[[FlippedView alloc] initWithFrame:[superview frame]] autorelease];
[siteSettingsSectionView addSubview:permissionsView_];
// The certificate section is created on demand.
certificateView_ = nil;
// Initialize the two containers that hold the controls. The initial frames
// are arbitrary, and will be adjusted after the controls are laid out.
PageInfoUI::PermissionInfo info;
info.type = CONTENT_SETTINGS_TYPE_COOKIES;
info.setting = CONTENT_SETTING_ALLOW;
cookiesView_ = [self
addInspectLinkToView:siteSettingsSectionView
sectionIcon:GetNSImageFromImageSkia(
PageInfoUI::GetPermissionIcon(
info, GetRelatedTextColor()))
sectionTitle:l10n_util::GetStringUTF16(IDS_PAGE_INFO_COOKIES)
linkText:l10n_util::GetPluralNSStringF(
IDS_PAGE_INFO_NUM_COOKIES, 0)];
[cookiesView_ setLinkTarget:self
withAction:@selector(showCookiesAndSiteData:)];
// Create the link button to view site settings. Its position will be set in
// performLayout.
NSString* siteSettingsButtonText =
l10n_util::GetNSString(IDS_PAGE_INFO_SITE_SETTINGS_LINK);
siteSettingsButton_ = [self addButtonWithText:siteSettingsButtonText
toView:siteSettingsSectionView];
[GTMUILocalizerAndLayoutTweaker sizeToFitView:siteSettingsButton_];
[siteSettingsButton_ setTarget:self];
[siteSettingsButton_ setAction:@selector(showSiteSettingsData:)];
return siteSettingsSectionView.get();
}
- (InspectLinkView*)addInspectLinkToView:(NSView*)superview
sectionIcon:(NSImage*)imageIcon
sectionTitle:(const base::string16&)titleText
linkText:(NSString*)linkText {
// Create the subview.
base::scoped_nsobject<InspectLinkView> newView(
[[InspectLinkView alloc] initWithFrame:[superview frame]]);
[superview addSubview:newView];
bool isRTL = base::i18n::IsRTL();
NSPoint controlOrigin = NSMakePoint(kSectionHorizontalPadding, 0);
CGFloat viewWidth = NSWidth([newView frame]);
// Reset X for the icon.
if (isRTL) {
controlOrigin.x =
viewWidth - kPermissionImageSize - kSectionHorizontalPadding;
}
NSImageView* imageView = [self addImageWithSize:[imageIcon size]
toView:newView
atPoint:controlOrigin];
[imageView setImage:imageIcon];
NSButton* actionLink = [self addLinkButtonWithText:linkText toView:newView];
[newView setActionLink:actionLink];
if (isRTL) {
controlOrigin.x -= kPermissionImageSpacing;
NSTextField* sectionTitle = [self addText:titleText
withSize:[NSFont systemFontSize]
bold:NO
toView:newView
atPoint:controlOrigin];
[sectionTitle sizeToFit];
NSPoint sectionTitleOrigin = [sectionTitle frame].origin;
sectionTitleOrigin.x -= NSWidth([sectionTitle frame]);
[sectionTitle setFrameOrigin:sectionTitleOrigin];
// Align the icon with the text.
[self alignPermissionIcon:imageView withTextField:sectionTitle];
controlOrigin.y +=
NSHeight([sectionTitle frame]) + kPermissionLabelBottomPadding;
controlOrigin.x -= NSWidth([actionLink frame]) - kLinkButtonXAdjustment;
[actionLink setFrameOrigin:controlOrigin];
} else {
controlOrigin.x += kPermissionImageSize + kPermissionImageSpacing;
NSTextField* sectionTitle = [self addText:titleText
withSize:[NSFont systemFontSize]
bold:NO
toView:newView
atPoint:controlOrigin];
[sectionTitle sizeToFit];
// Align the icon with the text.
[self alignPermissionIcon:imageView withTextField:sectionTitle];
controlOrigin.y +=
NSHeight([sectionTitle frame]) + kPermissionLabelBottomPadding;
controlOrigin.x -= kLinkButtonXAdjustment;
[actionLink setFrameOrigin:controlOrigin];
}
controlOrigin.y += NSHeight([actionLink frame]);
[newView setFrameSize:NSMakeSize(NSWidth([newView frame]), controlOrigin.y)];
return newView.get();
}
// Handler for the link button below the list of cookies.
- (void)showCookiesAndSiteData:(id)sender {
DCHECK(webContents_);
DCHECK(presenter_);
presenter_->RecordPageInfoAction(PageInfo::PAGE_INFO_COOKIES_DIALOG_OPENED);
TabDialogs::FromWebContents(webContents_)->ShowCollectedCookies();
}
// Handler for the site settings button below the list of permissions.
- (void)showSiteSettingsData:(id)sender {
DCHECK(webContents_);
DCHECK(presenter_);
presenter_->OpenSiteSettingsView();
}
- (void)openConnectionHelp:(id)sender {
DCHECK(webContents_);
DCHECK(presenter_);
presenter_->RecordPageInfoAction(PageInfo::PAGE_INFO_CONNECTION_HELP_OPENED);
webContents_->OpenURL(content::OpenURLParams(
GURL(chrome::kPageInfoHelpCenterURL), content::Referrer(),
WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK,
false));
}
// Handler for the link button to show certificate information.
- (void)showCertificateInfo:(id)sender {
DCHECK(certificate_.get());
DCHECK(presenter_);
presenter_->RecordPageInfoAction(
PageInfo::PAGE_INFO_CERTIFICATE_DIALOG_OPENED);
ShowCertificateViewer(webContents_, [self parentWindow], certificate_.get());
}
// Handler for the link button to revoke user certificate decisions.
- (void)resetCertificateDecisions:(id)sender {
DCHECK(resetDecisionsButton_);
presenter_->OnRevokeSSLErrorBypassButtonPressed();
[self close];
}
// Handler for the button to change password decisions.
- (void)changePasswordDecisions:(id)sender {
DCHECK(changePasswordButton_);
presenter_->OnChangePasswordButtonPressed(webContents_);
[self close];
}
// Handler for the button to whitelist password reuse decisions.
- (void)whitelistPasswordReuseDecisions:(id)sender {
DCHECK(whitelistPasswordReuseButton_);
presenter_->OnWhitelistPasswordReuseButtonPressed(webContents_);
[self close];
}
- (CGFloat)layoutViewAtRTLStart:(NSView*)view withYPosition:(CGFloat)yPos {
CGFloat xPos;
if (base::i18n::IsRTL()) {
xPos = kDefaultWindowWidth - kSectionHorizontalPadding -
NSWidth([view frame]) + kNSButtonBuiltinMargin;
} else {
xPos = kSectionHorizontalPadding - kNSButtonBuiltinMargin;
}
[view setFrameOrigin:NSMakePoint(xPos, yPos - kNSButtonBuiltinMargin)];
return yPos + NSHeight([view frame]) - kNSButtonBuiltinMargin;
}
// Set the Y position of |view| to the given position, and return the position
// of its bottom edge.
- (CGFloat)setYPositionOfView:(NSView*)view to:(CGFloat)position {
NSRect frame = [view frame];
frame.origin.y = position;
[view setFrame:frame];
return position + NSHeight(frame);
}
- (void)setWidthOfView:(NSView*)view to:(CGFloat)width {
[view setFrameSize:NSMakeSize(width, NSHeight([view frame]))];
}
- (void)setHeightOfView:(NSView*)view to:(CGFloat)height {
[view setFrameSize:NSMakeSize(NSWidth([view frame]), height)];
}
// Layout all of the controls in the window. This should be called whenever
// the content has changed.
- (void)performLayout {
// Skip layout if the bubble is closing.
InfoBubbleWindow* bubbleWindow =
base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
if ([bubbleWindow isClosing])
return;
// Make the content at least as wide as the permissions view.
CGFloat contentWidth =
std::max([self defaultWindowWidth], NSWidth([permissionsView_ frame]));
// Set the width of the content view now, so that all the text fields will
// be sized to fit before their heights and vertical positions are adjusted.
[self setWidthOfView:contentView_ to:contentWidth];
[self setWidthOfView:securitySectionView_ to:contentWidth];
[self setWidthOfView:siteSettingsSectionView_ to:contentWidth];
CGFloat yPos = 0;
[self layoutSecuritySection];
yPos = [self setYPositionOfView:securitySectionView_ to:yPos];
yPos = [self setYPositionOfView:separatorAfterSecuritySection_ to:yPos];
[self layoutSiteSettingsSection];
yPos = [self setYPositionOfView:siteSettingsSectionView_ to:yPos];
[contentView_ setFrame:NSMakeRect(0, 0, NSWidth([contentView_ frame]), yPos)];
[self sizeAndPositionWindow];
}
- (void)layoutSecuritySection {
// Margins are handled by the caller.
CGFloat yPos = 0;
[self sizeTextFieldHeightToFit:securitySummaryField_];
yPos = [self setYPositionOfView:securitySummaryField_
to:yPos + kSectionVerticalPadding];
[self sizeTextFieldHeightToFit:securityDetailsField_];
yPos = [self setYPositionOfView:securityDetailsField_
to:yPos + kSecurityParagraphSpacing];
// A common anchor point for link elements
CGFloat linkY = kSectionHorizontalPadding - kLinkButtonXAdjustment;
NSPoint helpOrigin = NSMakePoint(linkY, yPos);
if (base::i18n::IsRTL()) {
helpOrigin.x = NSWidth([contentView_ frame]) - helpOrigin.x -
NSWidth(connectionHelpButton_.frame);
}
[connectionHelpButton_ setFrameOrigin:helpOrigin];
yPos = NSMaxY([connectionHelpButton_ frame]);
if (resetDecisionsButton_) {
DCHECK(resetDecisionsField_);
yPos = [self setYPositionOfView:resetDecisionsField_
to:yPos + kSecurityParagraphSpacing];
NSPoint resetOrigin = NSMakePoint(linkY, yPos);
if (base::i18n::IsRTL()) {
resetOrigin.x = NSWidth([contentView_ frame]) - resetOrigin.x -
NSWidth(resetDecisionsButton_.frame);
}
[resetDecisionsButton_ setFrameOrigin:resetOrigin];
yPos = NSMaxY([resetDecisionsButton_ frame]);
}
if (changePasswordButton_) {
NSPoint changePasswordButtonOrigin;
NSPoint whitelistReuseButtonOrigin;
CGFloat viewWidth = NSWidth([contentView_ frame]);
CGFloat changePasswordButtonWidth = NSWidth([changePasswordButton_ frame]);
CGFloat whitelistReuseButtonWidth =
NSWidth([whitelistPasswordReuseButton_ frame]);
CGFloat horizontalPadding =
kSectionHorizontalPadding - kNSButtonBuiltinMargin;
bool canFitInOneLine = changePasswordButtonWidth +
whitelistReuseButtonWidth +
2 * horizontalPadding <=
viewWidth;
bool isRTL = base::i18n::IsRTL();
// Buttons are left-aligned for LTR languages, and are right aligned for
// RTL languages. Button order follows OSX convention.
if (canFitInOneLine) {
whitelistReuseButtonOrigin.y = changePasswordButtonOrigin.y =
yPos + kSecurityParagraphSpacing;
if (isRTL) {
whitelistReuseButtonOrigin.x =
viewWidth - whitelistReuseButtonWidth - horizontalPadding;
changePasswordButtonOrigin.x =
whitelistReuseButtonOrigin.x - changePasswordButtonWidth;
} else {
whitelistReuseButtonOrigin.x = horizontalPadding;
changePasswordButtonOrigin.x =
whitelistReuseButtonOrigin.x + whitelistReuseButtonWidth;
}
} else {
// If these buttons cannot fit in one line, stack them vertically.
CGFloat buttonWidth = viewWidth - 2 * horizontalPadding;
whitelistReuseButtonOrigin.x = horizontalPadding;
whitelistReuseButtonOrigin.y = yPos + kSecurityParagraphSpacing;
[whitelistPasswordReuseButton_
setFrameSize:NSMakeSize(
buttonWidth,
NSHeight([whitelistPasswordReuseButton_ frame]))];
changePasswordButtonOrigin.x = horizontalPadding;
changePasswordButtonOrigin.y =
yPos + kSecurityParagraphSpacing +
NSHeight([whitelistPasswordReuseButton_ frame]);
[changePasswordButton_
setFrameSize:NSMakeSize(buttonWidth,
NSHeight([changePasswordButton_ frame]))];
}
[changePasswordButton_ setFrameOrigin:changePasswordButtonOrigin];
[whitelistPasswordReuseButton_ setFrameOrigin:whitelistReuseButtonOrigin];
yPos = NSMaxY([changePasswordButton_ frame]) - kNSButtonBuiltinMargin;
}
// Resize the height based on contents.
[self setHeightOfView:securitySectionView_ to:yPos + kSectionVerticalPadding];
}
- (void)layoutSiteSettingsSection {
// Margins are handled by the caller.
CGFloat yPos = 0;
yPos = [self setYPositionOfView:permissionsView_ to:yPos] +
kPermissionsVerticalSpacing;
if (certificateView_) {
yPos = [self setYPositionOfView:certificateView_ to:yPos] +
kPermissionsVerticalSpacing;
}
yPos =
[self setYPositionOfView:cookiesView_ to:yPos] + kSectionVerticalPadding;
yPos = [self layoutViewAtRTLStart:siteSettingsButton_ withYPosition:yPos] +
kSectionVerticalPadding;
// Resize the height based on contents.
[self setHeightOfView:siteSettingsSectionView_ to:yPos];
}
// Adjust the size of the window to match the size of the content, and position
// the bubble anchor appropriately.
- (void)sizeAndPositionWindow {
NSRect windowFrame = [contentView_ frame];
windowFrame.size =
[[[self window] contentView] convertSize:windowFrame.size toView:nil];
// Adjust the origin by the difference in height.
windowFrame.origin = [[self window] frame].origin;
windowFrame.origin.y -=
NSHeight(windowFrame) - NSHeight([[self window] frame]);
// Resize the window. Only animate if the window is visible, otherwise it
// could be "growing" while it's opening, looking awkward.
[[self window] setFrame:windowFrame
display:YES
animate:[[self window] isVisible]];
// Adjust the anchor for the bubble.
[self setAnchorPoint:AnchorPointForWindow([self parentWindow])];
}
// Sets properties on the given |field| to act as the title or description
// labels in the bubble.
- (void)configureTextFieldAsLabel:(NSTextField*)textField {
[textField setEditable:NO];
[textField setSelectable:YES];
[textField setDrawsBackground:NO];
[textField setBezeled:NO];
}
// Adjust the height of the given text field to match its text.
- (void)sizeTextFieldHeightToFit:(NSTextField*)textField {
NSRect frame = [textField frame];
frame.size.height +=
[GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:textField];
[textField setFrame:frame];
}
// Create a new text field and add it to the given array of subviews.
// The array will retain a reference to the object.
- (NSTextField*)addText:(const base::string16&)text
withSize:(CGFloat)fontSize
bold:(BOOL)bold
toView:(NSView*)view
atPoint:(NSPoint)point {
// Size the text to take up the full available width, with some padding.
// The height is arbitrary as it will be adjusted later.
CGFloat width = NSWidth([view frame]) - point.x - kSectionHorizontalPadding;
NSRect frame = NSMakeRect(point.x, point.y, width, 100);
base::scoped_nsobject<NSTextField> textField(
[[NSTextField alloc] initWithFrame:frame]);
[self configureTextFieldAsLabel:textField.get()];
[textField setStringValue:base::SysUTF16ToNSString(text)];
NSFont* font = bold ? [NSFont boldSystemFontOfSize:fontSize]
: [NSFont systemFontOfSize:fontSize];
[textField setFont:font];
[self sizeTextFieldHeightToFit:textField];
[textField setAutoresizingMask:NSViewWidthSizable];
[view addSubview:textField.get()];
return textField.get();
}
// Add an image as a subview of the given view, placed at a pre-determined x
// position and the given y position. The image is not in the accessibility
// order, since the image is always accompanied by text in this bubble. Return
// the new NSImageView.
- (NSImageView*)addImageWithSize:(NSSize)size
toView:(NSView*)view
atPoint:(NSPoint)point {
NSRect frame = NSMakeRect(point.x, point.y, size.width, size.height);
base::scoped_nsobject<NSImageView> imageView(
[[NSImageView alloc] initWithFrame:frame]);
ui::a11y_util::HideImageFromAccessibilityOrder(imageView);
[imageView setImageFrameStyle:NSImageFrameNone];
[view addSubview:imageView.get()];
return imageView.get();
}
// Add a separator as a subview of the given view. Return the new view.
- (NSView*)addSeparatorToView:(NSView*)view {
// Use an arbitrary position; it will be adjusted in performLayout.
NSBox* spacer = [self
horizontalSeparatorWithFrame:NSMakeRect(0, 0, NSWidth([view frame]), 0)];
[view addSubview:spacer];
return spacer;
}
// Add a link button with the given text to |view|.
- (NSButton*)addLinkButtonWithText:(NSString*)text toView:(NSView*)view {
// Frame size is arbitrary; it will be adjusted by the layout tweaker.
NSRect frame = NSMakeRect(kSectionHorizontalPadding, 0, 100, 10);
base::scoped_nsobject<NSButton> button(
[[NSButton alloc] initWithFrame:frame]);
base::scoped_nsobject<HyperlinkButtonCell> cell(
[[HyperlinkButtonCell alloc] initTextCell:text]);
[cell setControlSize:NSSmallControlSize];
[button setCell:cell.get()];
[button setButtonType:NSMomentaryPushInButton];
[button setBezelStyle:NSRegularSquareBezelStyle];
[view addSubview:button.get()];
[GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()];
return button.get();
}
// Create and return a button with the specified text and add it to the given
// |view|. |view| retains the new button.
- (NSButton*)addButtonWithText:(NSString*)text toView:(NSView*)view {
NSRect containerFrame = [view frame];
// Frame size is arbitrary; it will be adjusted by the layout tweaker.
NSRect frame = NSMakeRect(kSectionHorizontalPadding, 0, 100, 10);
base::scoped_nsobject<NSButton> button(
[[NSButton alloc] initWithFrame:frame]);
// Determine the largest possible size for this button. The size is the width
// of the connection section minus the padding on both sides minus the
// connection image size and spacing.
CGFloat maxTitleWidth =
containerFrame.size.width - kSectionHorizontalPadding * 2;
base::scoped_nsobject<NSButtonCell> cell(
[[NSButtonCell alloc] initTextCell:text]);
[button setCell:cell.get()];
[GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()];
// Ensure the containing view is large enough to contain the button with its
// widest possible title.
NSRect buttonFrame = [button frame];
buttonFrame.size.width = maxTitleWidth;
[button setFrame:buttonFrame];
[button setButtonType:NSMomentaryPushInButton];
[button setBezelStyle:NSRegularSquareBezelStyle];
[view addSubview:button.get()];
return button.get();
}
// Set the content of the identity and identity status fields, and add the
// Certificate view or password reuse buttons if applicable.
- (void)setIdentityInfo:(const PageInfoUI::IdentityInfo&)identityInfo {
std::unique_ptr<PageInfoUI::SecurityDescription> security_description =
identityInfo.GetSecurityDescription();
[securitySummaryField_
setStringValue:base::SysUTF16ToNSString(security_description->summary)];
[securityDetailsField_
setStringValue:base::SysUTF16ToNSString(security_description->details)];
certificate_ = identityInfo.certificate;
if (certificate_) {
if (identityInfo.show_ssl_decision_revoke_button) {
resetDecisionsField_ =
[self addText:base::string16()
withSize:[NSFont smallSystemFontSize]
bold:NO
toView:securitySectionView_
atPoint:NSMakePoint(kSectionHorizontalPadding, 0)];
[resetDecisionsField_
setStringValue:l10n_util::GetNSString(
IDS_PAGE_INFO_INVALID_CERTIFICATE_DESCRIPTION)];
[self sizeTextFieldHeightToFit:resetDecisionsField_];
resetDecisionsButton_ = [self
addLinkButtonWithText:
l10n_util::GetNSString(
IDS_PAGE_INFO_RESET_INVALID_CERTIFICATE_DECISIONS_BUTTON)
toView:securitySectionView_];
[resetDecisionsButton_ setTarget:self];
[resetDecisionsButton_ setAction:@selector(resetCertificateDecisions:)];
if (base::i18n::IsRTL()) {
resetDecisionsField_.alignment = NSRightTextAlignment;
}
}
// Show information about the page's certificate.
bool isValid =
(identityInfo.identity_status != PageInfo::SITE_IDENTITY_STATUS_ERROR);
NSString* linkText = l10n_util::GetNSString(
isValid ? IDS_PAGE_INFO_CERTIFICATE_VALID_LINK
: IDS_PAGE_INFO_CERTIFICATE_INVALID_LINK);
certificateView_ =
[self addInspectLinkToView:siteSettingsSectionView_
sectionIcon:NSImageFromImageSkia(
PageInfoUI::GetCertificateIcon(
GetRelatedTextColor()))
sectionTitle:l10n_util::GetStringUTF16(
IDS_PAGE_INFO_CERTIFICATE)
linkText:linkText];
if (isValid) {
[certificateView_
setLinkToolTip:l10n_util::GetNSStringF(
IDS_PAGE_INFO_CERTIFICATE_VALID_LINK_TOOLTIP,
base::UTF8ToUTF16(
certificate_->issuer().GetDisplayName()))];
} else {
[certificateView_
setLinkToolTip:l10n_util::GetNSString(
IDS_PAGE_INFO_CERTIFICATE_INVALID_LINK_TOOLTIP)];
}
[certificateView_ setLinkTarget:self
withAction:@selector(showCertificateInfo:)];
}
if (identityInfo.show_change_password_buttons) {
whitelistPasswordReuseButton_ = [ButtonUtils
buttonWithTitle:l10n_util::GetNSString(
IDS_PAGE_INFO_WHITELIST_PASSWORD_REUSE_BUTTON)
action:@selector(whitelistPasswordReuseDecisions:)
target:self];
[whitelistPasswordReuseButton_ sizeToFit];
[whitelistPasswordReuseButton_ setKeyEquivalent:kKeyEquivalentEscape];
[securitySectionView_ addSubview:whitelistPasswordReuseButton_];
changePasswordButton_ =
[ButtonUtils buttonWithTitle:l10n_util::GetNSString(
IDS_PAGE_INFO_CHANGE_PASSWORD_BUTTON)
action:@selector(changePasswordDecisions:)
target:self];
[changePasswordButton_ sizeToFit];
[changePasswordButton_ setKeyEquivalent:kKeyEquivalentReturn];
[securitySectionView_ addSubview:changePasswordButton_];
}
[self performLayout];
}
// Add a pop-up button for |permissionInfo| to the given view.
- (NSPopUpButton*)addPopUpButtonForPermission:
(const PageInfoUI::PermissionInfo&)permissionInfo
toView:(NSView*)view
atPoint:(NSPoint)point {
GURL url = webContents_ ? webContents_->GetURL() : GURL();
__block PageInfoBubbleController* weakSelf = self;
PermissionMenuModel::ChangeCallback callback = base::BindRepeating(
base::RetainBlock(^(const PageInfoUI::PermissionInfo& permission) {
[weakSelf onPermissionChanged:permission.type to:permission.setting];
}));
base::scoped_nsobject<PermissionSelectorButton> button(
[[PermissionSelectorButton alloc] initWithPermissionInfo:permissionInfo
forURL:url
withCallback:callback
profile:[self profile]]);
// Determine the largest possible size for this button.
CGFloat maxTitleWidth =
[button maxTitleWidthForContentSettingsType:permissionInfo.type
withDefaultSetting:permissionInfo.default_setting
profile:[self profile]];
// Ensure the containing view is large enough to contain the button with its
// widest possible title.
NSRect containerFrame = [view frame];
containerFrame.size.width =
std::max(NSWidth(containerFrame),
point.x + maxTitleWidth + kSectionHorizontalPadding);
[view setFrame:containerFrame];
[view addSubview:button.get()];
// Tag the button with the permission type so it can be updated later.
[button setTag:permissionInfo.type];
return button.get();
}
// Add a delete button for |objectInfo| to the given view.
- (NSButton*)addDeleteButtonForChosenObject:(ChosenObjectInfoPtr)objectInfo
toView:(NSView*)view
atPoint:(NSPoint)point {
__block PageInfoBubbleController* weakSelf = self;
auto callback = base::BindRepeating(
base::RetainBlock(^(const PageInfoUI::ChosenObjectInfo& objectInfo) {
[weakSelf onChosenObjectDeleted:objectInfo];
}));
base::scoped_nsobject<ChosenObjectDeleteButton> button(
[[ChosenObjectDeleteButton alloc]
initWithChosenObject:std::move(objectInfo)
atPoint:point
withCallback:callback]);
// Ensure the containing view is large enough to contain the button.
NSRect containerFrame = [view frame];
containerFrame.size.width =
std::max(NSWidth(containerFrame), point.x + kPermissionDeleteImageSize +
kSectionHorizontalPadding);
[view setFrame:containerFrame];
[view addSubview:button.get()];
[button setTag:kChosenObjectTag];
return button.get();
}
// Called when the user changes the setting of a permission.
- (void)onPermissionChanged:(ContentSettingsType)permissionType
to:(ContentSetting)newSetting {
if (presenter_)
presenter_->OnSitePermissionChanged(permissionType, newSetting);
}
// Called when the user revokes permission for a previously chosen object.
- (void)onChosenObjectDeleted:(const PageInfoUI::ChosenObjectInfo&)info {
if (presenter_)
presenter_->OnSiteChosenObjectDeleted(info.ui_info, *info.object);
}
// Adds a new row to the UI listing the permissions. Returns the NSPoint of the
// last UI element added (either the permission button, in LTR, or the text
// label, in RTL).
- (NSPoint)addPermission:(const PageInfoUI::PermissionInfo&)permissionInfo
toView:(NSView*)view
atPoint:(NSPoint)point {
base::string16 labelText =
PageInfoUI::PermissionTypeToUIString(permissionInfo.type);
bool isRTL = base::i18n::IsRTL();
base::scoped_nsobject<NSImage> image(
[GetNSImageFromImageSkia(PageInfoUI::GetPermissionIcon(
permissionInfo, GetRelatedTextColor())) retain]);
NSPoint position;
NSImageView* imageView;
NSPopUpButton* button;
NSTextField* label;
CGFloat viewWidth = NSWidth([view frame]);
if (isRTL) {
point.x = NSWidth([view frame]) - kPermissionImageSize -
kSectionHorizontalPadding;
imageView = [self addImageWithSize:[image size] toView:view atPoint:point];
[imageView setImage:image];
point.x -= kPermissionImageSpacing;
label = [self addText:labelText
withSize:[NSFont systemFontSize]
bold:NO
toView:view
atPoint:point];
[label sizeToFit];
point.x -= NSWidth([label frame]);
[label setFrameOrigin:point];
position =
NSMakePoint(point.x, point.y + kPermissionPopupButtonYAdjustment);
button = [self addPopUpButtonForPermission:permissionInfo
toView:view
atPoint:position];
position.x -= NSWidth([button frame]);
[button setFrameOrigin:position];
} else {
imageView = [self addImageWithSize:[image size] toView:view atPoint:point];
[imageView setImage:image];
point.x += kPermissionImageSize + kPermissionImageSpacing;
label = [self addText:labelText
withSize:[NSFont systemFontSize]
bold:NO
toView:view
atPoint:point];
[label sizeToFit];
position = NSMakePoint(NSMaxX([label frame]),
point.y + kPermissionPopupButtonYAdjustment);
button = [self addPopUpButtonForPermission:permissionInfo
toView:view
atPoint:position];
}
[label setToolTip:base::SysUTF16ToNSString(labelText)];
[view setFrameSize:NSMakeSize(viewWidth, NSHeight([view frame]))];
// Adjust the vertical position of the button so that its title text is
// aligned with the label. Assumes that the text is the same size in both.
// Also adjust the horizontal position to remove excess space due to the
// invisible bezel.
NSRect titleRect = [[button cell] titleRectForBounds:[button bounds]];
if (isRTL) {
position.x = kSectionHorizontalPadding;
} else {
position.x = kDefaultWindowWidth - kSectionHorizontalPadding -
[button frame].size.width;
}
position.y -= titleRect.origin.y;
[button setFrameOrigin:position];
// Truncate the label if it's too wide.
// This is a workaround for https://crbug.com/654268 until MacViews ships.
NSRect labelFrame = [label frame];
if (isRTL) {
CGFloat maxLabelWidth = NSMaxX(labelFrame) - NSMaxX([button frame]) -
kMinSeparationBetweenLabelAndMenu;
if (NSWidth(labelFrame) > maxLabelWidth) {
labelFrame.origin.x = NSMaxX(labelFrame) - maxLabelWidth;
labelFrame.size.width = maxLabelWidth;
[label setFrame:labelFrame];
[[label cell] setLineBreakMode:NSLineBreakByTruncatingTail];
}
} else {
CGFloat maxLabelWidth = NSMinX([button frame]) - NSMinX(labelFrame) -
kMinSeparationBetweenLabelAndMenu;
if (NSWidth(labelFrame) > maxLabelWidth) {
labelFrame.size.width = maxLabelWidth;
[label setFrame:labelFrame];
[[label cell] setLineBreakMode:NSLineBreakByTruncatingTail];
}
}
// Align the icon with the text.
[self alignPermissionIcon:imageView withTextField:label];
// Permissions specified by policy or an extension cannot be changed.
if (permissionInfo.source == content_settings::SETTING_SOURCE_EXTENSION ||
permissionInfo.source == content_settings::SETTING_SOURCE_POLICY) {
[button setEnabled:NO];
}
// Update |point| to match the y of the bottomost UI element added (|button|).
NSRect buttonFrame = [button frame];
point.y = NSMaxY(labelFrame) + kPermissionLabelBottomPadding;
// Show the reason for the permission decision in a new row if it did not come
// from the user.
base::string16 reason = PageInfoUI::PermissionDecisionReasonToUIString(
[self profile], permissionInfo, url_);
if (!reason.empty()) {
// Do this even in RTL to make sure -addText sets the right width for the
// permission decision reason label.
point.x = kSectionHorizontalPadding + kPermissionImageSize +
kPermissionImageSpacing;
label = [self addText:reason
withSize:[NSFont smallSystemFontSize]
bold:NO
toView:view
atPoint:point];
if (isRTL) {
[label setAlignment:NSRightTextAlignment];
// Shift the reason left to align the permission label and the permission
// decision reason's right edges.
point.x -= (kPermissionImageSize + kPermissionImageSpacing);
[label setFrameOrigin:point];
}
label.textColor =
skia::SkColorToSRGBNSColor(PageInfoUI::GetSecondaryTextColor());
point.y += NSHeight(label.frame);
}
return NSMakePoint(NSMaxX(buttonFrame), point.y);
}
// Adds a new row to the UI listing the permissions. Returns the NSPoint of the
// last UI element added (either the permission button, in LTR, or the text
// label, in RTL).
- (NSPoint)addChosenObject:(ChosenObjectInfoPtr)objectInfo
toView:(NSView*)view
atPoint:(NSPoint)point {
base::string16 labelText = l10n_util::GetStringFUTF16(
objectInfo->ui_info.label_string_id,
PageInfoUI::ChosenObjectToUIString(*objectInfo));
bool isRTL = base::i18n::IsRTL();
base::scoped_nsobject<NSImage> image(
[GetNSImageFromImageSkia(PageInfoUI::GetChosenObjectIcon(
*objectInfo, false, GetRelatedTextColor())) retain]);
NSPoint position;
NSImageView* imageView;
NSButton* button;
NSTextField* label;
CGFloat viewWidth = NSWidth([view frame]);
if (isRTL) {
point.x = NSWidth([view frame]) - kPermissionImageSize -
kPermissionImageSpacing - kSectionHorizontalPadding;
imageView = [self addImageWithSize:[image size] toView:view atPoint:point];
[imageView setImage:image];
point.x -= kPermissionImageSpacing;
label = [self addText:labelText
withSize:[NSFont systemFontSize]
bold:NO
toView:view
atPoint:point];
[label sizeToFit];
point.x -= NSWidth([label frame]);
[label setFrameOrigin:point];
position = NSMakePoint(point.x, point.y);
button = [self addDeleteButtonForChosenObject:std::move(objectInfo)
toView:view
atPoint:position];
position.x -= NSWidth([button frame]);
[button setFrameOrigin:position];
} else {
imageView = [self addImageWithSize:[image size] toView:view atPoint:point];
[imageView setImage:image];
point.x += kPermissionImageSize + kPermissionImageSpacing;
label = [self addText:labelText
withSize:[NSFont systemFontSize]
bold:NO
toView:view
atPoint:point];
[label sizeToFit];
position = NSMakePoint(NSMaxX([label frame]), point.y);
button = [self addDeleteButtonForChosenObject:std::move(objectInfo)
toView:view
atPoint:position];
}
[imageView setTag:kChosenObjectTag];
[label setTag:kChosenObjectTag];
[view setFrameSize:NSMakeSize(viewWidth, NSHeight([view frame]))];
// Adjust the vertical position of the button so that its title text is
// aligned with the label. Assumes that the text is the same size in both.
// Also adjust the horizontal position to remove excess space due to the
// invisible bezel.
NSRect titleRect = [[button cell] titleRectForBounds:[button bounds]];
position.y -= titleRect.origin.y;
[button setFrameOrigin:position];
// Align the icon with the text.
[self alignPermissionIcon:imageView withTextField:label];
NSRect buttonFrame = [button frame];
return NSMakePoint(NSMaxX(buttonFrame), NSMaxY(buttonFrame));
}
// Align an image with a text field by vertically centering the image on
// the cap height of the first line of text.
- (void)alignPermissionIcon:(NSImageView*)imageView
withTextField:(NSTextField*)textField {
NSRect frame = [imageView frame];
frame.origin.y += kPermissionIconYAdjustment;
[imageView setFrame:frame];
}
- (void)setCookieInfo:(const CookieInfoList&)cookieInfoList {
// |cookieInfoList| should only ever have 2 items: first- and third-party
// cookies.
DCHECK_EQ(cookieInfoList.size(), 2u);
int totalAllowed = 0;
for (const auto& i : cookieInfoList) {
totalAllowed += i.allowed;
}
[cookiesView_ setLinkText:l10n_util::GetPluralNSStringF(
IDS_PAGE_INFO_NUM_COOKIES, totalAllowed)];
[self performLayout];
}
- (void)setPermissionInfo:(const PermissionInfoList&)permissionInfoList
andChosenObjects:(ChosenObjectInfoList)chosenObjectInfoList {
NSPoint controlOrigin = NSMakePoint(kSectionHorizontalPadding, 0);
// If |permissionsView_| is already populated, just handle updates to
// permissions made by the user here. This will avoid removing/adding new
// views (and thus breaking the responder chain), and also avoid immediately
// removing permissions set back to the default, which could be user error.
// Note that "update" means setPermissionInfo will only change the title of
// the permission |PermissionSelectorButton|. This is OK because it is not
// possible for the following to occur without closing the Page Info bubble:
// - a permission gets changed away from the factory default (and thus needs
// to be shown in Page Info)
// - a permission's source changes (and becomes disabled / needs to show a
// reason).
if ([permissionsView_ subviews].count != 0) {
NSView* view = nil;
// Remove all the chosen object views. They will be repopulated if the site
// still has access to them.
while ((view = [permissionsView_ viewWithTag:kChosenObjectTag]))
[view removeFromSuperview];
for (view in [permissionsView_ subviews]) {
// Skip views that don't need to be modified (default tags are -1 or 0).
if ([view tag] <= 0)
continue;
ContentSettingsType permissionType =
static_cast<ContentSettingsType>([view tag]);
PermissionSelectorButton* button =
base::mac::ObjCCastStrict<PermissionSelectorButton>(view);
const int yOrigin = [button frame].origin.y;
// Permissions set back to the factory default setting will disappear from
// |permissionInfoList|, so use |updated| to keep track of whether
// |button| has been updated with its new permission value yet.
bool updated = false;
for (const auto& permission : permissionInfoList) {
if (permissionType != permission.type)
continue;
updated = true;
[button setButtonTitle:permission profile:[self profile]];
break;
}
if (!updated) {
// Permissions that are no longer in |permissionInfoList| have been set
// back to factory default settings.
PageInfoUI::PermissionInfo default_info;
default_info.type = permissionType;
default_info.setting = CONTENT_SETTING_DEFAULT;
default_info.default_setting =
content_settings::ContentSettingsRegistry::GetInstance()
->Get(permissionType)
->GetInitialDefaultSetting();
default_info.source = content_settings::SETTING_SOURCE_USER;
default_info.is_incognito = [self profile]->IsOffTheRecord();
[button setButtonTitle:default_info profile:[self profile]];
}
// Updating the text might have changed the width of the
// |PermissionSelectorRow|, so reposition here.
if (base::i18n::IsRTL()) {
[button setFrameOrigin:NSMakePoint(kSectionHorizontalPadding, yOrigin)];
} else {
[button setFrameOrigin:NSMakePoint(NSWidth([permissionsView_ frame]) -
kSectionHorizontalPadding -
NSWidth([button frame]),
yOrigin)];
}
}
if ([[permissionsView_ subviews] count] != 0) {
controlOrigin.y =
NSMaxY([[[permissionsView_ subviews] lastObject] frame]);
}
} else {
// Creates permissions views (if any) for the first time.
for (const auto& permission : permissionInfoList) {
controlOrigin.y += kPermissionsVerticalSpacing;
NSPoint rowBottomRight = [self addPermission:permission
toView:permissionsView_
atPoint:controlOrigin];
controlOrigin.y = rowBottomRight.y;
}
}
for (auto& object : chosenObjectInfoList) {
controlOrigin.y += kPermissionsVerticalSpacing;
NSPoint rowBottomRight = [self addChosenObject:std::move(object)
toView:permissionsView_
atPoint:controlOrigin];
controlOrigin.y = rowBottomRight.y;
}
// |permissionsView_| was updated here, so make sure keyboard access still
// works by updating the responder chain.
[[self window] recalculateKeyViewLoop];
[permissionsView_ setFrameSize:NSMakeSize(NSWidth([permissionsView_ frame]),
controlOrigin.y)];
[self performLayout];
}
@end
PageInfoUIBridge::PageInfoUIBridge(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
web_contents_(web_contents),
bubble_controller_(nil) {
DCHECK(!g_page_info_bubble);
}
PageInfoUIBridge::~PageInfoUIBridge() {
DCHECK(g_page_info_bubble);
g_page_info_bubble = nullptr;
}
void PageInfoUIBridge::set_bubble_controller(
PageInfoBubbleController* controller) {
bubble_controller_ = controller;
g_page_info_bubble = controller;
}
void PageInfoUIBridge::SetIdentityInfo(
const PageInfoUI::IdentityInfo& identity_info) {
[bubble_controller_ setIdentityInfo:identity_info];
}
void PageInfoUIBridge::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (render_frame_host == web_contents_->GetMainFrame()) {
[bubble_controller_ close];
}
}
void PageInfoUIBridge::SetCookieInfo(const CookieInfoList& cookie_info_list) {
[bubble_controller_ setCookieInfo:cookie_info_list];
}
void PageInfoUIBridge::SetPermissionInfo(
const PermissionInfoList& permission_info_list,
ChosenObjectInfoList chosen_object_info_list) {
[bubble_controller_ setPermissionInfo:permission_info_list
andChosenObjects:std::move(chosen_object_info_list)];
}
void PageInfoUIBridge::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInMainFrame() ||
!navigation_handle->HasCommitted()) {
return;
}
// If the browser navigates to another page, close the bubble.
[bubble_controller_ close];
}
void ShowPageInfoDialogImpl(Browser* browser,
content::WebContents* web_contents,
const GURL& virtual_url,
const security_state::SecurityInfo& security_info,
bubble_anchor_util::Anchor anchor) {
if (chrome::ShowAllDialogsWithViewsToolkit()) {
chrome::ShowPageInfoBubbleViews(browser, web_contents, virtual_url,
security_info, anchor);
return;
}
// Don't show the bubble if it's already being shown. Since this method is
// called each time the location icon is clicked, each click toggles the
// bubble in and out.
if (g_page_info_bubble)
return;
// Create the bridge. This will be owned by the bubble controller.
PageInfoUIBridge* bridge = new PageInfoUIBridge(web_contents);
NSWindow* parent = browser->window()->GetNativeWindow();
// Create the bubble controller. It will dealloc itself when it closes,
// resetting |g_page_info_bubble|.
PageInfoBubbleController* bubble_controller =
[[PageInfoBubbleController alloc] initWithParentWindow:parent
pageInfoUIBridge:bridge
webContents:web_contents
url:virtual_url];
if (!IsInternalURL(virtual_url)) {
// Initialize the presenter, which holds the model and controls the UI.
// This is also owned by the bubble controller.
PageInfo* presenter =
new PageInfo(bridge, browser->profile(),
TabSpecificContentSettings::FromWebContents(web_contents),
web_contents, virtual_url, security_info);
[bubble_controller setPresenter:presenter];
}
[bubble_controller showWindow:nil];
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/page_info/page_info_bubble_controller.h"
#include <stddef.h>
#include "base/i18n/rtl.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/cocoa/browser_window_controller.h"
#include "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
#import "chrome/browser/ui/cocoa/location_bar/page_info_bubble_decoration.h"
#import "chrome/browser/ui/cocoa/test/cocoa_profile_test.h"
#include "content/public/test/test_web_contents_factory.h"
#include "net/test/test_certificate_data.h"
#include "testing/gtest_mac.h"
@interface PageInfoBubbleController (ExposedForTesting)
- (NSView*)permissionsView;
- (NSButton*)resetDecisionsButton;
- (NSButton*)connectionHelpButton;
@end
@implementation PageInfoBubbleController (ExposedForTesting)
- (NSView*)permissionsView {
return permissionsView_;
}
- (NSButton*)resetDecisionsButton {
return resetDecisionsButton_;
}
- (NSButton*)connectionHelpButton {
return connectionHelpButton_;
}
- (NSButton*)changePasswordButton {
return changePasswordButton_;
}
- (NSButton*)whitelistPasswordReuseButton {
return whitelistPasswordReuseButton_;
}
@end
@interface PageInfoBubbleControllerForTesting : PageInfoBubbleController {
@private
CGFloat defaultWindowWidth_;
}
@end
@implementation PageInfoBubbleControllerForTesting
- (void)setDefaultWindowWidth:(CGFloat)width {
defaultWindowWidth_ = width;
}
- (CGFloat)defaultWindowWidth {
// If |defaultWindowWidth_| is 0, use the superclass implementation.
return defaultWindowWidth_ ? defaultWindowWidth_ : [super defaultWindowWidth];
}
@end
namespace {
// Indices of the menu items in the permission menu.
enum PermissionMenuIndices {
kMenuIndexContentSettingAllow = 0,
kMenuIndexContentSettingBlock,
kMenuIndexContentSettingDefault
};
const ContentSettingsType kTestPermissionTypes[] = {
CONTENT_SETTINGS_TYPE_IMAGES,
CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
CONTENT_SETTINGS_TYPE_JAVASCRIPT,
CONTENT_SETTINGS_TYPE_PLUGINS,
CONTENT_SETTINGS_TYPE_POPUPS,
CONTENT_SETTINGS_TYPE_GEOLOCATION,
CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC};
const ContentSetting kTestSettings[] = {
CONTENT_SETTING_DEFAULT, CONTENT_SETTING_DEFAULT, CONTENT_SETTING_DEFAULT,
CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW,
CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK};
const ContentSetting kTestDefaultSettings[] = {
CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, CONTENT_SETTING_ASK};
const content_settings::SettingSource kTestSettingSources[] = {
content_settings::SETTING_SOURCE_USER,
content_settings::SETTING_SOURCE_USER,
content_settings::SETTING_SOURCE_USER,
content_settings::SETTING_SOURCE_USER,
content_settings::SETTING_SOURCE_USER,
content_settings::SETTING_SOURCE_POLICY,
content_settings::SETTING_SOURCE_POLICY,
content_settings::SETTING_SOURCE_EXTENSION};
class PageInfoBubbleControllerTest : public CocoaProfileTest {
public:
PageInfoBubbleControllerTest() { controller_ = nil; }
void TearDown() override {
[controller_ close];
CocoaProfileTest::TearDown();
}
protected:
PageInfoUIBridge* bridge_; // Weak, owned by controller.
enum MatchType { TEXT_EQUAL = 0, TEXT_NOT_EQUAL };
// Creates a new page info bubble, with the given default width.
// If |default_width| is 0, the *default* default width will be used.
void CreateBubbleWithWidth(CGFloat default_width) {
bridge_ = new PageInfoUIBridge(nullptr);
// The controller cleans up after itself when the window closes.
controller_ = [PageInfoBubbleControllerForTesting alloc];
[controller_ setDefaultWindowWidth:default_width];
[controller_ initWithParentWindow:browser()->window()->GetNativeWindow()
pageInfoUIBridge:bridge_
webContents:web_contents_factory_.CreateWebContents(
browser()->profile())
url:GURL("https://www.google.com")];
window_ = [controller_ window];
[controller_ showWindow:nil];
}
void CreateBubble() { CreateBubbleWithWidth(0.0); }
// Return a pointer to the first NSTextField found that either matches, or
// doesn't match, the given text.
NSTextField* FindTextField(MatchType match_type, NSString* text) {
// The window's only immediate child is an invisible view that has a flipped
// coordinate origin. It is into this that all views get placed.
NSArray* window_subviews = [[window_ contentView] subviews];
EXPECT_EQ(1U, [window_subviews count]);
NSArray* bubble_subviews = [[window_subviews lastObject] subviews];
NSArray* security_section_subviews =
[[bubble_subviews firstObject] subviews];
/**
*Expect 3 views:
* - the identity
* - identity status
* - security details link
*/
EXPECT_EQ(3U, [security_section_subviews count]);
bool desired_result = match_type == TEXT_EQUAL;
for (NSView* view in security_section_subviews) {
if ([view isKindOfClass:[NSTextField class]]) {
NSTextField* text_field = static_cast<NSTextField*>(view);
if ([[text_field stringValue] isEqual:text] == desired_result)
return text_field;
}
}
return nil;
}
NSMutableArray* FindAllSubviewsOfClass(NSView* parent_view, Class a_class) {
NSMutableArray* views = [NSMutableArray array];
for (NSView* view in [parent_view subviews]) {
if ([view isKindOfClass:a_class])
[views addObject:view];
}
return views;
}
// Sets up the dialog with some test permission settings.
void SetTestPermissions() {
// Create a list of 5 different permissions, corresponding to all the
// possible settings:
// - [allow, block, ask] by default
// - [block, allow] * by user
PermissionInfoList permission_info_list;
PageInfoUI::PermissionInfo info;
for (size_t i = 0; i < arraysize(kTestPermissionTypes); ++i) {
info.type = kTestPermissionTypes[i];
info.setting = kTestSettings[i];
if (info.setting == CONTENT_SETTING_DEFAULT)
info.default_setting = kTestDefaultSettings[i];
info.source = kTestSettingSources[i];
info.is_incognito = false;
permission_info_list.push_back(info);
}
ChosenObjectInfoList chosen_object_info_list;
bridge_->SetPermissionInfo(permission_info_list,
std::move(chosen_object_info_list));
}
int NumSettingsNotSetByUser() const {
int num_non_user_settings = 0;
for (size_t i = 0; i < arraysize(kTestSettingSources); ++i) {
num_non_user_settings +=
(kTestSettingSources[i] != content_settings::SETTING_SOURCE_USER) ? 1
: 0;
}
return num_non_user_settings;
}
content::TestWebContentsFactory web_contents_factory_;
PageInfoBubbleControllerForTesting* controller_; // Weak, owns self.
NSWindow* window_; // Weak, owned by controller.
};
TEST_F(PageInfoBubbleControllerTest, ConnectionHelpButton) {
PageInfoUI::IdentityInfo info;
info.site_identity = std::string("example.com");
info.identity_status = PageInfo::SITE_IDENTITY_STATUS_UNKNOWN;
CreateBubble();
bridge_->SetIdentityInfo(const_cast<PageInfoUI::IdentityInfo&>(info));
EXPECT_EQ([[controller_ connectionHelpButton] action],
@selector(openConnectionHelp:));
}
TEST_F(PageInfoBubbleControllerTest, ResetDecisionsButton) {
PageInfoUI::IdentityInfo info;
info.site_identity = std::string("example.com");
info.identity_status = PageInfo::SITE_IDENTITY_STATUS_UNKNOWN;
CreateBubble();
// Set identity info, specifying that the button should not be shown.
info.show_ssl_decision_revoke_button = false;
bridge_->SetIdentityInfo(const_cast<PageInfoUI::IdentityInfo&>(info));
EXPECT_EQ([controller_ resetDecisionsButton], nil);
// Set identity info, specifying that the button should be shown.
info.certificate = net::X509Certificate::CreateFromBytes(
reinterpret_cast<const char*>(google_der), sizeof(google_der));
ASSERT_TRUE(info.certificate);
info.show_ssl_decision_revoke_button = true;
bridge_->SetIdentityInfo(const_cast<PageInfoUI::IdentityInfo&>(info));
EXPECT_NE([controller_ resetDecisionsButton], nil);
// Check that clicking the button calls the right selector.
EXPECT_EQ([[controller_ resetDecisionsButton] action],
@selector(resetCertificateDecisions:));
// Since the bubble is only created once per identity, we only need to check
// the button is *added* when needed. So we don't check that it's removed
// when we set an identity with `show_ssl_decision_revoke_button == false`
// again.
}
TEST_F(PageInfoBubbleControllerTest, SetPermissionInfo) {
CreateBubble();
SetTestPermissions();
// There should be three subviews per permission.
NSArray* subviews = [[controller_ permissionsView] subviews];
EXPECT_EQ(arraysize(kTestPermissionTypes) * 3,
[subviews count] - NumSettingsNotSetByUser());
// Ensure that there is a label for each permission.
NSMutableArray* permission_labels = [NSMutableArray array];
for (NSView* view in subviews) {
if ([view isKindOfClass:[NSTextField class]])
[permission_labels
addObject:[static_cast<NSTextField*>(view) stringValue]];
}
EXPECT_EQ(arraysize(kTestPermissionTypes),
[permission_labels count] - NumSettingsNotSetByUser());
// Ensure that the button labels are distinct, and look for the correct
// number of disabled buttons.
int disabled_count = 0;
NSMutableSet* button_labels = [NSMutableSet set];
for (NSView* view in subviews) {
if ([view isKindOfClass:[NSPopUpButton class]]) {
NSPopUpButton* button = static_cast<NSPopUpButton*>(view);
[button_labels addObject:[[button selectedCell] title]];
if (![button isEnabled])
++disabled_count;
}
}
EXPECT_EQ(5UL, [button_labels count]);
// Permissions with a setting source of SETTING_SOURCE_POLICY or
// SETTING_SOURCE_EXTENSION should have their buttons disabled.
EXPECT_EQ(NumSettingsNotSetByUser(), disabled_count);
}
TEST_F(PageInfoBubbleControllerTest, WindowWidth) {
const CGFloat kBigEnoughBubbleWidth = 310;
// Creating a window that should fit everything.
CreateBubbleWithWidth(kBigEnoughBubbleWidth);
SetTestPermissions();
CGFloat window_width = NSWidth([[controller_ window] frame]);
// Check the window was made bigger to fit the content.
EXPECT_EQ(kBigEnoughBubbleWidth, window_width);
// Check that the window is wider than the right edge of all the permission
// popup buttons (LTR locales) or wider than the left edge (RTL locales).
bool is_rtl = base::i18n::IsRTL();
for (NSView* view in [[controller_ permissionsView] subviews]) {
if (is_rtl) {
if ([view isKindOfClass:[NSPopUpButton class]]) {
NSPopUpButton* button = static_cast<NSPopUpButton*>(view);
EXPECT_GT(NSMinX([button frame]), 0);
}
if ([view isKindOfClass:[NSImageView class]]) {
NSImageView* icon = static_cast<NSImageView*>(view);
EXPECT_LT(NSMaxX([icon frame]), window_width);
}
} else {
if ([view isKindOfClass:[NSImageView class]]) {
NSImageView* icon = static_cast<NSImageView*>(view);
EXPECT_GT(NSMinX([icon frame]), 0);
}
if ([view isKindOfClass:[NSPopUpButton class]]) {
NSPopUpButton* button = static_cast<NSPopUpButton*>(view);
EXPECT_LT(NSMaxX([button frame]), window_width);
}
}
}
}
// Tests the page icon decoration's active state.
TEST_F(PageInfoBubbleControllerTest, PageIconDecorationActiveState) {
NSWindow* window = browser()->window()->GetNativeWindow();
BrowserWindowController* controller =
[BrowserWindowController browserWindowControllerForWindow:window];
LocationBarDecoration* decoration =
[controller locationBarBridge]->page_info_decoration();
CreateBubble();
EXPECT_TRUE([[controller_ window] isVisible]);
EXPECT_TRUE(decoration->active());
[controller_ close];
EXPECT_FALSE(decoration->active());
}
TEST_F(PageInfoBubbleControllerTest, PasswordReuseButtons) {
PageInfoUI::IdentityInfo info;
info.site_identity = std::string("example.com");
info.identity_status = PageInfo::SITE_IDENTITY_STATUS_UNKNOWN;
CreateBubble();
// Set identity info, specifying that buttons should not be shown.
info.show_change_password_buttons = false;
bridge_->SetIdentityInfo(const_cast<PageInfoUI::IdentityInfo&>(info));
EXPECT_EQ([controller_ changePasswordButton], nil);
EXPECT_EQ([controller_ whitelistPasswordReuseButton], nil);
// Set identity info, specifying that buttons should be shown.
info.show_change_password_buttons = true;
bridge_->SetIdentityInfo(const_cast<PageInfoUI::IdentityInfo&>(info));
EXPECT_NE([controller_ changePasswordButton], nil);
EXPECT_NE([controller_ whitelistPasswordReuseButton], nil);
// Check that clicking the button calls the right selector.
EXPECT_EQ([[controller_ changePasswordButton] action],
@selector(changePasswordDecisions:));
EXPECT_EQ([[controller_ whitelistPasswordReuseButton] action],
@selector(whitelistPasswordReuseDecisions:));
}
} // namespace
......@@ -66,7 +66,6 @@ IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewsMacTest, NoCrashOnFullScreenToggle) {
access_manager->fullscreen_controller();
fullscreen_controller->ToggleBrowserFullscreenMode();
if (ui::MaterialDesignController::IsSecondaryUiMaterial()) {
views::BubbleDialogDelegateView* page_info =
PageInfoBubbleView::GetPageInfoBubble();
EXPECT_TRUE(page_info);
......@@ -74,11 +73,6 @@ IN_PROC_BROWSER_TEST_P(PageInfoBubbleViewsMacTest, NoCrashOnFullScreenToggle) {
EXPECT_TRUE(page_info_bubble);
EXPECT_EQ(GetParam().bubble_type, PageInfoBubbleView::GetShownBubbleType());
EXPECT_TRUE(page_info_bubble->IsVisible());
} else {
EXPECT_TRUE([PageInfoBubbleController getPageInfoBubbleForTest]);
// In Cocoa, the crash occurs when the bubble tries to re-layout.
[[PageInfoBubbleController getPageInfoBubbleForTest] performLayout];
}
// There should be no crash here from re-anchoring the Page Info bubble while
// transitioning into full screen.
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_PAGE_INFO_PAGE_INFO_UTILS_COCOA_H_
#define CHROME_BROWSER_UI_COCOA_PAGE_INFO_PAGE_INFO_UTILS_COCOA_H_
#import <Cocoa/Cocoa.h>
NSSize SizeForPageInfoButtonTitle(NSPopUpButton* button, NSString* title);
#endif // CHROME_BROWSER_UI_COCOA_PAGE_INFO_PAGE_INFO_UTILS_COCOA_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/cocoa/page_info/page_info_utils_cocoa.h"
namespace {
// The amount of horizontal space between the button's title and its arrow icon.
const CGFloat kButtonTitleRightPadding = 4.0f;
}
// Determine the size of a popup button with the given title.
NSSize SizeForPageInfoButtonTitle(NSPopUpButton* button, NSString* title) {
NSDictionary* textAttributes =
[[button attributedTitle] attributesAtIndex:0 effectiveRange:NULL];
NSSize titleSize = [title sizeWithAttributes:textAttributes];
NSRect frame = [button frame];
NSRect titleRect = [[button cell] titleRectForBounds:frame];
CGFloat width = titleSize.width + NSWidth(frame) - NSWidth(titleRect);
return NSMakeSize(width + kButtonTitleRightPadding, NSHeight(frame));
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_PAGE_INFO_PERMISSION_SELECTOR_BUTTON_H_
#define CHROME_BROWSER_UI_COCOA_PAGE_INFO_PERMISSION_SELECTOR_BUTTON_H_
#import <Cocoa/Cocoa.h>
#include <memory>
#include "base/mac/scoped_nsobject.h"
#include "chrome/browser/ui/page_info/permission_menu_model.h"
#include "components/content_settings/core/common/content_settings.h"
@class MenuControllerCocoa;
class Profile;
@interface PermissionSelectorButton : NSPopUpButton {
@private
std::unique_ptr<PermissionMenuModel> menuModel_;
base::scoped_nsobject<MenuControllerCocoa> menuController_;
}
// Designated initializer.
- (id)initWithPermissionInfo:(const PageInfoUI::PermissionInfo&)permissionInfo
forURL:(const GURL&)url
withCallback:(PermissionMenuModel::ChangeCallback)callback
profile:(Profile*)profile;
// Returns the largest possible size given all of the items in the menu.
- (CGFloat)maxTitleWidthForContentSettingsType:(ContentSettingsType)type
withDefaultSetting:(ContentSetting)defaultSetting
profile:(Profile*)profile;
// Updates the title of the NSPopUpButton and resizes it to fit the new text.
- (void)setButtonTitle:(const PageInfoUI::PermissionInfo&)permissionInfo
profile:(Profile*)profile;
@end
#endif // CHROME_BROWSER_UI_COCOA_PAGE_INFO_PERMISSION_SELECTOR_BUTTON_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/page_info/permission_selector_button.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/ui/cocoa/page_info/page_info_utils_cocoa.h"
#include "chrome/browser/ui/page_info/page_info_ui.h"
#import "ui/base/cocoa/menu_controller.h"
@implementation PermissionSelectorButton
- (id)initWithPermissionInfo:(const PageInfoUI::PermissionInfo&)permissionInfo
forURL:(const GURL&)url
withCallback:(PermissionMenuModel::ChangeCallback)callback
profile:(Profile*)profile {
if (self = [super initWithFrame:NSMakeRect(0, 0, 1, 1) pullsDown:NO]) {
[self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
[self setBordered:NO];
[[self cell] setControlSize:NSSmallControlSize];
menuModel_.reset(
new PermissionMenuModel(profile, url, permissionInfo, callback));
menuController_.reset([[MenuControllerCocoa alloc]
initWithModel:menuModel_.get()
useWithPopUpButtonCell:NO]);
[self setMenu:[menuController_ menu]];
[self selectItemWithTag:permissionInfo.setting];
[self setButtonTitle:permissionInfo profile:profile];
NSString* description = base::SysUTF16ToNSString(
PageInfoUI::PermissionTypeToUIString(permissionInfo.type));
[[self cell]
accessibilitySetOverrideValue:description
forAttribute:NSAccessibilityDescriptionAttribute];
}
return self;
}
- (CGFloat)maxTitleWidthForContentSettingsType:(ContentSettingsType)type
withDefaultSetting:(ContentSetting)defaultSetting
profile:(Profile*)profile {
// Determine the largest possible size for this button.
CGFloat maxTitleWidth = 0;
for (NSMenuItem* item in [self itemArray]) {
NSString* title =
base::SysUTF16ToNSString(PageInfoUI::PermissionActionToUIString(
profile, type, static_cast<ContentSetting>([item tag]),
defaultSetting, content_settings::SETTING_SOURCE_USER));
NSSize size = SizeForPageInfoButtonTitle(self, title);
maxTitleWidth = std::max(maxTitleWidth, size.width);
}
return maxTitleWidth;
}
// Accessor function for testing only.
- (NSMenu*)permissionMenu {
return [menuController_ menu];
}
- (void)setButtonTitle:(const PageInfoUI::PermissionInfo&)permissionInfo
profile:(Profile*)profile {
// Set the button title.
base::scoped_nsobject<NSMenuItem> titleItem([[NSMenuItem alloc] init]);
base::string16 buttonTitle = PageInfoUI::PermissionActionToUIString(
profile, permissionInfo.type, permissionInfo.setting,
permissionInfo.default_setting, permissionInfo.source);
[titleItem setTitle:base::SysUTF16ToNSString(buttonTitle)];
[[self cell] setUsesItemFromMenu:NO];
[[self cell] setMenuItem:titleItem];
// Although the frame is reset, below, this sizes the cell properly.
[self sizeToFit];
// Size the button to just fit the visible title - not all of its items.
[self setFrameSize:SizeForPageInfoButtonTitle(self, [self title])];
}
@end
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/page_info/permission_selector_button.h"
#include "base/mac/scoped_nsobject.h"
#include "base/test/scoped_feature_list.h"
#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
#include "chrome/browser/ui/page_info/page_info_ui.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "ui/base/ui_base_features.h"
@interface PermissionSelectorButton (Testing)
- (NSMenu*)permissionMenu;
@end
namespace {
const ContentSettingsType kTestPermissionType =
CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC;
class PermissionSelectorButtonTest : public CocoaTest {
public:
PermissionSelectorButtonTest() {
// This file only tests Cocoa UI and can be deleted when kSecondaryUiMd is
// default.
scoped_feature_list_.InitAndDisableFeature(features::kSecondaryUiMd);
got_callback_ = false;
PageInfoUI::PermissionInfo test_info;
test_info.type = kTestPermissionType;
test_info.setting = CONTENT_SETTING_BLOCK;
test_info.source = content_settings::SETTING_SOURCE_USER;
test_info.is_incognito = false;
GURL test_url("http://www.google.com");
PermissionMenuModel::ChangeCallback callback = base::Bind(
&PermissionSelectorButtonTest::Callback, base::Unretained(this));
view_.reset([[PermissionSelectorButton alloc]
initWithPermissionInfo:test_info
forURL:test_url
withCallback:callback
profile:&profile_]);
[[test_window() contentView] addSubview:view_];
}
void Callback(const PageInfoUI::PermissionInfo& permission) {
EXPECT_TRUE(permission.type == kTestPermissionType);
got_callback_ = true;
}
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
bool got_callback_;
base::scoped_nsobject<PermissionSelectorButton> view_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(PermissionSelectorButtonTest);
};
TEST_VIEW(PermissionSelectorButtonTest, view_);
TEST_F(PermissionSelectorButtonTest, Callback) {
NSMenu* menu = [view_ permissionMenu];
NSMenuItem* item = [menu itemAtIndex:0];
[[item target] performSelector:[item action] withObject:item];
EXPECT_TRUE(got_callback_);
}
} // namespace
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_COCOA_PAGE_INFO_SPLIT_BLOCK_BUTTON_H_
#define CHROME_BROWSER_UI_COCOA_PAGE_INFO_SPLIT_BLOCK_BUTTON_H_
#import <Cocoa/Cocoa.h>
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
#include "ui/base/models/simple_menu_model.h"
@class SplitButtonPopUpCell;
@class SplitButtonTitleCell;
// Block ('deny') button for the permissions bubble. Subclassed from
// ConstrainedWindowButton, so that it shares styling, but contains two cells
// instead of just one. The left cell behaves as a normal button, and when
// clicked, calls the button's |action| on its |target|. The right cell behaves
// as a NSPopUpButtonCell, and implements a single-item menu.
@interface SplitBlockButton : ConstrainedWindowButton {
@private
base::scoped_nsobject<SplitButtonTitleCell> leftCell_;
base::scoped_nsobject<SplitButtonPopUpCell> rightCell_;
ui::ScopedCrTrackingArea leftTrackingArea_;
ui::ScopedCrTrackingArea rightTrackingArea_;
}
// Designated initializer.
- (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate;
@end
#endif // CHROME_BROWSER_UI_COCOA_PAGE_INFO_SPLIT_BLOCK_BUTTON_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/page_info/split_block_button.h"
#include <cmath>
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "chrome/grit/generated_resources.h"
#include "skia/ext/skia_utils_mac.h"
#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/models/simple_menu_model.h"
namespace {
enum MouseLocation {
kInsideLeftCell,
kInsideRightCell,
kNotInside,
};
enum CornerType {
kRounded,
kAngled,
};
NSBezierPath* PathWithCornerStyles(NSRect frame,
CornerType leftCornerStyle,
CornerType rightCornerStyle) {
base::scoped_nsobject<NSBezierPath> path([[NSBezierPath bezierPath] retain]);
const CGFloat x0 = NSMinX(frame);
const CGFloat x1 = NSMaxX(frame);
const CGFloat y0 = NSMinY(frame);
const CGFloat y1 = NSMaxY(frame);
const CGFloat radius = 2;
// Start at the center bottom. Draw left and up, including both left corners.
[path moveToPoint:NSMakePoint(std::floor((x1 - x0) * .5), y0)];
if (leftCornerStyle == kAngled) {
[path lineToPoint:NSMakePoint(x0, y0)];
[path lineToPoint:NSMakePoint(x0, y1)];
} else {
[path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y0)
toPoint:NSMakePoint(x0, y0 + radius)
radius:radius];
[path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y1)
toPoint:NSMakePoint(x0 + radius, y1)
radius:radius];
}
// Draw through the upper right-hand and lower-right-hand corners.
if (rightCornerStyle == kAngled) {
[path lineToPoint:NSMakePoint(x1, y1)];
[path lineToPoint:NSMakePoint(x1, y0)];
} else {
[path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y1)
toPoint:NSMakePoint(x1, y1 - radius)
radius:radius];
[path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y0)
toPoint:NSMakePoint(x1 - radius, y0)
radius:radius];
}
return path.autorelease();
}
void DrawBezel(id<ConstrainedWindowButtonDrawableCell> cell,
CornerType leftCorners,
CornerType rightCorners,
NSRect frame,
NSView* view) {
if ([cell isMouseInside]) {
base::scoped_nsobject<NSBezierPath> path(
[PathWithCornerStyles(frame, leftCorners, rightCorners) retain]);
[ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
withCell:cell
inView:view];
[ConstrainedWindowButton DrawInnerHighlightForPath:path
withCell:cell
inView:view];
}
}
} // namespace
// A button cell used by SplitBlockButton, containing the title.
@interface SplitButtonTitleCell : ConstrainedWindowButtonCell
- (NSRect)rect;
@end
// A button cell used by SplitBlockButton, containing the popup menu.
@interface SplitButtonPopUpCell
: NSPopUpButtonCell<ConstrainedWindowButtonDrawableCell> {
@private
BOOL isMouseInside_;
base::scoped_nsobject<MenuControllerCocoa> menuController_;
std::unique_ptr<ui::SimpleMenuModel> menuModel_;
}
// Designated initializer.
- (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate;
- (NSRect)rect;
@end
@implementation SplitBlockButton
- (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
if (self = [super initWithFrame:NSZeroRect]) {
leftCell_.reset([[SplitButtonTitleCell alloc] init]);
rightCell_.reset(
[[SplitButtonPopUpCell alloc] initWithMenuDelegate:menuDelegate]);
[leftCell_ setTitle:l10n_util::GetNSString(IDS_PERMISSION_DENY)];
[leftCell_ setEnabled:YES];
[rightCell_ setEnabled:YES];
}
return self;
}
+ (Class)cellClass {
return nil;
}
- (NSString*)title {
return [leftCell_ title];
}
- (void)setAction:(SEL)action {
[leftCell_ setAction:action];
}
- (void)setTarget:(id)target {
[leftCell_ setTarget:target];
}
- (void)drawRect:(NSRect)rect {
// Copy the base class: inset to leave room for the shadow.
--rect.size.height;
// This function assumes that |rect| is always the same as [self frame].
// If that changes, the drawing functions will need to be adjusted.
const CGFloat radius = 2;
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
[ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
withCell:nil
inView:self];
// Use intersection rects for the cell drawing, to ensure the height
// adjustment is honored.
[leftCell_ setControlView:self];
[leftCell_ drawWithFrame:NSIntersectionRect(rect, [self leftCellRect])
inView:self];
[rightCell_ setControlView:self];
[rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect])
inView:self];
// Draw the border.
path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 0.5, 0.5)
xRadius:radius
yRadius:radius];
[ConstrainedWindowButton DrawBorderForPath:path withCell:nil inView:self];
}
- (void)updateTrackingAreas {
[self updateTrackingArea:&leftTrackingArea_
forCell:leftCell_
withRect:[self leftCellRect]];
[self updateTrackingArea:&rightTrackingArea_
forCell:rightCell_
withRect:[self rightCellRect]];
}
- (void)updateTrackingArea:(ui::ScopedCrTrackingArea*)trackingArea
forCell:(id<ConstrainedWindowButtonDrawableCell>)cell
withRect:(NSRect)rect {
DCHECK(trackingArea);
NSTrackingAreaOptions options =
NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp;
[self removeTrackingArea:trackingArea->get()];
trackingArea->reset([[CrTrackingArea alloc] initWithRect:rect
options:options
owner:self
userInfo:nil]);
[self addTrackingArea:trackingArea->get()];
}
- (void)mouseEntered:(NSEvent*)theEvent {
[self mouseMoved:theEvent];
}
- (void)mouseExited:(NSEvent*)theEvent {
[self mouseMoved:theEvent];
}
- (void)mouseMoved:(NSEvent*)theEvent {
MouseLocation location = [self mouseLocationForEvent:theEvent];
[rightCell_ setIsMouseInside:NO];
[leftCell_ setIsMouseInside:NO];
if (location == kInsideLeftCell)
[leftCell_ setIsMouseInside:YES];
else if (location == kInsideRightCell)
[rightCell_ setIsMouseInside:YES];
[self setNeedsDisplay:YES];
}
- (void)mouseDown:(NSEvent*)theEvent {
MouseLocation downLocation = [self mouseLocationForEvent:theEvent];
NSCell* focusCell = nil;
NSRect rect;
if (downLocation == kInsideLeftCell) {
focusCell = leftCell_.get();
rect = [self leftCellRect];
} else if (downLocation == kInsideRightCell) {
focusCell = rightCell_.get();
rect = [self rightCellRect];
}
do {
MouseLocation location = [self mouseLocationForEvent:theEvent];
if (location != kNotInside) {
[focusCell setHighlighted:YES];
[self setNeedsDisplay:YES];
if ([focusCell trackMouse:theEvent
inRect:rect
ofView:self
untilMouseUp:NO]) {
[focusCell setState:![focusCell state]];
[self setNeedsDisplay:YES];
break;
} else {
// The above -trackMouse call returned NO, so we know that
// the mouse left the cell before a mouse up event occurred.
[focusCell setHighlighted:NO];
[self setNeedsDisplay:YES];
}
}
const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
theEvent = [[self window] nextEventMatchingMask:mask];
} while ([theEvent type] != NSLeftMouseUp);
}
- (MouseLocation)mouseLocationForEvent:(NSEvent*)theEvent {
MouseLocation location = kNotInside;
NSPoint mousePoint =
[self convertPoint:[theEvent locationInWindow] fromView:nil];
if ([self mouse:mousePoint inRect:[leftCell_ rect]])
location = kInsideLeftCell;
else if ([self mouse:mousePoint inRect:[self rightCellRect]])
location = kInsideRightCell;
return location;
}
- (void)sizeToFit {
NSSize leftSize = [leftCell_ cellSize];
NSSize rightSize = [rightCell_ cellSize];
NSSize size = NSMakeSize(
std::ceil(std::max(leftSize.width + rightSize.width,
constrained_window_button::kButtonMinWidth)),
std::ceil(std::max(leftSize.height, rightSize.height)));
[self setFrameSize:size];
}
- (NSRect)leftCellRect {
return [leftCell_ rect];
}
- (NSRect)rightCellRect {
NSRect leftFrame, rightFrame;
NSDivideRect([self bounds], &leftFrame, &rightFrame,
NSWidth([self leftCellRect]), NSMinXEdge);
return rightFrame;
}
// Accessor for Testing.
- (NSMenu*)menu {
return [rightCell_ menu];
}
@end
@implementation SplitButtonTitleCell
- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView*)controlView {
DrawBezel(self, kRounded, kAngled, frame, controlView);
}
- (NSRect)rect {
NSSize size = [self cellSize];
return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
}
@end
@implementation SplitButtonPopUpCell
@synthesize isMouseInside = isMouseInside_;
- (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
if (self = [super initTextCell:@"" pullsDown:YES]) {
[self setControlSize:NSSmallControlSize];
[self setArrowPosition:NSPopUpArrowAtCenter];
[self setBordered:NO];
[self setBackgroundColor:[NSColor clearColor]];
menuModel_.reset(new ui::SimpleMenuModel(menuDelegate));
menuModel_->AddItemWithStringId(0, IDS_PERMISSION_CUSTOMIZE);
menuController_.reset([[MenuControllerCocoa alloc]
initWithModel:menuModel_.get()
useWithPopUpButtonCell:NO]);
[self setMenu:[menuController_ menu]];
[self setUsesItemFromMenu:NO];
}
return self;
}
- (void)drawBorderAndBackgroundWithFrame:(NSRect)frame
inView:(NSView*)controlView {
// The arrow, which is what should be drawn by the base class, is drawn
// during -drawBezelWithFrame. The only way to draw our own border with
// the default arrow is to make the cell unbordered, and draw the border
// from -drawBorderAndBackgroundWithFrame, rather than simply overriding
// -drawBezelWithFrame.
DrawBezel(self, kAngled, kRounded, frame, controlView);
[super drawBorderAndBackgroundWithFrame:NSOffsetRect(frame, -4, 0)
inView:controlView];
}
- (NSRect)rect {
NSSize size = [self cellSize];
return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
}
@end
......@@ -4168,7 +4168,6 @@ test("unit_tests") {
"../browser/ui/cocoa/omnibox/omnibox_popup_view_mac_unittest.mm",
"../browser/ui/cocoa/omnibox/omnibox_view_mac_unittest.mm",
"../browser/ui/cocoa/page_info/page_info_bubble_controller_unittest.mm",
"../browser/ui/cocoa/page_info/permission_selector_button_unittest.mm",
"../browser/ui/cocoa/profiles/avatar_button_controller_unittest.mm",
"../browser/ui/cocoa/profiles/avatar_button_unittest.mm",
"../browser/ui/cocoa/profiles/avatar_icon_controller_unittest.mm",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment