message_pump_mac_unittest.mm 6.0 KB

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