1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_controller.h"
7 #include <Carbon/Carbon.h>
9 #include "base/mac/foundation_util.h"
10 #import "base/mac/scoped_objc_class_swizzler.h"
11 #include "base/mac/sdk_forward_declarations.h"
12 #include "base/stl_util.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #include "chrome/browser/ui/cocoa/browser_window_controller.h"
18 #import "chrome/browser/ui/cocoa/cocoa_profile_test.h"
19 #include "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
20 #include "chrome/browser/ui/cocoa/run_loop_testing.h"
21 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
22 #import "chrome/browser/ui/cocoa/website_settings/split_block_button.h"
23 #include "chrome/browser/ui/website_settings/mock_permission_bubble_request.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #import "testing/gtest_mac.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/l10n/l10n_util_mac.h"
29 #import "ui/events/test/cocoa_test_event_utils.h"
31 @class ConstrainedWindowButton;
33 @interface PermissionBubbleController (ExposedForTesting)
34 - (void)ok:(id)sender;
35 - (void)onAllow:(id)sender;
36 - (void)onBlock:(id)sender;
37 - (void)onCustomize:(id)sender;
38 - (void)onCheckboxChanged:(id)sender;
41 @interface SplitBlockButton (ExposedForTesting)
45 @interface MockBubbleYesLocationBar : NSObject
48 @implementation MockBubbleYesLocationBar
49 - (bool)hasLocationBar { return true; }
52 @interface MockBubbleNoLocationBar : NSObject
55 @implementation MockBubbleNoLocationBar
56 - (bool)hasLocationBar { return false; }
60 const char* const kPermissionA = "Permission A";
61 const char* const kPermissionB = "Permission B";
62 const char* const kPermissionC = "Permission C";
65 class PermissionBubbleControllerTest : public CocoaProfileTest,
66 public PermissionBubbleView::Delegate {
69 MOCK_METHOD2(ToggleAccept, void(int, bool));
70 MOCK_METHOD0(SetCustomizationMode, void());
71 MOCK_METHOD0(Accept, void());
72 MOCK_METHOD0(Deny, void());
73 MOCK_METHOD0(Closing, void());
74 MOCK_METHOD1(SetView, void(PermissionBubbleView*));
76 void SetUp() override {
77 CocoaProfileTest::SetUp();
78 bridge_.reset(new PermissionBubbleCocoa(browser()));
79 AddRequest(kPermissionA);
81 [[PermissionBubbleController alloc] initWithBrowser:browser()
82 bridge:bridge_.get()];
85 void TearDown() override {
87 chrome::testing::NSRunLoopRunAllPending();
88 STLDeleteElements(&requests_);
89 CocoaProfileTest::TearDown();
92 void AddRequest(const std::string& title) {
93 MockPermissionBubbleRequest* request = new MockPermissionBubbleRequest(
95 l10n_util::GetStringUTF8(IDS_PERMISSION_ALLOW),
96 l10n_util::GetStringUTF8(IDS_PERMISSION_DENY));
97 requests_.push_back(request);
100 NSButton* FindButtonWithTitle(const std::string& title) {
101 return FindButtonWithTitle(base::SysUTF8ToNSString(title),
102 [ConstrainedWindowButton class]);
105 NSButton* FindButtonWithTitle(int title_id) {
106 return FindButtonWithTitle(l10n_util::GetNSString(title_id),
107 [ConstrainedWindowButton class]);
110 NSButton* FindMenuButtonWithTitle(int title_id) {
111 return FindButtonWithTitle(l10n_util::GetNSString(title_id),
112 [NSPopUpButton class]);
115 // IDS_PERMISSION_ALLOW and IDS_PERMISSION_DENY are used for two distinct
116 // UI elements, both of which derive from NSButton. So check the expected
117 // class, not just NSButton, as well as the title.
118 NSButton* FindButtonWithTitle(NSString* title, Class button_class) {
119 for (NSButton* view in [[controller_ bubble] subviews]) {
120 if ([view isKindOfClass:button_class] &&
121 [title isEqualToString:[view title]]) {
128 NSTextField* FindTextFieldWithString(const std::string& text) {
129 NSView* parent = base::mac::ObjCCastStrict<NSView>([controller_ bubble]);
130 return FindTextFieldWithString(parent, base::SysUTF8ToNSString(text));
133 NSTextField* FindTextFieldWithString(NSView* view, NSString* text) {
134 NSTextField* textField = nil;
135 for (NSView* child in [view subviews]) {
136 textField = base::mac::ObjCCast<NSTextField>(child);
137 if (![[textField stringValue] hasSuffix:text]) {
138 textField = FindTextFieldWithString(child, text);
146 void ChangePermissionMenuSelection(NSButton* menu_button, int next_title_id) {
147 NSMenu* menu = [base::mac::ObjCCastStrict<NSPopUpButton>(menu_button) menu];
148 NSString* next_title = l10n_util::GetNSString(next_title_id);
149 EXPECT_EQ([[menu itemWithTitle:[menu_button title]] state], NSOnState);
150 NSMenuItem* next_item = [menu itemWithTitle:next_title];
151 EXPECT_EQ([next_item state], NSOffState);
152 [menu performActionForItemAtIndex:[menu indexOfItem:next_item]];
156 PermissionBubbleController* controller_; // Weak; it deletes itself.
157 scoped_ptr<PermissionBubbleCocoa> bridge_;
158 std::vector<PermissionBubbleRequest*> requests_;
159 std::vector<bool> accept_states_;
162 TEST_F(PermissionBubbleControllerTest, ShowAndClose) {
163 EXPECT_FALSE([[controller_ window] isVisible]);
164 [controller_ showWindow:nil];
165 EXPECT_TRUE([[controller_ window] isVisible]);
168 TEST_F(PermissionBubbleControllerTest, ShowSinglePermission) {
169 [controller_ showWithDelegate:this
170 forRequests:requests_
171 acceptStates:accept_states_];
173 EXPECT_TRUE(FindTextFieldWithString(kPermissionA));
174 EXPECT_TRUE(FindButtonWithTitle(IDS_PERMISSION_ALLOW));
175 EXPECT_TRUE(FindButtonWithTitle(IDS_PERMISSION_DENY));
176 EXPECT_FALSE(FindButtonWithTitle(IDS_OK));
179 TEST_F(PermissionBubbleControllerTest, ShowMultiplePermissions) {
180 AddRequest(kPermissionB);
181 AddRequest(kPermissionC);
183 accept_states_.push_back(true); // A
184 accept_states_.push_back(true); // B
185 accept_states_.push_back(true); // C
187 [controller_ showWithDelegate:this
188 forRequests:requests_
189 acceptStates:accept_states_];
191 EXPECT_TRUE(FindTextFieldWithString(kPermissionA));
192 EXPECT_TRUE(FindTextFieldWithString(kPermissionB));
193 EXPECT_TRUE(FindTextFieldWithString(kPermissionC));
195 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_ALLOW));
196 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_DENY));
197 EXPECT_TRUE(FindButtonWithTitle(IDS_OK));
200 TEST_F(PermissionBubbleControllerTest, ShowMultiplePermissionsAllow) {
201 AddRequest(kPermissionB);
203 accept_states_.push_back(true); // A
204 accept_states_.push_back(true); // B
206 [controller_ showWithDelegate:this
207 forRequests:requests_
208 acceptStates:accept_states_];
210 // Test that all menus have 'Allow' visible.
211 EXPECT_TRUE(FindMenuButtonWithTitle(IDS_PERMISSION_ALLOW));
212 EXPECT_FALSE(FindMenuButtonWithTitle(IDS_PERMISSION_DENY));
214 EXPECT_TRUE(FindButtonWithTitle(IDS_OK));
215 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_ALLOW));
216 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_DENY));
219 TEST_F(PermissionBubbleControllerTest, ShowMultiplePermissionsBlock) {
220 AddRequest(kPermissionB);
222 accept_states_.push_back(false); // A
223 accept_states_.push_back(false); // B
225 [controller_ showWithDelegate:this
226 forRequests:requests_
227 acceptStates:accept_states_];
229 // Test that all menus have 'Block' visible.
230 EXPECT_TRUE(FindMenuButtonWithTitle(IDS_PERMISSION_DENY));
231 EXPECT_FALSE(FindMenuButtonWithTitle(IDS_PERMISSION_ALLOW));
233 EXPECT_TRUE(FindButtonWithTitle(IDS_OK));
234 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_ALLOW));
235 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_DENY));
238 TEST_F(PermissionBubbleControllerTest, ShowMultiplePermissionsMixed) {
239 AddRequest(kPermissionB);
240 AddRequest(kPermissionC);
242 accept_states_.push_back(false); // A
243 accept_states_.push_back(false); // B
244 accept_states_.push_back(true); // C
246 [controller_ showWithDelegate:this
247 forRequests:requests_
248 acceptStates:accept_states_];
250 // Test that both 'allow' and 'deny' are visible.
251 EXPECT_TRUE(FindMenuButtonWithTitle(IDS_PERMISSION_DENY));
252 EXPECT_TRUE(FindMenuButtonWithTitle(IDS_PERMISSION_ALLOW));
254 EXPECT_TRUE(FindButtonWithTitle(IDS_OK));
255 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_ALLOW));
256 EXPECT_FALSE(FindButtonWithTitle(IDS_PERMISSION_DENY));
259 TEST_F(PermissionBubbleControllerTest, OK) {
260 AddRequest(kPermissionB);
262 accept_states_.push_back(true); // A
263 accept_states_.push_back(true); // B
265 [controller_ showWithDelegate:this
266 forRequests:requests_
267 acceptStates:accept_states_];
269 EXPECT_CALL(*this, Accept()).Times(1);
270 [FindButtonWithTitle(IDS_OK) performClick:nil];
273 TEST_F(PermissionBubbleControllerTest, Allow) {
274 [controller_ showWithDelegate:this
275 forRequests:requests_
276 acceptStates:accept_states_];
278 EXPECT_CALL(*this, Accept()).Times(1);
279 [FindButtonWithTitle(IDS_PERMISSION_ALLOW) performClick:nil];
282 TEST_F(PermissionBubbleControllerTest, Deny) {
283 [controller_ showWithDelegate:this
284 forRequests:requests_
285 acceptStates:accept_states_];
287 EXPECT_CALL(*this, Deny()).Times(1);
288 [FindButtonWithTitle(IDS_PERMISSION_DENY) performClick:nil];
291 TEST_F(PermissionBubbleControllerTest, ChangePermissionSelection) {
292 AddRequest(kPermissionB);
294 accept_states_.push_back(true); // A
295 accept_states_.push_back(false); // B
297 [controller_ showWithDelegate:this
298 forRequests:requests_
299 acceptStates:accept_states_];
301 EXPECT_CALL(*this, ToggleAccept(0, false)).Times(1);
302 EXPECT_CALL(*this, ToggleAccept(1, true)).Times(1);
303 NSButton* menu_a = FindMenuButtonWithTitle(IDS_PERMISSION_ALLOW);
304 NSButton* menu_b = FindMenuButtonWithTitle(IDS_PERMISSION_DENY);
305 ChangePermissionMenuSelection(menu_a, IDS_PERMISSION_DENY);
306 ChangePermissionMenuSelection(menu_b, IDS_PERMISSION_ALLOW);
309 TEST_F(PermissionBubbleControllerTest, EscapeCloses) {
310 [controller_ showWithDelegate:this
311 forRequests:requests_
312 acceptStates:accept_states_];
314 EXPECT_TRUE([[controller_ window] isVisible]);
315 [[controller_ window]
316 performKeyEquivalent:cocoa_test_event_utils::KeyEventWithKeyCode(
317 kVK_Escape, '\e', NSKeyDown, 0)];
318 EXPECT_FALSE([[controller_ window] isVisible]);
321 TEST_F(PermissionBubbleControllerTest, EnterFullscreen) {
322 [controller_ showWithDelegate:this
323 forRequests:requests_
324 acceptStates:accept_states_];
326 EXPECT_TRUE([[controller_ window] isVisible]);
328 // Post the "enter fullscreen" notification.
329 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
330 [center postNotificationName:NSWindowWillEnterFullScreenNotification
331 object:test_window()];
333 EXPECT_TRUE([[controller_ window] isVisible]);
336 TEST_F(PermissionBubbleControllerTest, ExitFullscreen) {
337 [controller_ showWithDelegate:this
338 forRequests:requests_
339 acceptStates:accept_states_];
341 EXPECT_TRUE([[controller_ window] isVisible]);
343 // Post the "enter fullscreen" notification.
344 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
345 [center postNotificationName:NSWindowWillExitFullScreenNotification
346 object:test_window()];
348 EXPECT_TRUE([[controller_ window] isVisible]);
351 TEST_F(PermissionBubbleControllerTest, AnchorPositionWithLocationBar) {
352 base::mac::ScopedObjCClassSwizzler locationSwizzle(
353 [PermissionBubbleController class],
354 [MockBubbleYesLocationBar class],
355 @selector(hasLocationBar));
357 NSPoint anchor = [controller_ getExpectedAnchorPoint];
359 // Expected anchor location will be the same as the page info bubble.
360 NSWindow* window = browser()->window()->GetNativeWindow();
361 BrowserWindowController* controller =
362 [BrowserWindowController browserWindowControllerForWindow:window];
363 LocationBarViewMac* location_bar_bridge = [controller locationBarBridge];
364 NSPoint expected = location_bar_bridge->GetPageInfoBubblePoint();
365 expected = [window convertBaseToScreen:expected];
366 EXPECT_NSEQ(expected, anchor);
369 TEST_F(PermissionBubbleControllerTest, AnchorPositionWithoutLocationBar) {
370 base::mac::ScopedObjCClassSwizzler locationSwizzle(
371 [PermissionBubbleController class],
372 [MockBubbleNoLocationBar class],
373 @selector(hasLocationBar));
375 NSPoint anchor = [controller_ getExpectedAnchorPoint];
377 // Expected anchor location will be top center when there's no location bar.
378 NSWindow* window = browser()->window()->GetNativeWindow();
379 NSRect frame = [window frame];
380 NSPoint expected = NSMakePoint(frame.size.width / 2, frame.size.height);
381 expected = [window convertBaseToScreen:expected];
382 EXPECT_NSEQ(expected, anchor);
385 TEST_F(PermissionBubbleControllerTest,
386 AnchorPositionDifferentWithAndWithoutLocationBar) {
387 NSPoint withLocationBar;
389 base::mac::ScopedObjCClassSwizzler locationSwizzle(
390 [PermissionBubbleController class],
391 [MockBubbleYesLocationBar class],
392 @selector(hasLocationBar));
393 withLocationBar = [controller_ getExpectedAnchorPoint];
396 NSPoint withoutLocationBar;
398 base::mac::ScopedObjCClassSwizzler locationSwizzle(
399 [PermissionBubbleController class],
400 [MockBubbleNoLocationBar class],
401 @selector(hasLocationBar));
402 withoutLocationBar = [controller_ getExpectedAnchorPoint];
405 // The bubble should be in different places depending if the location bar is
407 EXPECT_NSNE(withLocationBar, withoutLocationBar);