• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/message_loop/message_pump_mac.h"
6
7#include "base/cancelable_callback.h"
8#include "base/functional/bind.h"
9#include "base/mac/scoped_cftyperef.h"
10#import "base/mac/scoped_nsobject.h"
11#include "base/task/current_thread.h"
12#include "base/task/single_thread_task_runner.h"
13#include "base/test/bind.h"
14#include "base/test/task_environment.h"
15#include "testing/gtest/include/gtest/gtest.h"
16
17@interface TestModalAlertCloser : NSObject
18- (void)runTestThenCloseAlert:(NSAlert*)alert;
19@end
20
21namespace {
22
23// Internal constants from message_pump_mac.mm.
24constexpr int kAllModesMask = 0xf;
25constexpr int kNSApplicationModalSafeModeMask = 0x3;
26
27}  // namespace
28
29namespace base {
30
31namespace {
32
33// PostedTasks are only executed while the message pump has a delegate. That is,
34// when a base::RunLoop is running, so in order to test whether posted tasks
35// are run by CFRunLoopRunInMode and *not* by the regular RunLoop, we need to
36// be inside a task that is also calling CFRunLoopRunInMode.
37// This function posts |task| and runs the given |mode|.
38void RunTaskInMode(CFRunLoopMode mode, OnceClosure task) {
39  // Since this task is "ours" rather than a system task, allow nesting.
40  CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
41  CancelableOnceClosure cancelable(std::move(task));
42  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
43                                                        cancelable.callback());
44  while (CFRunLoopRunInMode(mode, 0, true) == kCFRunLoopRunHandledSource)
45    ;
46}
47
48}  // namespace
49
50// Tests the correct behavior of ScopedPumpMessagesInPrivateModes.
51TEST(MessagePumpMacTest, ScopedPumpMessagesInPrivateModes) {
52  test::SingleThreadTaskEnvironment task_environment(
53      test::SingleThreadTaskEnvironment::MainThreadType::UI);
54
55  CFRunLoopMode kRegular = kCFRunLoopDefaultMode;
56  CFRunLoopMode kPrivate = CFSTR("NSUnhighlightMenuRunLoopMode");
57
58  // Work is seen when running in the default mode.
59  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
60      FROM_HERE,
61      BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
62  EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
63
64  // But not seen when running in a private mode.
65  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
66      FROM_HERE,
67      BindOnce(&RunTaskInMode, kPrivate, MakeExpectedNotRunClosure(FROM_HERE)));
68  EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
69
70  {
71    ScopedPumpMessagesInPrivateModes allow_private;
72    // Now the work should be seen.
73    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
74        FROM_HERE,
75        BindOnce(&RunTaskInMode, kPrivate, MakeExpectedRunClosure(FROM_HERE)));
76    EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
77
78    // The regular mode should also work the same.
79    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
80        FROM_HERE,
81        BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
82    EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
83  }
84
85  // And now the scoper is out of scope, private modes should no longer see it.
86  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
87      FROM_HERE,
88      BindOnce(&RunTaskInMode, kPrivate, MakeExpectedNotRunClosure(FROM_HERE)));
89  EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
90
91  // Only regular modes see it.
92  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
93      FROM_HERE,
94      BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
95  EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
96}
97
98// Tests that private message loop modes are not pumped while a modal dialog is
99// present.
100TEST(MessagePumpMacTest, ScopedPumpMessagesAttemptWithModalDialog) {
101  test::SingleThreadTaskEnvironment task_environment(
102      test::SingleThreadTaskEnvironment::MainThreadType::UI);
103
104  {
105    base::ScopedPumpMessagesInPrivateModes allow_private;
106    // No modal window, so all modes should be pumped.
107    EXPECT_EQ(kAllModesMask, allow_private.GetModeMaskForTest());
108  }
109
110  base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]);
111  [alert addButtonWithTitle:@"OK"];
112  base::scoped_nsobject<TestModalAlertCloser> closer(
113      [[TestModalAlertCloser alloc] init]);
114  [closer performSelector:@selector(runTestThenCloseAlert:)
115               withObject:alert
116               afterDelay:0
117                  inModes:@[ NSModalPanelRunLoopMode ]];
118  NSInteger result = [alert runModal];
119  EXPECT_EQ(NSAlertFirstButtonReturn, result);
120}
121
122TEST(MessagePumpMacTest, QuitWithModalWindow) {
123  test::SingleThreadTaskEnvironment task_environment(
124      test::SingleThreadTaskEnvironment::MainThreadType::UI);
125  NSWindow* window =
126      [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
127                                   styleMask:NSWindowStyleMaskBorderless
128                                     backing:NSBackingStoreBuffered
129                                       defer:NO] autorelease];
130
131  // Check that quitting the run loop while a modal window is shown applies to
132  // |run_loop| rather than the internal NSApplication modal run loop.
133  RunLoop run_loop;
134  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
135      FROM_HERE, base::BindLambdaForTesting([&] {
136        CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
137        ScopedPumpMessagesInPrivateModes pump_private;
138        [NSApp runModalForWindow:window];
139      }));
140  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
141      FROM_HERE, base::BindLambdaForTesting([&] {
142        [NSApp stopModal];
143        run_loop.Quit();
144      }));
145
146  EXPECT_NO_FATAL_FAILURE(run_loop.Run());
147}
148
149}  // namespace base
150
151@implementation TestModalAlertCloser
152
153- (void)runTestThenCloseAlert:(NSAlert*)alert {
154  EXPECT_TRUE([NSApp modalWindow]);
155  {
156    base::ScopedPumpMessagesInPrivateModes allow_private;
157    // With a modal window, only safe modes should be pumped.
158    EXPECT_EQ(kNSApplicationModalSafeModeMask,
159              allow_private.GetModeMaskForTest());
160  }
161  [[alert buttons][0] performClick:nil];
162}
163
164@end
165