|  | // Copyright 2018 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/renderer_host/frame_token_message_queue.h" | 
|  |  | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/time/time.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Test verision of FrameTokenMessageQueue::Client which tracks the number of | 
|  | // calls to client methods, and the associated input parameters. | 
|  | class TestFrameTokenMessageQueueClient : public FrameTokenMessageQueue::Client { | 
|  | public: | 
|  | TestFrameTokenMessageQueueClient() {} | 
|  |  | 
|  | TestFrameTokenMessageQueueClient(const TestFrameTokenMessageQueueClient&) = | 
|  | delete; | 
|  | TestFrameTokenMessageQueueClient& operator=( | 
|  | const TestFrameTokenMessageQueueClient&) = delete; | 
|  |  | 
|  | ~TestFrameTokenMessageQueueClient() {} | 
|  |  | 
|  | // Resets all method counters. | 
|  | void Reset(); | 
|  |  | 
|  | // FrameTokenMessageQueue::Client: | 
|  | void OnInvalidFrameToken(uint32_t frame_token) override; | 
|  |  | 
|  | bool invalid_frame_token_called() const { | 
|  | return invalid_frame_token_called_; | 
|  | } | 
|  | uint32_t invalid_frame_token() const { return invalid_frame_token_; } | 
|  |  | 
|  | private: | 
|  | bool invalid_frame_token_called_ = false; | 
|  | uint32_t invalid_frame_token_ = 0u; | 
|  | }; | 
|  |  | 
|  | void TestFrameTokenMessageQueueClient::Reset() { | 
|  | invalid_frame_token_called_ = false; | 
|  | invalid_frame_token_ = 0u; | 
|  | } | 
|  |  | 
|  | void TestFrameTokenMessageQueueClient::OnInvalidFrameToken( | 
|  | uint32_t frame_token) { | 
|  | invalid_frame_token_called_ = true; | 
|  | invalid_frame_token_ = frame_token; | 
|  | } | 
|  |  | 
|  | // Test class which provides FrameTokenCallback() to be used as a closure when | 
|  | // enqueueing non-IPC callbacks. This only tracks if the callback was called. | 
|  | class TestNonIPCMessageEnqueuer { | 
|  | public: | 
|  | TestNonIPCMessageEnqueuer() {} | 
|  |  | 
|  | TestNonIPCMessageEnqueuer(const TestNonIPCMessageEnqueuer&) = delete; | 
|  | TestNonIPCMessageEnqueuer& operator=(const TestNonIPCMessageEnqueuer&) = | 
|  | delete; | 
|  |  | 
|  | ~TestNonIPCMessageEnqueuer() {} | 
|  |  | 
|  | void FrameTokenCallback(base::TimeTicks activation_time); | 
|  |  | 
|  | bool frame_token_callback_called() const { | 
|  | return frame_token_callback_called_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool frame_token_callback_called_ = false; | 
|  | }; | 
|  |  | 
|  | void TestNonIPCMessageEnqueuer::FrameTokenCallback( | 
|  | base::TimeTicks activation_time) { | 
|  | frame_token_callback_called_ = true; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class FrameTokenMessageQueueTest : public testing::Test { | 
|  | public: | 
|  | FrameTokenMessageQueueTest(); | 
|  |  | 
|  | FrameTokenMessageQueueTest(const FrameTokenMessageQueueTest&) = delete; | 
|  | FrameTokenMessageQueueTest& operator=(const FrameTokenMessageQueueTest&) = | 
|  | delete; | 
|  |  | 
|  | ~FrameTokenMessageQueueTest() override {} | 
|  |  | 
|  | TestFrameTokenMessageQueueClient* test_client() { return &test_client_; } | 
|  | TestNonIPCMessageEnqueuer* test_non_ipc_enqueuer() { | 
|  | return &test_non_ipc_enqueuer_; | 
|  | } | 
|  | FrameTokenMessageQueue* frame_token_message_queue() { | 
|  | return &frame_token_message_queue_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | TestFrameTokenMessageQueueClient test_client_; | 
|  | TestNonIPCMessageEnqueuer test_non_ipc_enqueuer_; | 
|  | FrameTokenMessageQueue frame_token_message_queue_; | 
|  | }; | 
|  |  | 
|  | FrameTokenMessageQueueTest::FrameTokenMessageQueueTest() { | 
|  | frame_token_message_queue_.Init(&test_client_); | 
|  | } | 
|  |  | 
|  | // Tests that if we only have a non-IPC callback enqueued that it is called once | 
|  | // the frame token arrive. | 
|  | TEST_F(FrameTokenMessageQueueTest, EnqueueOnlyNonIPC) { | 
|  | FrameTokenMessageQueue* queue = frame_token_message_queue(); | 
|  | TestFrameTokenMessageQueueClient* client = test_client(); | 
|  | TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer(); | 
|  | ASSERT_EQ(0u, queue->size()); | 
|  |  | 
|  | const uint32_t frame_token = 42; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(enqueuer))); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  |  | 
|  | queue->DidProcessFrame(frame_token, base::TimeTicks::Now()); | 
|  | EXPECT_EQ(0u, queue->size()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  | EXPECT_TRUE(enqueuer->frame_token_callback_called()); | 
|  | } | 
|  |  | 
|  | // Verifies that if there are multiple non-IPC messages enqueued that they are | 
|  | // all called. | 
|  | TEST_F(FrameTokenMessageQueueTest, MultipleNonIPCMessages) { | 
|  | FrameTokenMessageQueue* queue = frame_token_message_queue(); | 
|  | TestFrameTokenMessageQueueClient* client = test_client(); | 
|  | TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer(); | 
|  | ASSERT_EQ(0u, queue->size()); | 
|  |  | 
|  | const uint32_t frame_token = 42; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(enqueuer))); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  |  | 
|  | // Create a second callback | 
|  | TestNonIPCMessageEnqueuer second_enqueuer; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(&second_enqueuer))); | 
|  | EXPECT_FALSE(second_enqueuer.frame_token_callback_called()); | 
|  | EXPECT_EQ(2u, queue->size()); | 
|  |  | 
|  | queue->DidProcessFrame(frame_token, base::TimeTicks::Now()); | 
|  | EXPECT_EQ(0u, queue->size()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  | EXPECT_TRUE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_TRUE(second_enqueuer.frame_token_callback_called()); | 
|  | } | 
|  |  | 
|  | // Tests that if a non-IPC callback is enqueued, after its frame token as been | 
|  | // received, that it is immediately processed. | 
|  | TEST_F(FrameTokenMessageQueueTest, EnqueuedAfterFrameTokenImmediatelyRuns) { | 
|  | FrameTokenMessageQueue* queue = frame_token_message_queue(); | 
|  | TestFrameTokenMessageQueueClient* client = test_client(); | 
|  | TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer(); | 
|  | ASSERT_EQ(0u, queue->size()); | 
|  |  | 
|  | const uint32_t frame_token = 42; | 
|  | queue->DidProcessFrame(frame_token, base::TimeTicks::Now()); | 
|  | EXPECT_EQ(0u, queue->size()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  |  | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(enqueuer))); | 
|  | EXPECT_EQ(0u, queue->size()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  | EXPECT_TRUE(enqueuer->frame_token_callback_called()); | 
|  | } | 
|  |  | 
|  | // Test that if non-IPC callbacks are enqueued for different frame tokens, that | 
|  | // we only process the messages associated with the arriving token, and keep the | 
|  | // others enqueued. | 
|  | TEST_F(FrameTokenMessageQueueTest, DifferentFrameTokensEnqueuedNonIPC) { | 
|  | FrameTokenMessageQueue* queue = frame_token_message_queue(); | 
|  | TestFrameTokenMessageQueueClient* client = test_client(); | 
|  | TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer(); | 
|  | ASSERT_EQ(0u, queue->size()); | 
|  |  | 
|  | const uint32_t frame_token_1 = 42; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token_1, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(enqueuer))); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  |  | 
|  | // Create a second callback | 
|  | const uint32_t frame_token_2 = 1337; | 
|  | TestNonIPCMessageEnqueuer second_enqueuer; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token_2, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(&second_enqueuer))); | 
|  | EXPECT_FALSE(second_enqueuer.frame_token_callback_called()); | 
|  | EXPECT_EQ(2u, queue->size()); | 
|  |  | 
|  | queue->DidProcessFrame(frame_token_1, base::TimeTicks::Now()); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  | EXPECT_TRUE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_FALSE(second_enqueuer.frame_token_callback_called()); | 
|  |  | 
|  | queue->DidProcessFrame(frame_token_2, base::TimeTicks::Now()); | 
|  | EXPECT_TRUE(second_enqueuer.frame_token_callback_called()); | 
|  | } | 
|  |  | 
|  | // Tests that if DidProcessFrame is called with an invalid token, that it is | 
|  | // rejected, and that no callbacks are processed. | 
|  | TEST_F(FrameTokenMessageQueueTest, InvalidDidProcessFrameTokenNotProcessed) { | 
|  | FrameTokenMessageQueue* queue = frame_token_message_queue(); | 
|  | TestFrameTokenMessageQueueClient* client = test_client(); | 
|  | TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer(); | 
|  | ASSERT_EQ(0u, queue->size()); | 
|  |  | 
|  | const uint32_t frame_token = 42; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(enqueuer))); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  |  | 
|  | // Empty token should be invalid even with no process frames processed. | 
|  | const uint32_t invalid_frame_token = 0; | 
|  | queue->DidProcessFrame(invalid_frame_token, base::TimeTicks::Now()); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  | EXPECT_TRUE(client->invalid_frame_token_called()); | 
|  | EXPECT_EQ(invalid_frame_token, client->invalid_frame_token()); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | } | 
|  |  | 
|  | // Test that if DidProcessFrame is called with an earlier frame token, that it | 
|  | // is rejected, and that no callbacks are processed. | 
|  | TEST_F(FrameTokenMessageQueueTest, EarlierTokenForDidProcessFrameRejected) { | 
|  | FrameTokenMessageQueue* queue = frame_token_message_queue(); | 
|  | TestFrameTokenMessageQueueClient* client = test_client(); | 
|  | TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer(); | 
|  | ASSERT_EQ(0u, queue->size()); | 
|  |  | 
|  | // Settings a low value frame token will not block enqueueing. | 
|  | const uint32_t earlier_frame_token = 42; | 
|  | queue->DidProcessFrame(earlier_frame_token, base::TimeTicks::Now()); | 
|  |  | 
|  | const uint32_t frame_token = 1337; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | frame_token, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(enqueuer))); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  |  | 
|  | // Using a frame token that is earlier than the last received should be | 
|  | // rejected. | 
|  | const uint32_t invalid_frame_token = earlier_frame_token - 1; | 
|  | queue->DidProcessFrame(invalid_frame_token, base::TimeTicks::Now()); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  | EXPECT_TRUE(client->invalid_frame_token_called()); | 
|  | EXPECT_EQ(invalid_frame_token, client->invalid_frame_token()); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | } | 
|  |  | 
|  | // Tests that if we have already enqueued a callback for a frame token, that if | 
|  | // a request for an earlier frame token arrives, that it is still enqueued. Then | 
|  | // once the large frame token arrives, both are processed. | 
|  | TEST_F(FrameTokenMessageQueueTest, OutOfOrderFrameTokensEnqueue) { | 
|  | FrameTokenMessageQueue* queue = frame_token_message_queue(); | 
|  | TestFrameTokenMessageQueueClient* client = test_client(); | 
|  | TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer(); | 
|  | ASSERT_EQ(0u, queue->size()); | 
|  |  | 
|  | const uint32_t larger_frame_token = 1337; | 
|  | queue->EnqueueOrRunFrameTokenCallback( | 
|  | larger_frame_token, | 
|  | base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback, | 
|  | base::Unretained(enqueuer))); | 
|  | EXPECT_EQ(1u, queue->size()); | 
|  | EXPECT_FALSE(enqueuer->frame_token_callback_called()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  |  | 
|  | // Process both with the larger frame token arriving. | 
|  | queue->DidProcessFrame(larger_frame_token, base::TimeTicks::Now()); | 
|  | EXPECT_EQ(0u, queue->size()); | 
|  | EXPECT_FALSE(client->invalid_frame_token_called()); | 
|  | EXPECT_TRUE(enqueuer->frame_token_callback_called()); | 
|  | } | 
|  |  | 
|  | }  // namespace content |