|  | // Copyright 2015 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "extensions/browser/load_monitoring_extension_host_queue.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <limits> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "extensions/browser/deferred_start_render_host.h" | 
|  | #include "extensions/browser/extensions_test.h" | 
|  | #include "extensions/browser/serial_extension_host_queue.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class StubDeferredStartRenderHost : public DeferredStartRenderHost { | 
|  | public: | 
|  | // Returns true if this host is being observed by |observer|. | 
|  | bool IsObservedBy(DeferredStartRenderHostObserver* observer) { | 
|  | return observers_.count(observer) > 0; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // DeferredStartRenderHost: | 
|  | void AddDeferredStartRenderHostObserver( | 
|  | DeferredStartRenderHostObserver* observer) override { | 
|  | observers_.insert(observer); | 
|  | } | 
|  | void RemoveDeferredStartRenderHostObserver( | 
|  | DeferredStartRenderHostObserver* observer) override { | 
|  | observers_.erase(observer); | 
|  | } | 
|  | void CreateRenderViewNow() override {} | 
|  |  | 
|  | std::set<DeferredStartRenderHostObserver*> observers_; | 
|  | }; | 
|  |  | 
|  | const size_t g_invalid_size_t = std::numeric_limits<size_t>::max(); | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class LoadMonitoringExtensionHostQueueTest : public ExtensionsTest { | 
|  | public: | 
|  | LoadMonitoringExtensionHostQueueTest() | 
|  | : finished_(false), | 
|  | // Arbitrary choice of an invalid size_t. | 
|  | num_queued_(g_invalid_size_t), | 
|  | num_loaded_(g_invalid_size_t), | 
|  | max_awaiting_loading_(g_invalid_size_t), | 
|  | max_active_loading_(g_invalid_size_t) {} | 
|  |  | 
|  | void SetUp() override { | 
|  | queue_.reset(new LoadMonitoringExtensionHostQueue( | 
|  | // Use a SerialExtensionHostQueue because it's simple. | 
|  | std::unique_ptr<ExtensionHostQueue>(new SerialExtensionHostQueue()), | 
|  | base::TimeDelta(),  // no delay, easier to test | 
|  | base::Bind(&LoadMonitoringExtensionHostQueueTest::Finished, | 
|  | base::Unretained(this)))); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | // Creates a new DeferredStartRenderHost. Ownership is held by this class, | 
|  | // not passed to caller. | 
|  | StubDeferredStartRenderHost* CreateHost() { | 
|  | stubs_.push_back(std::make_unique<StubDeferredStartRenderHost>()); | 
|  | return stubs_.back().get(); | 
|  | } | 
|  |  | 
|  | // Our single LoadMonitoringExtensionHostQueue instance. | 
|  | LoadMonitoringExtensionHostQueue* queue() { return queue_.get(); } | 
|  |  | 
|  | // Returns true if the queue has finished monitoring. | 
|  | bool finished() const { return finished_; } | 
|  |  | 
|  | // These are available after the queue has finished (in which case finished() | 
|  | // will return true). | 
|  | size_t num_queued() { return num_queued_; } | 
|  | size_t num_loaded() { return num_loaded_; } | 
|  | size_t max_awaiting_loading() { return max_awaiting_loading_; } | 
|  | size_t max_active_loading() { return max_active_loading_; } | 
|  |  | 
|  | private: | 
|  | // Callback when queue has finished monitoring. | 
|  | void Finished(size_t num_queued, | 
|  | size_t num_loaded, | 
|  | size_t max_awaiting_loading, | 
|  | size_t max_active_loading) { | 
|  | CHECK(!finished_); | 
|  | finished_ = true; | 
|  | num_queued_ = num_queued; | 
|  | num_loaded_ = num_loaded; | 
|  | max_awaiting_loading_ = max_awaiting_loading; | 
|  | max_active_loading_ = max_active_loading; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<LoadMonitoringExtensionHostQueue> queue_; | 
|  | std::vector<std::unique_ptr<StubDeferredStartRenderHost>> stubs_; | 
|  |  | 
|  | // Set after the queue has finished monitoring. | 
|  | bool finished_; | 
|  | size_t num_queued_; | 
|  | size_t num_loaded_; | 
|  | size_t max_awaiting_loading_; | 
|  | size_t max_active_loading_; | 
|  | }; | 
|  |  | 
|  | // Tests that if monitoring is never started, nor any hosts added, nothing is | 
|  | // recorded. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, NeverStarted) { | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_FALSE(finished()); | 
|  | } | 
|  |  | 
|  | // Tests that if monitoring has started but no hosts added, it's recorded as 0. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, NoHosts) { | 
|  | queue()->StartMonitoring(); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(0u, num_queued()); | 
|  | EXPECT_EQ(0u, num_loaded()); | 
|  | EXPECT_EQ(0u, max_awaiting_loading()); | 
|  | EXPECT_EQ(0u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests that adding a host starts monitoring. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddOneHost) { | 
|  | queue()->Add(CreateHost()); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(0u, num_loaded()); | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(0u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests that a host added and removed is still recorded, but not as a load | 
|  | // finished. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndRemoveOneHost) { | 
|  | DeferredStartRenderHost* host = CreateHost(); | 
|  | queue()->Add(host); | 
|  | queue()->Remove(host); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(0u, num_loaded()); | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(0u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests adding and starting a single host. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartOneHost) { | 
|  | DeferredStartRenderHost* host = CreateHost(); | 
|  | queue()->Add(host); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(0u, num_loaded()); | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(1u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests adding and destroying a single host without starting it. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndDestroyOneHost) { | 
|  | DeferredStartRenderHost* host = CreateHost(); | 
|  | queue()->Add(host); | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(0u, num_loaded()); | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(0u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests adding, starting, and stopping a single host. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndStopOneHost) { | 
|  | DeferredStartRenderHost* host = CreateHost(); | 
|  | queue()->Add(host); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host); | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(1u, num_loaded()); | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(1u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests adding, starting, and stopping a single host - twice. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndStopOneHostTwice) { | 
|  | DeferredStartRenderHost* host = CreateHost(); | 
|  | queue()->Add(host); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host); | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host); | 
|  |  | 
|  | // Re-starting loading should also be recorded fine (e.g. navigations could | 
|  | // trigger this). | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host); | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(2u, num_loaded());  // 2 loaded this time, because we ran twice | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(1u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests adding, starting, and destroying (i.e. an implicit stop) a single host. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndDestroyOneHost) { | 
|  | DeferredStartRenderHost* host = CreateHost(); | 
|  | queue()->Add(host); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host); | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(1u, num_loaded()); | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(1u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests adding, starting, and removing a single host. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndRemoveOneHost) { | 
|  | DeferredStartRenderHost* host = CreateHost(); | 
|  | queue()->Add(host); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host); | 
|  | queue()->Remove(host); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(1u, num_queued()); | 
|  | EXPECT_EQ(0u, num_loaded()); | 
|  | EXPECT_EQ(1u, max_awaiting_loading()); | 
|  | EXPECT_EQ(1u, max_active_loading()); | 
|  | } | 
|  |  | 
|  | // Tests monitoring a sequence of hosts. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, Sequence) { | 
|  | // Scenario: | 
|  | // | 
|  | // 6 hosts will be added, only 5 will start loading, with a maximum of 4 in | 
|  | // the queue and 3 loading at any time. Only 2 will finish. | 
|  | DeferredStartRenderHost* host1 = CreateHost(); | 
|  | DeferredStartRenderHost* host2 = CreateHost(); | 
|  | DeferredStartRenderHost* host3 = CreateHost(); | 
|  | DeferredStartRenderHost* host4 = CreateHost(); | 
|  | DeferredStartRenderHost* host5 = CreateHost(); | 
|  | DeferredStartRenderHost* host6 = CreateHost(); | 
|  |  | 
|  | queue()->Add(host1); | 
|  | queue()->Add(host2); | 
|  | queue()->Add(host3); | 
|  |  | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host1); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host2); | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host1); | 
|  |  | 
|  | queue()->Add(host4); | 
|  | queue()->Add(host5); | 
|  | queue()->Add(host6); | 
|  |  | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host3); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host4); | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host4); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host5); | 
|  |  | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(finished()); | 
|  | EXPECT_EQ(6u, num_queued()); | 
|  | EXPECT_EQ(2u, num_loaded()); | 
|  | EXPECT_EQ(4u, max_awaiting_loading()); | 
|  | EXPECT_EQ(3u, max_active_loading()); | 
|  |  | 
|  | // Complete a realistic sequence by stopping and/or destroying all hosts. | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host1); | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host2); | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host2); | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host3); | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host3); | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host4); | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host5);  // never stopped | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host6);  // never started/stopped | 
|  | } | 
|  |  | 
|  | // Tests that the queue is observing Hosts from adding them through to being | 
|  | // removed - that the load sequence itself is irrelevant. | 
|  | // | 
|  | // This is an unfortunate implementation-style test, but it used to be a bug | 
|  | // and difficult to catch outside of a proper test framework - one in which we | 
|  | // don't have to trigger events by hand. | 
|  | TEST_F(LoadMonitoringExtensionHostQueueTest, ObserverLifetime) { | 
|  | StubDeferredStartRenderHost* host1 = CreateHost(); | 
|  | StubDeferredStartRenderHost* host2 = CreateHost(); | 
|  | StubDeferredStartRenderHost* host3 = CreateHost(); | 
|  | StubDeferredStartRenderHost* host4 = CreateHost(); | 
|  |  | 
|  | EXPECT_FALSE(host1->IsObservedBy(queue())); | 
|  | EXPECT_FALSE(host2->IsObservedBy(queue())); | 
|  | EXPECT_FALSE(host3->IsObservedBy(queue())); | 
|  | EXPECT_FALSE(host4->IsObservedBy(queue())); | 
|  |  | 
|  | queue()->Add(host1); | 
|  | queue()->Add(host2); | 
|  | queue()->Add(host3); | 
|  | queue()->Add(host4); | 
|  |  | 
|  | EXPECT_TRUE(host1->IsObservedBy(queue())); | 
|  | EXPECT_TRUE(host2->IsObservedBy(queue())); | 
|  | EXPECT_TRUE(host3->IsObservedBy(queue())); | 
|  | EXPECT_TRUE(host4->IsObservedBy(queue())); | 
|  |  | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host1); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host2); | 
|  | queue()->OnDeferredStartRenderHostDidStartFirstLoad(host3); | 
|  | // host4 will test that we Remove before Starting - so don't start. | 
|  |  | 
|  | EXPECT_TRUE(host1->IsObservedBy(queue())); | 
|  | EXPECT_TRUE(host2->IsObservedBy(queue())); | 
|  | EXPECT_TRUE(host3->IsObservedBy(queue())); | 
|  | EXPECT_TRUE(host4->IsObservedBy(queue())); | 
|  |  | 
|  | queue()->OnDeferredStartRenderHostDidStopFirstLoad(host1); | 
|  | queue()->OnDeferredStartRenderHostDestroyed(host2); | 
|  |  | 
|  | EXPECT_TRUE(host1->IsObservedBy(queue())); | 
|  | EXPECT_TRUE(host2->IsObservedBy(queue())); | 
|  |  | 
|  | queue()->Remove(host1); | 
|  | queue()->Remove(host2); | 
|  | queue()->Remove(host3); | 
|  | queue()->Remove(host4); | 
|  |  | 
|  | EXPECT_FALSE(host1->IsObservedBy(queue())); | 
|  | EXPECT_FALSE(host2->IsObservedBy(queue())); | 
|  | EXPECT_FALSE(host3->IsObservedBy(queue())); | 
|  | EXPECT_FALSE(host4->IsObservedBy(queue())); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |