1 // Copyright 2013 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 #include "extensions/browser/app_window/native_app_window.h"
7 #import <Cocoa/Cocoa.h>
9 #import "base/mac/foundation_util.h"
10 #import "base/mac/mac_util.h"
11 #import "base/mac/scoped_cftyperef.h"
12 #import "base/mac/scoped_nsobject.h"
13 #import "base/mac/sdk_forward_declarations.h"
14 #include "chrome/browser/apps/app_browsertest_util.h"
15 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
16 #include "chrome/browser/apps/app_shim/test/app_shim_host_manager_test_api_mac.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/extensions/app_launch_params.h"
20 #include "chrome/browser/ui/extensions/application_launch.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/test/test_utils.h"
24 #include "extensions/browser/app_window/app_window_registry.h"
25 #include "extensions/common/constants.h"
26 #include "skia/ext/skia_utils_mac.h"
27 #include "testing/gmock/include/gmock/gmock.h"
28 #import "testing/gtest_mac.h"
29 #import "ui/base/test/nswindow_fullscreen_notification_waiter.h"
30 #import "ui/base/test/scoped_fake_nswindow_focus.h"
31 #import "ui/base/test/scoped_fake_nswindow_fullscreen.h"
32 #import "ui/base/test/windowed_nsnotification_observer.h"
33 #import "ui/gfx/mac/nswindow_frame_controls.h"
35 using extensions::AppWindow;
36 using extensions::PlatformAppBrowserTest;
39 using ::testing::Invoke;
40 using ::testing::Return;
44 // The param selects whether to use ChromeNativeAppWindowViewsMac, otherwise it
45 // will use NativeAppWindowCocoa.
46 class NativeAppWindowCocoaBrowserTest
47 : public testing::WithParamInterface<bool>,
48 public PlatformAppBrowserTest {
50 NativeAppWindowCocoaBrowserTest() {}
52 void SetUpCommandLine(base::CommandLine* command_line) override {
53 PlatformAppBrowserTest::SetUpCommandLine(command_line);
54 command_line->AppendSwitch(
55 GetParam() ? switches::kEnableMacViewsNativeAppWindows
56 : switches::kDisableMacViewsNativeAppWindows);
59 void SetUpAppWithWindows(int num_windows) {
60 app_ = InstallExtension(
61 test_data_dir_.AppendASCII("platform_apps").AppendASCII("minimal"), 1);
64 for (int i = 0; i < num_windows; ++i) {
65 content::WindowedNotificationObserver app_loaded_observer(
66 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
67 content::NotificationService::AllSources());
69 AppLaunchParams(profile(), app_, extensions::LAUNCH_CONTAINER_NONE,
70 NEW_WINDOW, extensions::SOURCE_TEST));
71 app_loaded_observer.Wait();
75 const extensions::Extension* app_;
78 DISALLOW_COPY_AND_ASSIGN(NativeAppWindowCocoaBrowserTest);
83 // Test interaction of Hide/Show() with Hide/ShowWithApp().
84 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, HideShowWithApp) {
85 SetUpAppWithWindows(2);
86 extensions::AppWindowRegistry::AppWindowList windows =
87 extensions::AppWindowRegistry::Get(profile())->app_windows();
89 AppWindow* app_window = windows.front();
90 extensions::NativeAppWindow* native_window = app_window->GetBaseWindow();
91 NSWindow* ns_window = native_window->GetNativeWindow();
93 AppWindow* other_app_window = windows.back();
94 extensions::NativeAppWindow* other_native_window =
95 other_app_window->GetBaseWindow();
96 NSWindow* other_ns_window = other_native_window->GetNativeWindow();
100 EXPECT_FALSE([ns_window isVisible]);
101 app_window->Show(AppWindow::SHOW_ACTIVE);
102 EXPECT_TRUE([ns_window isVisible]);
104 // Normal Hide/ShowWithApp.
105 native_window->HideWithApp();
106 EXPECT_FALSE([ns_window isVisible]);
107 native_window->ShowWithApp();
108 EXPECT_TRUE([ns_window isVisible]);
110 // HideWithApp, Hide, ShowWithApp does not show.
111 native_window->HideWithApp();
113 native_window->ShowWithApp();
114 EXPECT_FALSE([ns_window isVisible]);
116 // Hide, HideWithApp, ShowWithApp does not show.
117 native_window->HideWithApp();
118 native_window->ShowWithApp();
119 EXPECT_FALSE([ns_window isVisible]);
121 // Return to shown state.
122 app_window->Show(AppWindow::SHOW_ACTIVE);
123 EXPECT_TRUE([ns_window isVisible]);
125 // HideWithApp the other window.
126 EXPECT_TRUE([other_ns_window isVisible]);
127 other_native_window->HideWithApp();
128 EXPECT_FALSE([other_ns_window isVisible]);
130 // HideWithApp, Show shows just one window since there's no shim.
131 native_window->HideWithApp();
132 EXPECT_FALSE([ns_window isVisible]);
133 app_window->Show(AppWindow::SHOW_ACTIVE);
134 EXPECT_TRUE([ns_window isVisible]);
135 EXPECT_FALSE([other_ns_window isVisible]);
137 // Hide the other window.
138 other_app_window->Hide();
139 EXPECT_FALSE([other_ns_window isVisible]);
141 // HideWithApp, ShowWithApp does not show the other window.
142 native_window->HideWithApp();
143 EXPECT_FALSE([ns_window isVisible]);
144 native_window->ShowWithApp();
145 EXPECT_TRUE([ns_window isVisible]);
146 EXPECT_FALSE([other_ns_window isVisible]);
151 class MockAppShimHost : public apps::AppShimHandler::Host {
154 ~MockAppShimHost() override {}
156 MOCK_METHOD1(OnAppLaunchComplete, void(apps::AppShimLaunchResult));
157 MOCK_METHOD0(OnAppClosed, void());
158 MOCK_METHOD0(OnAppHide, void());
159 MOCK_METHOD0(OnAppUnhideWithoutActivation, void());
160 MOCK_METHOD1(OnAppRequestUserAttention, void(apps::AppShimAttentionType));
161 MOCK_CONST_METHOD0(GetProfilePath, base::FilePath());
162 MOCK_CONST_METHOD0(GetAppId, std::string());
165 class MockExtensionAppShimHandler : public apps::ExtensionAppShimHandler {
167 MockExtensionAppShimHandler() {
168 ON_CALL(*this, FindHost(_, _))
169 .WillByDefault(Invoke(this, &apps::ExtensionAppShimHandler::FindHost));
171 ~MockExtensionAppShimHandler() override {}
173 MOCK_METHOD2(FindHost, AppShimHandler::Host*(Profile*, const std::string&));
178 // Test Hide/Show and Hide/ShowWithApp() behavior when shims are enabled.
179 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest,
180 HideShowWithAppWithShim) {
181 test::AppShimHostManagerTestApi test_api(
182 g_browser_process->platform_part()->app_shim_host_manager());
183 MockExtensionAppShimHandler* mock = new MockExtensionAppShimHandler();
184 test_api.SetExtensionAppShimHandler(
185 scoped_ptr<apps::ExtensionAppShimHandler>(mock)); // Takes ownership.
186 MockAppShimHost mock_host;
188 SetUpAppWithWindows(1);
189 extensions::AppWindowRegistry::AppWindowList windows =
190 extensions::AppWindowRegistry::Get(profile())->app_windows();
192 extensions::AppWindow* app_window = windows.front();
193 extensions::NativeAppWindow* native_window = app_window->GetBaseWindow();
194 NSWindow* ns_window = native_window->GetNativeWindow();
197 native_window->HideWithApp();
198 EXPECT_FALSE([ns_window isVisible]);
200 // Show notifies the shim to unhide.
201 EXPECT_CALL(mock_host, OnAppUnhideWithoutActivation());
202 EXPECT_CALL(*mock, FindHost(_, _)).WillOnce(Return(&mock_host));
203 app_window->Show(extensions::AppWindow::SHOW_ACTIVE);
204 EXPECT_TRUE([ns_window isVisible]);
205 testing::Mock::VerifyAndClearExpectations(mock);
206 testing::Mock::VerifyAndClearExpectations(&mock_host);
209 native_window->HideWithApp();
210 EXPECT_FALSE([ns_window isVisible]);
212 // Activate does the same.
213 EXPECT_CALL(mock_host, OnAppUnhideWithoutActivation());
214 EXPECT_CALL(*mock, FindHost(_, _)).WillOnce(Return(&mock_host));
215 native_window->Activate();
216 EXPECT_TRUE([ns_window isVisible]);
217 testing::Mock::VerifyAndClearExpectations(mock);
218 testing::Mock::VerifyAndClearExpectations(&mock_host);
221 // Test that NativeAppWindow and AppWindow fullscreen state is updated when
222 // the window is fullscreened natively.
223 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, Fullscreen) {
224 if (!base::mac::IsOSLionOrLater())
227 ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
229 extensions::AppWindow* app_window =
230 CreateTestAppWindow("{\"alwaysOnTop\": true }");
231 extensions::NativeAppWindow* window = app_window->GetBaseWindow();
232 NSWindow* ns_window = app_window->GetNativeWindow();
233 base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
234 [[NSWindowFullscreenNotificationWaiter alloc] initWithWindow:ns_window]);
236 EXPECT_EQ(AppWindow::FULLSCREEN_TYPE_NONE,
237 app_window->fullscreen_types_for_test());
238 EXPECT_FALSE(window->IsFullscreen());
239 EXPECT_FALSE([ns_window styleMask] & NSFullScreenWindowMask);
240 EXPECT_TRUE(gfx::IsNSWindowAlwaysOnTop(ns_window));
242 [ns_window toggleFullScreen:nil];
243 [waiter waitForEnterCount:1 exitCount:0];
244 EXPECT_TRUE(app_window->fullscreen_types_for_test() &
245 AppWindow::FULLSCREEN_TYPE_OS);
246 EXPECT_TRUE(window->IsFullscreen());
247 EXPECT_TRUE([ns_window styleMask] & NSFullScreenWindowMask);
248 EXPECT_FALSE(gfx::IsNSWindowAlwaysOnTop(ns_window));
250 app_window->Restore();
251 EXPECT_FALSE(window->IsFullscreenOrPending());
252 [waiter waitForEnterCount:1 exitCount:1];
253 EXPECT_EQ(AppWindow::FULLSCREEN_TYPE_NONE,
254 app_window->fullscreen_types_for_test());
255 EXPECT_FALSE(window->IsFullscreen());
256 EXPECT_FALSE([ns_window styleMask] & NSFullScreenWindowMask);
257 EXPECT_TRUE(gfx::IsNSWindowAlwaysOnTop(ns_window));
259 app_window->Fullscreen();
260 EXPECT_TRUE(window->IsFullscreenOrPending());
261 [waiter waitForEnterCount:2 exitCount:1];
262 EXPECT_TRUE(app_window->fullscreen_types_for_test() &
263 AppWindow::FULLSCREEN_TYPE_WINDOW_API);
264 EXPECT_TRUE(window->IsFullscreen());
265 EXPECT_TRUE([ns_window styleMask] & NSFullScreenWindowMask);
266 EXPECT_FALSE(gfx::IsNSWindowAlwaysOnTop(ns_window));
268 [ns_window toggleFullScreen:nil];
269 [waiter waitForEnterCount:2 exitCount:2];
270 EXPECT_EQ(AppWindow::FULLSCREEN_TYPE_NONE,
271 app_window->fullscreen_types_for_test());
272 EXPECT_FALSE(window->IsFullscreen());
273 EXPECT_FALSE([ns_window styleMask] & NSFullScreenWindowMask);
274 EXPECT_TRUE(gfx::IsNSWindowAlwaysOnTop(ns_window));
277 // Test Minimize, Restore combinations with their native equivalents.
278 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, Minimize) {
279 SetUpAppWithWindows(1);
280 AppWindow* app_window = GetFirstAppWindow();
281 extensions::NativeAppWindow* window = app_window->GetBaseWindow();
282 NSWindow* ns_window = app_window->GetNativeWindow();
284 NSRect initial_frame = [ns_window frame];
286 EXPECT_FALSE(window->IsMinimized());
287 EXPECT_FALSE([ns_window isMiniaturized]);
289 // Native minimize, Restore.
290 [ns_window miniaturize:nil];
291 EXPECT_NSEQ(initial_frame, [ns_window frame]);
292 EXPECT_TRUE(window->IsMinimized());
293 EXPECT_TRUE([ns_window isMiniaturized]);
295 app_window->Restore();
296 EXPECT_NSEQ(initial_frame, [ns_window frame]);
297 EXPECT_FALSE(window->IsMinimized());
298 EXPECT_FALSE([ns_window isMiniaturized]);
300 // Minimize, native restore.
301 app_window->Minimize();
302 EXPECT_NSEQ(initial_frame, [ns_window frame]);
303 EXPECT_TRUE(window->IsMinimized());
304 EXPECT_TRUE([ns_window isMiniaturized]);
306 [ns_window deminiaturize:nil];
307 EXPECT_NSEQ(initial_frame, [ns_window frame]);
308 EXPECT_FALSE(window->IsMinimized());
309 EXPECT_FALSE([ns_window isMiniaturized]);
312 // Test Maximize, Restore combinations with their native equivalents.
313 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, Maximize) {
314 // This test is flaky on 10.6. Disable it until we're sure we need MacViews on
315 // 10.6. See http://crbug.com/503208
316 if (GetParam() && base::mac::IsOSSnowLeopard())
319 SetUpAppWithWindows(1);
320 AppWindow* app_window = GetFirstAppWindow();
321 extensions::NativeAppWindow* window = app_window->GetBaseWindow();
322 NSWindow* ns_window = app_window->GetNativeWindow();
323 base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
325 gfx::Rect initial_restored_bounds = window->GetRestoredBounds();
326 NSRect initial_frame = [ns_window frame];
327 NSRect maximized_frame = [[ns_window screen] visibleFrame];
329 EXPECT_FALSE(window->IsMaximized());
331 // Native maximize, Restore.
332 watcher.reset([[WindowedNSNotificationObserver alloc]
333 initForNotification:NSWindowDidResizeNotification
335 [ns_window zoom:nil];
337 EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
338 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
339 EXPECT_TRUE(window->IsMaximized());
341 watcher.reset([[WindowedNSNotificationObserver alloc]
342 initForNotification:NSWindowDidResizeNotification
344 app_window->Restore();
346 EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
347 EXPECT_NSEQ(initial_frame, [ns_window frame]);
348 EXPECT_FALSE(window->IsMaximized());
350 // Maximize, native restore.
351 watcher.reset([[WindowedNSNotificationObserver alloc]
352 initForNotification:NSWindowDidResizeNotification
354 app_window->Maximize();
356 EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
357 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
358 EXPECT_TRUE(window->IsMaximized());
360 watcher.reset([[WindowedNSNotificationObserver alloc]
361 initForNotification:NSWindowDidResizeNotification
363 [ns_window zoom:nil];
365 EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
366 EXPECT_NSEQ(initial_frame, [ns_window frame]);
367 EXPECT_FALSE(window->IsMaximized());
370 // Test Maximize when the window has a maximum size. The maximum size means that
371 // the window is not user-maximizable. However, calling Maximize() via the
372 // javascript API should still maximize and since the zoom button is removed,
373 // the codepath changes.
374 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, MaximizeConstrained) {
375 AppWindow* app_window = CreateTestAppWindow(
376 "{\"outerBounds\": {\"maxWidth\":200, \"maxHeight\":300}}");
377 extensions::NativeAppWindow* window = app_window->GetBaseWindow();
378 NSWindow* ns_window = app_window->GetNativeWindow();
379 base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
381 gfx::Rect initial_restored_bounds = window->GetRestoredBounds();
382 NSRect initial_frame = [ns_window frame];
383 NSRect maximized_frame = [[ns_window screen] visibleFrame];
385 EXPECT_FALSE(window->IsMaximized());
387 // Maximize, Restore.
388 watcher.reset([[WindowedNSNotificationObserver alloc]
389 initForNotification:NSWindowDidResizeNotification
391 app_window->Maximize();
393 EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
394 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
395 EXPECT_TRUE(window->IsMaximized());
397 watcher.reset([[WindowedNSNotificationObserver alloc]
398 initForNotification:NSWindowDidResizeNotification
400 app_window->Restore();
402 EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
403 EXPECT_NSEQ(initial_frame, [ns_window frame]);
404 EXPECT_FALSE(window->IsMaximized());
407 // Test Minimize, Maximize, Restore combinations with their native equivalents.
408 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, MinimizeMaximize) {
409 SetUpAppWithWindows(1);
410 AppWindow* app_window = GetFirstAppWindow();
411 extensions::NativeAppWindow* window = app_window->GetBaseWindow();
412 NSWindow* ns_window = app_window->GetNativeWindow();
413 base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
415 NSRect initial_frame = [ns_window frame];
416 NSRect maximized_frame = [[ns_window screen] visibleFrame];
418 EXPECT_FALSE(window->IsMaximized());
419 EXPECT_FALSE(window->IsMinimized());
420 EXPECT_FALSE([ns_window isMiniaturized]);
422 // Maximize, Minimize, Restore.
423 watcher.reset([[WindowedNSNotificationObserver alloc]
424 initForNotification:NSWindowDidResizeNotification
426 app_window->Maximize();
428 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
429 EXPECT_TRUE(window->IsMaximized());
431 app_window->Minimize();
432 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
433 EXPECT_FALSE(window->IsMaximized());
434 EXPECT_TRUE(window->IsMinimized());
435 EXPECT_TRUE([ns_window isMiniaturized]);
437 app_window->Restore();
438 EXPECT_NSEQ(initial_frame, [ns_window frame]);
439 EXPECT_FALSE(window->IsMaximized());
440 EXPECT_FALSE(window->IsMinimized());
441 EXPECT_FALSE([ns_window isMiniaturized]);
443 // Minimize, Maximize.
444 app_window->Minimize();
445 EXPECT_NSEQ(initial_frame, [ns_window frame]);
446 EXPECT_TRUE(window->IsMinimized());
447 EXPECT_TRUE([ns_window isMiniaturized]);
449 app_window->Maximize();
450 EXPECT_TRUE([ns_window isVisible]);
451 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
452 EXPECT_TRUE(window->IsMaximized());
453 EXPECT_FALSE(window->IsMinimized());
454 EXPECT_FALSE([ns_window isMiniaturized]);
457 // Test Maximize, Fullscreen, Restore combinations.
458 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, MaximizeFullscreen) {
459 if (base::mac::IsOSSnowLeopard())
462 ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
464 SetUpAppWithWindows(1);
465 AppWindow* app_window = GetFirstAppWindow();
466 extensions::NativeAppWindow* window = app_window->GetBaseWindow();
467 NSWindow* ns_window = app_window->GetNativeWindow();
468 base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
469 base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
470 [[NSWindowFullscreenNotificationWaiter alloc] initWithWindow:ns_window]);
472 NSRect initial_frame = [ns_window frame];
473 NSRect maximized_frame = [[ns_window screen] visibleFrame];
475 EXPECT_FALSE(window->IsMaximized());
476 EXPECT_FALSE(window->IsFullscreen());
478 // Maximize, Fullscreen, Restore, Restore.
479 watcher.reset([[WindowedNSNotificationObserver alloc]
480 initForNotification:NSWindowDidResizeNotification
482 app_window->Maximize();
484 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
485 EXPECT_TRUE(window->IsMaximized());
487 EXPECT_EQ(0, [waiter enterCount]);
488 app_window->Fullscreen();
489 [waiter waitForEnterCount:1 exitCount:0];
490 EXPECT_FALSE(window->IsMaximized());
491 EXPECT_TRUE(window->IsFullscreen());
493 app_window->Restore();
494 [waiter waitForEnterCount:1 exitCount:1];
495 EXPECT_NSEQ(maximized_frame, [ns_window frame]);
496 EXPECT_TRUE(window->IsMaximized());
497 EXPECT_FALSE(window->IsFullscreen());
499 app_window->Restore();
500 EXPECT_NSEQ(initial_frame, [ns_window frame]);
501 EXPECT_FALSE(window->IsMaximized());
503 // Fullscreen, Maximize, Restore.
504 app_window->Fullscreen();
505 [waiter waitForEnterCount:2 exitCount:1];
506 EXPECT_FALSE(window->IsMaximized());
507 EXPECT_TRUE(window->IsFullscreen());
509 app_window->Maximize();
510 EXPECT_FALSE(window->IsMaximized());
511 EXPECT_TRUE(window->IsFullscreen());
513 app_window->Restore();
514 [waiter waitForEnterCount:2 exitCount:2];
515 EXPECT_NSEQ(initial_frame, [ns_window frame]);
516 EXPECT_FALSE(window->IsMaximized());
517 EXPECT_FALSE(window->IsFullscreen());
520 // Test that, in frameless windows, the web contents has the same size as the
522 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, Frameless) {
523 AppWindow* app_window = CreateTestAppWindow("{\"frame\": \"none\"}");
524 NSWindow* ns_window = app_window->GetNativeWindow();
525 NSView* web_contents = app_window->web_contents()->GetNativeView();
526 EXPECT_TRUE(NSEqualSizes(NSMakeSize(512, 384), [web_contents frame].size));
527 // Move and resize the window.
528 NSRect new_frame = NSMakeRect(50, 50, 200, 200);
529 [ns_window setFrame:new_frame display:YES];
530 EXPECT_TRUE(NSEqualSizes(new_frame.size, [web_contents frame].size));
532 // Windows created with NSBorderlessWindowMask by default don't have shadow,
533 // but packaged apps should always have one.
534 EXPECT_TRUE([ns_window hasShadow]);
536 // Since the window has no constraints, it should have all of the following
538 NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
539 NSMiniaturizableWindowMask | NSResizableWindowMask |
540 NSTexturedBackgroundWindowMask;
541 EXPECT_EQ(style_mask, [ns_window styleMask]);
543 CloseAppWindow(app_window);
548 // Test that resize and fullscreen controls are correctly enabled/disabled.
549 void TestControls(AppWindow* app_window) {
550 NSWindow* ns_window = app_window->GetNativeWindow();
552 // The window is resizable.
553 EXPECT_TRUE([ns_window styleMask] & NSResizableWindowMask);
554 if (base::mac::IsOSSnowLeopard())
555 EXPECT_TRUE([ns_window showsResizeIndicator]);
557 // Due to this bug: http://crbug.com/362039, which manifests on the Cocoa
558 // implementation but not the views one, frameless windows should have
559 // fullscreen controls disabled.
560 BOOL can_fullscreen =
561 ![NSStringFromClass([ns_window class]) isEqualTo:@"AppFramelessNSWindow"];
562 // The window can fullscreen and maximize.
563 if (base::mac::IsOSLionOrLater()) {
564 EXPECT_EQ(can_fullscreen, !!([ns_window collectionBehavior] &
565 NSWindowCollectionBehaviorFullScreenPrimary));
568 // In OSX 10.10+, the zoom button performs the zoom action rather than the
569 // fullscreen action. The above check that collectionBehavior does not include
570 // NSWindowCollectionBehaviorFullScreenPrimary is sufficient to determine that
571 // the window can't be fullscreened.
572 if (base::mac::IsOSMavericksOrEarlier()) {
573 EXPECT_EQ(can_fullscreen,
574 [[ns_window standardWindowButton:NSWindowZoomButton] isEnabled]);
577 // Set a maximum size.
578 app_window->SetContentSizeConstraints(gfx::Size(), gfx::Size(200, 201));
579 EXPECT_EQ(200, [ns_window contentMaxSize].width);
580 EXPECT_EQ(201, [ns_window contentMaxSize].height);
581 NSView* web_contents = app_window->web_contents()->GetNativeView();
582 EXPECT_EQ(200, [web_contents frame].size.width);
583 EXPECT_EQ(201, [web_contents frame].size.height);
586 EXPECT_TRUE([ns_window styleMask] & NSResizableWindowMask);
587 if (base::mac::IsOSSnowLeopard())
588 EXPECT_TRUE([ns_window showsResizeIndicator]);
590 // Fullscreen and maximize are disabled.
591 if (base::mac::IsOSLionOrLater())
592 EXPECT_FALSE([ns_window collectionBehavior] &
593 NSWindowCollectionBehaviorFullScreenPrimary);
594 EXPECT_FALSE([[ns_window standardWindowButton:NSWindowZoomButton] isEnabled]);
596 // Set a minimum size equal to the maximum size.
597 app_window->SetContentSizeConstraints(gfx::Size(200, 201),
598 gfx::Size(200, 201));
599 EXPECT_EQ(200, [ns_window contentMinSize].width);
600 EXPECT_EQ(201, [ns_window contentMinSize].height);
602 // No longer resizable.
603 EXPECT_FALSE([ns_window styleMask] & NSResizableWindowMask);
604 if (base::mac::IsOSSnowLeopard())
605 EXPECT_FALSE([ns_window showsResizeIndicator]);
607 // If a window is made fullscreen by the API, fullscreen should be enabled so
608 // the user can exit fullscreen.
609 if (base::mac::IsOSLionOrLater()) {
610 ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
611 base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter([
612 [NSWindowFullscreenNotificationWaiter alloc] initWithWindow:ns_window]);
613 app_window->SetFullscreen(AppWindow::FULLSCREEN_TYPE_WINDOW_API, true);
614 [waiter waitForEnterCount:1 exitCount:0];
615 EXPECT_TRUE([ns_window collectionBehavior] &
616 NSWindowCollectionBehaviorFullScreenPrimary);
617 EXPECT_EQ(NSWidth([[ns_window contentView] frame]),
618 NSWidth([ns_window frame]));
619 // Once it leaves fullscreen, it is disabled again.
620 app_window->SetFullscreen(AppWindow::FULLSCREEN_TYPE_WINDOW_API, false);
621 [waiter waitForEnterCount:1 exitCount:1];
622 EXPECT_FALSE([ns_window collectionBehavior] &
623 NSWindowCollectionBehaviorFullScreenPrimary);
629 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, Controls) {
630 TestControls(CreateTestAppWindow("{}"));
633 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, ControlsFrameless) {
634 TestControls(CreateTestAppWindow("{\"frame\": \"none\"}"));
639 // Convert a color constant to an NSColor that can be compared with |bitmap|.
640 NSColor* ColorInBitmapColorSpace(SkColor color, NSBitmapImageRep* bitmap) {
641 return [gfx::SkColorToSRGBNSColor(color)
642 colorUsingColorSpace:[bitmap colorSpace]];
645 // Take a screenshot of the window, including its native frame.
646 NSBitmapImageRep* ScreenshotNSWindow(NSWindow* window) {
647 // When building with 10.10 SDK and running on 10.9, -[NSView
648 // cacheDisplayInRect] does not seem to capture subviews. This seems related
649 // to the frame view having a layer with 10.10 SDK, but is probably a bug
650 // since it doesn't manifest on 10.10. See http://crbug.com/508722.
651 // In this case, take a screenshot using the CGWindowList API instead. The
652 // bitmap is now in the display's color space, so expected colors need to be
654 // TODO(jackhou): Update this if it is fixed in AppKit, or if other
655 // platform/SDK combinations need it.
656 // NOTE: This doesn't work with Views, but the regular test does, so use that.
657 bool mac_views = base::CommandLine::ForCurrentProcess()->HasSwitch(
658 switches::kEnableMacViewsNativeAppWindows);
659 if (base::mac::IsOSMavericks() && !mac_views) {
660 // -[NSView setNeedsDisplay:YES] doesn't synchronously display the view, it
661 // gets drawn by another event in the queue, so let that run first.
662 content::RunAllPendingInMessageLoop();
663 base::ScopedCFTypeRef<CGImageRef> cg_image(CGWindowListCreateImage(
664 CGRectNull, kCGWindowListOptionIncludingWindow, [window windowNumber],
665 kCGWindowImageBoundsIgnoreFraming));
666 return [[[NSBitmapImageRep alloc] initWithCGImage:cg_image] autorelease];
669 NSView* frame_view = [[window contentView] superview];
670 NSRect bounds = [frame_view bounds];
671 NSBitmapImageRep* bitmap =
672 [frame_view bitmapImageRepForCachingDisplayInRect:bounds];
673 [frame_view cacheDisplayInRect:bounds toBitmapImageRep:bitmap];
679 // Test that the colored frames have the correct color when active and inactive.
680 IN_PROC_BROWSER_TEST_P(NativeAppWindowCocoaBrowserTest, FrameColor) {
681 // The hex values indicate an RGB color. When we get the NSColor later, the
682 // components are CGFloats in the range [0, 1].
683 extensions::AppWindow* app_window = CreateTestAppWindow(
684 "{\"frame\": {\"color\": \"#FF0000\", \"inactiveColor\": \"#0000FF\"}}");
685 NSWindow* ns_window = app_window->GetNativeWindow();
686 // No color correction in the default case.
687 [ns_window setColorSpace:[NSColorSpace sRGBColorSpace]];
689 int half_width = NSWidth([ns_window frame]) / 2;
691 NSBitmapImageRep* bitmap = ScreenshotNSWindow(ns_window);
692 // The window is currently inactive so it should be blue (#0000FF).
693 NSColor* expected_color = ColorInBitmapColorSpace(0xFF0000FF, bitmap);
694 NSColor* color = [bitmap colorAtX:half_width y:5];
695 CGFloat expected_components[4], color_components[4];
696 [expected_color getComponents:expected_components];
697 [color getComponents:color_components];
698 EXPECT_NEAR(expected_components[0], color_components[0], 0.01);
699 EXPECT_NEAR(expected_components[1], color_components[1], 0.01);
700 EXPECT_NEAR(expected_components[2], color_components[2], 0.01);
702 ui::test::ScopedFakeNSWindowFocus fake_focus;
703 [ns_window makeMainWindow];
705 bitmap = ScreenshotNSWindow(ns_window);
706 // The window is now active so it should be red (#FF0000).
707 expected_color = ColorInBitmapColorSpace(0xFFFF0000, bitmap);
708 color = [bitmap colorAtX:half_width y:5];
709 [expected_color getComponents:expected_components];
710 [color getComponents:color_components];
711 EXPECT_NEAR(expected_components[0], color_components[0], 0.01);
712 EXPECT_NEAR(expected_components[1], color_components[1], 0.01);
713 EXPECT_NEAR(expected_components[2], color_components[2], 0.01);
716 INSTANTIATE_TEST_CASE_P(NativeAppWindowCocoaBrowserTestInstance,
717 NativeAppWindowCocoaBrowserTest,