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