1 // Copyright 2018 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.h"
6
7 #include <type_traits>
8
9 #include "base/functional/bind.h"
10 #include "base/logging.h"
11 #include "base/memory/raw_ptr.h"
12 #include "base/message_loop/message_pump_for_io.h"
13 #include "base/message_loop/message_pump_for_ui.h"
14 #include "base/message_loop/message_pump_type.h"
15 #include "base/run_loop.h"
16 #include "base/task/single_thread_task_executor.h"
17 #include "base/test/bind.h"
18 #include "base/test/scoped_feature_list.h"
19 #include "base/test/test_timeouts.h"
20 #include "base/threading/thread.h"
21 #include "build/build_config.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24
25 #if BUILDFLAG(IS_ANDROID)
26 #include "base/android/input_hint_checker.h"
27 #include "base/test/test_support_android.h"
28 #endif
29
30 #if BUILDFLAG(IS_WIN)
31 #include <windows.h>
32 #endif
33
34 using ::testing::_;
35 using ::testing::AnyNumber;
36 using ::testing::AtMost;
37 using ::testing::Invoke;
38 using ::testing::Return;
39
40 namespace base {
41
42 namespace {
43
44 // On most platforms, the MessagePump impl controls when native work (e.g.
45 // handling input messages) gets its turn. Tests below verify that by expecting
46 // OnBeginWorkItem() calls that cover native work. In some configurations
47 // however, the platform owns the message loop and is the one yielding to
48 // Chrome's MessagePump to DoWork(). Under those configurations, it is not
49 // possible to precisely account for OnBeginWorkItem() calls as they can occur
50 // nondeterministically. For example, on some versions of iOS, the native loop
51 // can surprisingly go through multiple cycles of
52 // kCFRunLoopAfterWaiting=>kCFRunLoopBeforeWaiting before invoking Chrome's
53 // RunWork() for the first time, triggering multiple ScopedDoWorkItem 's for
54 // potential native work before the first DoWork().
ChromeControlsNativeEventProcessing(MessagePumpType pump_type)55 constexpr bool ChromeControlsNativeEventProcessing(MessagePumpType pump_type) {
56 #if BUILDFLAG(IS_MAC)
57 return pump_type != MessagePumpType::UI;
58 #elif BUILDFLAG(IS_IOS)
59 return false;
60 #else
61 return true;
62 #endif
63 }
64
65 class MockMessagePumpDelegate : public MessagePump::Delegate {
66 public:
MockMessagePumpDelegate(MessagePumpType pump_type)67 explicit MockMessagePumpDelegate(MessagePumpType pump_type)
68 : check_work_items_(ChromeControlsNativeEventProcessing(pump_type)),
69 native_work_item_accounting_is_on_(
70 !ChromeControlsNativeEventProcessing(pump_type)) {}
71
~MockMessagePumpDelegate()72 ~MockMessagePumpDelegate() override { ValidateNoOpenWorkItems(); }
73
74 MockMessagePumpDelegate(const MockMessagePumpDelegate&) = delete;
75 MockMessagePumpDelegate& operator=(const MockMessagePumpDelegate&) = delete;
76
BeforeWait()77 void BeforeWait() override {}
BeginNativeWorkBeforeDoWork()78 void BeginNativeWorkBeforeDoWork() override {}
79 MOCK_METHOD0(DoWork, MessagePump::Delegate::NextWorkInfo());
80 MOCK_METHOD0(DoIdleWork, void());
81
82 // Functions invoked directly by the message pump.
OnBeginWorkItem()83 void OnBeginWorkItem() override {
84 any_work_begun_ = true;
85
86 if (check_work_items_) {
87 MockOnBeginWorkItem();
88 }
89
90 ++work_item_count_;
91 }
92
OnEndWorkItem(int run_level_depth)93 void OnEndWorkItem(int run_level_depth) override {
94 if (check_work_items_) {
95 MockOnEndWorkItem(run_level_depth);
96 }
97
98 EXPECT_EQ(run_level_depth, work_item_count_);
99
100 --work_item_count_;
101
102 // It's not possible to close more scopes than there are open ones.
103 EXPECT_GE(work_item_count_, 0);
104 }
105
RunDepth()106 int RunDepth() override { return work_item_count_; }
107
ValidateNoOpenWorkItems()108 void ValidateNoOpenWorkItems() {
109 // Upon exiting there cannot be any open scopes.
110 EXPECT_EQ(work_item_count_, 0);
111
112 if (native_work_item_accounting_is_on_) {
113 // Tests should trigger work beginning at least once except on iOS where
114 // they need a call to MessagePumpUIApplication::Attach() to do so when on
115 // the UI thread.
116 #if !BUILDFLAG(IS_IOS)
117 EXPECT_TRUE(any_work_begun_);
118 #endif
119 }
120 }
121
122 // Mock functions for asserting.
123 MOCK_METHOD0(MockOnBeginWorkItem, void(void));
124 MOCK_METHOD1(MockOnEndWorkItem, void(int));
125
126 // If native events are covered in the current configuration it's not
127 // possible to precisely test all assertions related to work items. This is
128 // because a number of speculative WorkItems are created during execution of
129 // such loops and it's not possible to determine their number before the
130 // execution of the test. In such configurations the functioning of the
131 // message pump is still verified by looking at the counts of opened and
132 // closed WorkItems.
133 const bool check_work_items_;
134 const bool native_work_item_accounting_is_on_;
135
136 int work_item_count_ = 0;
137 bool any_work_begun_ = false;
138 };
139
140 class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> {
141 public:
MessagePumpTest()142 MessagePumpTest() : message_pump_(MessagePump::Create(GetParam())) {}
143
144 protected:
145 #if defined(USE_GLIB)
146 // Because of a GLIB implementation quirk, the pump doesn't do the same things
147 // between each DoWork. In this case, it won't set/clear a ScopedDoWorkItem
148 // because we run a chrome work item in the runloop outside of GLIB's control,
149 // so we oscillate between setting and not setting PreDoWorkExpectations.
150 std::map<MessagePump::Delegate*, int> do_work_counts;
151 #endif
AddPreDoWorkExpectations(testing::StrictMock<MockMessagePumpDelegate> & delegate)152 void AddPreDoWorkExpectations(
153 testing::StrictMock<MockMessagePumpDelegate>& delegate) {
154 #if BUILDFLAG(IS_WIN)
155 if (GetParam() == MessagePumpType::UI) {
156 // The Windows MessagePumpForUI may do native work from ::PeekMessage()
157 // and labels itself as such.
158 EXPECT_CALL(delegate, MockOnBeginWorkItem);
159 EXPECT_CALL(delegate, MockOnEndWorkItem);
160
161 // If the above event was MessagePumpForUI's own kMsgHaveWork internal
162 // event, it will process another event to replace it (ref.
163 // ProcessPumpReplacementMessage).
164 EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
165 EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
166 }
167 #endif // BUILDFLAG(IS_WIN)
168 #if defined(USE_GLIB)
169 do_work_counts.try_emplace(&delegate, 0);
170 if (GetParam() == MessagePumpType::UI) {
171 if (++do_work_counts[&delegate] % 2) {
172 // The GLib MessagePump will do native work before chrome work on
173 // startup.
174 EXPECT_CALL(delegate, MockOnBeginWorkItem);
175 EXPECT_CALL(delegate, MockOnEndWorkItem);
176 }
177 }
178 #endif // defined(USE_GLIB)
179 }
180
AddPostDoWorkExpectations(testing::StrictMock<MockMessagePumpDelegate> & delegate)181 void AddPostDoWorkExpectations(
182 testing::StrictMock<MockMessagePumpDelegate>& delegate) {
183 #if defined(USE_GLIB)
184 if (GetParam() == MessagePumpType::UI) {
185 // The GLib MessagePump can create and destroy work items between DoWorks
186 // depending on internal state.
187 EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
188 EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
189 }
190 #endif // defined(USE_GLIB)
191 }
192
193 std::unique_ptr<MessagePump> message_pump_;
194 };
195
196 } // namespace
197
TEST_P(MessagePumpTest,QuitStopsWork)198 TEST_P(MessagePumpTest, QuitStopsWork) {
199 testing::InSequence sequence;
200 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
201
202 AddPreDoWorkExpectations(delegate);
203
204 // Not expecting any calls to DoIdleWork after quitting, nor any of the
205 // PostDoWorkExpectations, quitting should be instantaneous.
206 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
207 message_pump_->Quit();
208 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
209 }));
210
211 // MessagePumpGlib uses a work item between a HandleDispatch() call and
212 // passing control back to the chrome loop, which handles the Quit() despite
213 // us not necessarily doing any native work during that time.
214 #if defined(USE_GLIB)
215 if (GetParam() == MessagePumpType::UI) {
216 AddPostDoWorkExpectations(delegate);
217 }
218 #endif
219
220 EXPECT_CALL(delegate, DoIdleWork()).Times(0);
221
222 message_pump_->ScheduleWork();
223 message_pump_->Run(&delegate);
224 }
225
226 #if BUILDFLAG(IS_ANDROID)
227 class MockInputHintChecker : public android::InputHintChecker {
228 public:
229 MOCK_METHOD(bool, HasInputImplWithThrottling, (), (override));
230 };
231
TEST_P(MessagePumpTest,DetectingHasInputYieldsOnUi)232 TEST_P(MessagePumpTest, DetectingHasInputYieldsOnUi) {
233 testing::InSequence sequence;
234 MessagePumpType pump_type = GetParam();
235 testing::StrictMock<MockMessagePumpDelegate> delegate(pump_type);
236 testing::StrictMock<MockInputHintChecker> hint_checker_mock;
237 android::InputHintChecker::ScopedOverrideInstance scoped_override_hint(
238 &hint_checker_mock);
239 base::test::ScopedFeatureList scoped_feature_list;
240 scoped_feature_list.InitAndEnableFeature(android::kYieldWithInputHint);
241 android::InputHintChecker::InitializeFeatures();
242 uint32_t initial_work_enters = GetAndroidNonDelayedWorkEnterCount();
243
244 // Override the first DoWork() to return an immediate next.
245 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([] {
246 auto work_info =
247 MessagePump::Delegate::NextWorkInfo{.delayed_run_time = TimeTicks()};
248 CHECK(work_info.is_immediate());
249 return work_info;
250 }));
251
252 if (pump_type == MessagePumpType::UI) {
253 // Override the following InputHintChecker::HasInput() to return true.
254 EXPECT_CALL(hint_checker_mock, HasInputImplWithThrottling())
255 .WillOnce(Invoke([] { return true; }));
256 }
257
258 // Override the second DoWork() to quit the loop.
259 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
260 message_pump_->Quit();
261 return MessagePump::Delegate::NextWorkInfo{.delayed_run_time =
262 TimeTicks::Max()};
263 }));
264
265 // No immediate next_work_info remaining before the yield. Not expecting
266 // to observe an input hint check.
267 EXPECT_CALL(delegate, DoIdleWork()).Times(0);
268
269 message_pump_->Run(&delegate);
270
271 // Expect two calls to DoNonDelayedLooperWork(). The first one occurs as a
272 // result of MessagePump::Run(). The second one is the result of yielding
273 // after HasInput() returns true. For non-UI MessagePumpType the
274 // MessagePump::Create() does not intercept entering DoNonDelayedLooperWork(),
275 // so it remains 0 instead of 1.
276 uint32_t work_loop_entered = (pump_type == MessagePumpType::UI) ? 2 : 0;
277 EXPECT_EQ(initial_work_enters + work_loop_entered,
278 GetAndroidNonDelayedWorkEnterCount());
279 }
280 #endif
281
TEST_P(MessagePumpTest,QuitStopsWorkWithNestedRunLoop)282 TEST_P(MessagePumpTest, QuitStopsWorkWithNestedRunLoop) {
283 testing::InSequence sequence;
284 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
285 testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
286
287 AddPreDoWorkExpectations(delegate);
288
289 // We first schedule a call to DoWork, which runs a nested run loop. After
290 // the nested loop exits, we schedule another DoWork which quits the outer
291 // (original) run loop. The test verifies that there are no extra calls to
292 // DoWork after the outer loop quits.
293 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([&] {
294 message_pump_->Run(&nested_delegate);
295 // A null NextWorkInfo indicates immediate follow-up work.
296 return MessagePump::Delegate::NextWorkInfo();
297 }));
298
299 AddPreDoWorkExpectations(nested_delegate);
300 EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([&] {
301 // Quit the nested run loop.
302 message_pump_->Quit();
303 // The underlying pump should process the next task in the first run-level
304 // regardless of whether the nested run-level indicates there's no more work
305 // (e.g. can happen when the only remaining tasks are non-nestable).
306 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
307 }));
308
309 // The `nested_delegate` will quit first.
310 AddPostDoWorkExpectations(nested_delegate);
311
312 // Return a delayed task with |yield_to_native| set, and exit.
313 AddPostDoWorkExpectations(delegate);
314
315 AddPreDoWorkExpectations(delegate);
316
317 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
318 message_pump_->Quit();
319 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
320 }));
321
322 message_pump_->ScheduleWork();
323 message_pump_->Run(&delegate);
324 }
325
TEST_P(MessagePumpTest,YieldToNativeRequestedSmokeTest)326 TEST_P(MessagePumpTest, YieldToNativeRequestedSmokeTest) {
327 // The handling of the "yield_to_native" boolean in the NextWorkInfo is only
328 // implemented on the MessagePumpAndroid. However since we inject a fake one
329 // for testing this is hard to test. This test ensures that setting this
330 // boolean doesn't cause any MessagePump to explode.
331 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
332
333 testing::InSequence sequence;
334
335 // Return an immediate task with |yield_to_native| set.
336 AddPreDoWorkExpectations(delegate);
337 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([] {
338 return MessagePump::Delegate::NextWorkInfo{TimeTicks(), TimeDelta(),
339 TimeTicks(),
340 /* yield_to_native = */ true};
341 }));
342 AddPostDoWorkExpectations(delegate);
343
344 AddPreDoWorkExpectations(delegate);
345 // Return a delayed task with |yield_to_native| set, and exit.
346 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
347 message_pump_->Quit();
348 auto now = TimeTicks::Now();
349 return MessagePump::Delegate::NextWorkInfo{now + Milliseconds(1),
350 TimeDelta(), now, true};
351 }));
352 EXPECT_CALL(delegate, DoIdleWork()).Times(AnyNumber());
353
354 message_pump_->ScheduleWork();
355 message_pump_->Run(&delegate);
356 }
357
TEST_P(MessagePumpTest,LeewaySmokeTest)358 TEST_P(MessagePumpTest, LeewaySmokeTest) {
359 // The handling of the "leeway" in the NextWorkInfo is only implemented on
360 // mac. However since we inject a fake one for testing this is hard to test.
361 // This test ensures that setting this boolean doesn't cause any MessagePump
362 // to explode.
363 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
364
365 testing::InSequence sequence;
366
367 AddPreDoWorkExpectations(delegate);
368 // Return a delayed task with |yield_to_native| set, and exit.
369 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
370 message_pump_->Quit();
371 auto now = TimeTicks::Now();
372 return MessagePump::Delegate::NextWorkInfo{now + Milliseconds(1),
373 Milliseconds(8), now};
374 }));
375 EXPECT_CALL(delegate, DoIdleWork()).Times(AnyNumber());
376
377 message_pump_->ScheduleWork();
378 message_pump_->Run(&delegate);
379 }
380
TEST_P(MessagePumpTest,RunWithoutScheduleWorkInvokesDoWork)381 TEST_P(MessagePumpTest, RunWithoutScheduleWorkInvokesDoWork) {
382 testing::InSequence sequence;
383 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
384
385 AddPreDoWorkExpectations(delegate);
386
387 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
388 message_pump_->Quit();
389 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
390 }));
391
392 AddPostDoWorkExpectations(delegate);
393
394 #if BUILDFLAG(IS_IOS)
395 EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
396 #endif
397
398 message_pump_->Run(&delegate);
399 }
400
TEST_P(MessagePumpTest,NestedRunWithoutScheduleWorkInvokesDoWork)401 TEST_P(MessagePumpTest, NestedRunWithoutScheduleWorkInvokesDoWork) {
402 testing::InSequence sequence;
403 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
404 testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
405
406 AddPreDoWorkExpectations(delegate);
407
408 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this, &nested_delegate] {
409 message_pump_->Run(&nested_delegate);
410 message_pump_->Quit();
411 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
412 }));
413
414 AddPreDoWorkExpectations(nested_delegate);
415
416 EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([this] {
417 message_pump_->Quit();
418 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
419 }));
420
421 // We quit `nested_delegate` before `delegate`
422 AddPostDoWorkExpectations(nested_delegate);
423
424 AddPostDoWorkExpectations(delegate);
425
426 #if BUILDFLAG(IS_IOS)
427 EXPECT_CALL(nested_delegate, DoIdleWork).Times(AnyNumber());
428 EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
429 #endif
430
431 message_pump_->Run(&delegate);
432 }
433
434 INSTANTIATE_TEST_SUITE_P(All,
435 MessagePumpTest,
436 ::testing::Values(MessagePumpType::DEFAULT,
437 MessagePumpType::UI,
438 MessagePumpType::IO));
439
440 } // namespace base
441