165 lines
6.0 KiB
Plaintext
165 lines
6.0 KiB
Plaintext
|
|
// Copyright 2017 The Chromium Authors
|
||
|
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
|
// found in the LICENSE file.
|
||
|
|
|
||
|
|
#include "base/message_loop/message_pump_mac.h"
|
||
|
|
|
||
|
|
#include "base/cancelable_callback.h"
|
||
|
|
#include "base/functional/bind.h"
|
||
|
|
#include "base/mac/scoped_cftyperef.h"
|
||
|
|
#import "base/mac/scoped_nsobject.h"
|
||
|
|
#include "base/task/current_thread.h"
|
||
|
|
#include "base/task/single_thread_task_runner.h"
|
||
|
|
#include "base/test/bind.h"
|
||
|
|
#include "base/test/task_environment.h"
|
||
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
||
|
|
|
||
|
|
@interface TestModalAlertCloser : NSObject
|
||
|
|
- (void)runTestThenCloseAlert:(NSAlert*)alert;
|
||
|
|
@end
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// Internal constants from message_pump_mac.mm.
|
||
|
|
constexpr int kAllModesMask = 0xf;
|
||
|
|
constexpr int kNSApplicationModalSafeModeMask = 0x3;
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
namespace base {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// PostedTasks are only executed while the message pump has a delegate. That is,
|
||
|
|
// when a base::RunLoop is running, so in order to test whether posted tasks
|
||
|
|
// are run by CFRunLoopRunInMode and *not* by the regular RunLoop, we need to
|
||
|
|
// be inside a task that is also calling CFRunLoopRunInMode.
|
||
|
|
// This function posts |task| and runs the given |mode|.
|
||
|
|
void RunTaskInMode(CFRunLoopMode mode, OnceClosure task) {
|
||
|
|
// Since this task is "ours" rather than a system task, allow nesting.
|
||
|
|
CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
|
||
|
|
CancelableOnceClosure cancelable(std::move(task));
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
|
||
|
|
cancelable.callback());
|
||
|
|
while (CFRunLoopRunInMode(mode, 0, true) == kCFRunLoopRunHandledSource)
|
||
|
|
;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
// Tests the correct behavior of ScopedPumpMessagesInPrivateModes.
|
||
|
|
TEST(MessagePumpMacTest, ScopedPumpMessagesInPrivateModes) {
|
||
|
|
test::SingleThreadTaskEnvironment task_environment(
|
||
|
|
test::SingleThreadTaskEnvironment::MainThreadType::UI);
|
||
|
|
|
||
|
|
CFRunLoopMode kRegular = kCFRunLoopDefaultMode;
|
||
|
|
CFRunLoopMode kPrivate = CFSTR("NSUnhighlightMenuRunLoopMode");
|
||
|
|
|
||
|
|
// Work is seen when running in the default mode.
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
|
||
|
|
EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
|
||
|
|
|
||
|
|
// But not seen when running in a private mode.
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&RunTaskInMode, kPrivate, MakeExpectedNotRunClosure(FROM_HERE)));
|
||
|
|
EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
|
||
|
|
|
||
|
|
{
|
||
|
|
ScopedPumpMessagesInPrivateModes allow_private;
|
||
|
|
// Now the work should be seen.
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&RunTaskInMode, kPrivate, MakeExpectedRunClosure(FROM_HERE)));
|
||
|
|
EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
|
||
|
|
|
||
|
|
// The regular mode should also work the same.
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
|
||
|
|
EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
|
||
|
|
}
|
||
|
|
|
||
|
|
// And now the scoper is out of scope, private modes should no longer see it.
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&RunTaskInMode, kPrivate, MakeExpectedNotRunClosure(FROM_HERE)));
|
||
|
|
EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
|
||
|
|
|
||
|
|
// Only regular modes see it.
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE,
|
||
|
|
BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
|
||
|
|
EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Tests that private message loop modes are not pumped while a modal dialog is
|
||
|
|
// present.
|
||
|
|
TEST(MessagePumpMacTest, ScopedPumpMessagesAttemptWithModalDialog) {
|
||
|
|
test::SingleThreadTaskEnvironment task_environment(
|
||
|
|
test::SingleThreadTaskEnvironment::MainThreadType::UI);
|
||
|
|
|
||
|
|
{
|
||
|
|
base::ScopedPumpMessagesInPrivateModes allow_private;
|
||
|
|
// No modal window, so all modes should be pumped.
|
||
|
|
EXPECT_EQ(kAllModesMask, allow_private.GetModeMaskForTest());
|
||
|
|
}
|
||
|
|
|
||
|
|
base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]);
|
||
|
|
[alert addButtonWithTitle:@"OK"];
|
||
|
|
base::scoped_nsobject<TestModalAlertCloser> closer(
|
||
|
|
[[TestModalAlertCloser alloc] init]);
|
||
|
|
[closer performSelector:@selector(runTestThenCloseAlert:)
|
||
|
|
withObject:alert
|
||
|
|
afterDelay:0
|
||
|
|
inModes:@[ NSModalPanelRunLoopMode ]];
|
||
|
|
NSInteger result = [alert runModal];
|
||
|
|
EXPECT_EQ(NSAlertFirstButtonReturn, result);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(MessagePumpMacTest, QuitWithModalWindow) {
|
||
|
|
test::SingleThreadTaskEnvironment task_environment(
|
||
|
|
test::SingleThreadTaskEnvironment::MainThreadType::UI);
|
||
|
|
NSWindow* window =
|
||
|
|
[[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
|
||
|
|
styleMask:NSWindowStyleMaskBorderless
|
||
|
|
backing:NSBackingStoreBuffered
|
||
|
|
defer:NO] autorelease];
|
||
|
|
|
||
|
|
// Check that quitting the run loop while a modal window is shown applies to
|
||
|
|
// |run_loop| rather than the internal NSApplication modal run loop.
|
||
|
|
RunLoop run_loop;
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE, base::BindLambdaForTesting([&] {
|
||
|
|
CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
|
||
|
|
ScopedPumpMessagesInPrivateModes pump_private;
|
||
|
|
[NSApp runModalForWindow:window];
|
||
|
|
}));
|
||
|
|
SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
|
|
FROM_HERE, base::BindLambdaForTesting([&] {
|
||
|
|
[NSApp stopModal];
|
||
|
|
run_loop.Quit();
|
||
|
|
}));
|
||
|
|
|
||
|
|
EXPECT_NO_FATAL_FAILURE(run_loop.Run());
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace base
|
||
|
|
|
||
|
|
@implementation TestModalAlertCloser
|
||
|
|
|
||
|
|
- (void)runTestThenCloseAlert:(NSAlert*)alert {
|
||
|
|
EXPECT_TRUE([NSApp modalWindow]);
|
||
|
|
{
|
||
|
|
base::ScopedPumpMessagesInPrivateModes allow_private;
|
||
|
|
// With a modal window, only safe modes should be pumped.
|
||
|
|
EXPECT_EQ(kNSApplicationModalSafeModeMask,
|
||
|
|
allow_private.GetModeMaskForTest());
|
||
|
|
}
|
||
|
|
[[alert buttons][0] performClick:nil];
|
||
|
|
}
|
||
|
|
|
||
|
|
@end
|