1 // Copyright 2018 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 
5 #include "chrome/browser/resource_coordinator/tab_load_tracker.h"
6 
7 #include <memory>
8 #include <vector>
9 
10 #include "base/process/kill.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/prefetch/no_state_prefetch/prerender_manager_factory.h"
13 #include "chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.h"
14 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
15 #include "chrome/test/base/testing_profile.h"
16 #include "components/no_state_prefetch/browser/prerender_handle.h"
17 #include "components/no_state_prefetch/browser/prerender_manager.h"
18 #include "content/public/browser/web_contents_observer.h"
19 #include "content/public/test/mock_render_process_host.h"
20 #include "content/public/test/navigation_simulator.h"
21 #include "content/public/test/web_contents_tester.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "url/gurl.h"
25 
26 namespace resource_coordinator {
27 
28 using testing::_;
29 using testing::StrictMock;
30 using LoadingState = TabLoadTracker::LoadingState;
31 
32 namespace {
33 
NavigateAndFinishLoading(content::WebContents * web_contents,const GURL & url)34 void NavigateAndFinishLoading(content::WebContents* web_contents,
35                               const GURL& url) {
36   content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents, url);
37 }
38 
NavigateAndKeepLoading(content::WebContents * web_contents,const GURL & url)39 std::unique_ptr<content::NavigationSimulator> NavigateAndKeepLoading(
40     content::WebContents* web_contents,
41     const GURL& url) {
42   auto navigation =
43       content::NavigationSimulator::CreateBrowserInitiated(url, web_contents);
44   navigation->SetKeepLoading(true);
45   navigation->Commit();
46   return navigation;
47 }
48 
49 }  // namespace
50 
51 // Test wrapper of TabLoadTracker that exposes some internals.
52 class TestTabLoadTracker : public TabLoadTracker {
53  public:
54   using TabLoadTracker::DetermineLoadingState;
55   using TabLoadTracker::DidReceiveResponse;
56   using TabLoadTracker::OnPageStoppedLoading;
57   using TabLoadTracker::RenderProcessGone;
58   using TabLoadTracker::StartTracking;
59   using TabLoadTracker::StopTracking;
60 
TestTabLoadTracker()61   TestTabLoadTracker() : all_tabs_are_non_ui_tabs_(false) {}
~TestTabLoadTracker()62   virtual ~TestTabLoadTracker() {}
63 
64   // Some accessors for TabLoadTracker internals.
tabs() const65   const TabMap& tabs() const { return tabs_; }
66 
IsUiTab(content::WebContents * web_contents)67   bool IsUiTab(content::WebContents* web_contents) override {
68     if (all_tabs_are_non_ui_tabs_)
69       return false;
70     return TabLoadTracker::IsUiTab(web_contents);
71   }
72 
SetAllTabsAreNonUiTabs(bool enabled)73   void SetAllTabsAreNonUiTabs(bool enabled) {
74     all_tabs_are_non_ui_tabs_ = enabled;
75   }
76 
77  private:
78   bool all_tabs_are_non_ui_tabs_;
79 };
80 
81 // A mock observer class.
82 class LenientMockObserver : public TabLoadTracker::Observer {
83  public:
LenientMockObserver()84   LenientMockObserver() {}
~LenientMockObserver()85   ~LenientMockObserver() override {}
86 
87   // TabLoadTracker::Observer implementation:
88   MOCK_METHOD2(OnStartTracking, void(content::WebContents*, LoadingState));
89   MOCK_METHOD3(OnLoadingStateChange,
90                void(content::WebContents*, LoadingState, LoadingState));
91   MOCK_METHOD2(OnStopTracking, void(content::WebContents*, LoadingState));
92 
93  private:
94   DISALLOW_COPY_AND_ASSIGN(LenientMockObserver);
95 };
96 using MockObserver = testing::StrictMock<LenientMockObserver>;
97 
98 // A WebContentsObserver that forwards relevant WebContents events to the
99 // provided tracker.
100 class TestWebContentsObserver : public content::WebContentsObserver {
101  public:
TestWebContentsObserver(content::WebContents * web_contents,TestTabLoadTracker * tracker)102   TestWebContentsObserver(content::WebContents* web_contents,
103                           TestTabLoadTracker* tracker)
104       : content::WebContentsObserver(web_contents), tracker_(tracker) {}
105 
~TestWebContentsObserver()106   ~TestWebContentsObserver() override {}
107 
108   // content::WebContentsObserver:
DidReceiveResponse()109   void DidReceiveResponse() override {
110     tracker_->DidReceiveResponse(web_contents());
111   }
RenderProcessGone(base::TerminationStatus status)112   void RenderProcessGone(base::TerminationStatus status) override {
113     tracker_->RenderProcessGone(web_contents(), status);
114   }
115 
116  private:
117   TestTabLoadTracker* tracker_;
118 };
119 
120 // The test harness.
121 class TabLoadTrackerTest : public ChromeRenderViewHostTestHarness {
122  public:
SetUp()123   void SetUp() override {
124     ChromeRenderViewHostTestHarness::SetUp();
125 
126     contents1_ = CreateTestWebContents();
127     contents2_ = CreateTestWebContents();
128     contents3_ = CreateTestWebContents();
129 
130     tracker_.AddObserver(&observer_);
131   }
132 
TearDown()133   void TearDown() override {
134     // The WebContents must be deleted before the test harness deletes the
135     // RenderProcessHost.
136     contents1_.reset();
137     contents2_.reset();
138     contents3_.reset();
139 
140     ChromeRenderViewHostTestHarness::TearDown();
141   }
142 
ExpectTabCounts(size_t tabs,size_t unloaded,size_t loading,size_t loaded)143   void ExpectTabCounts(size_t tabs,
144                        size_t unloaded,
145                        size_t loading,
146                        size_t loaded) {
147     EXPECT_EQ(tabs, unloaded + loading + loaded);
148     EXPECT_EQ(tabs, tracker().GetTabCount());
149     EXPECT_EQ(unloaded, tracker().GetUnloadedTabCount());
150     EXPECT_EQ(loading, tracker().GetLoadingTabCount());
151     EXPECT_EQ(loaded, tracker().GetLoadedTabCount());
152   }
153 
ExpectUiTabCounts(size_t tabs,size_t unloaded,size_t loading,size_t loaded)154   void ExpectUiTabCounts(size_t tabs,
155                          size_t unloaded,
156                          size_t loading,
157                          size_t loaded) {
158     EXPECT_EQ(tabs, unloaded + loading + loaded);
159     EXPECT_EQ(tabs, tracker().GetUiTabCount());
160     EXPECT_EQ(unloaded, tracker().GetUnloadedUiTabCount());
161     EXPECT_EQ(loading, tracker().GetLoadingUiTabCount());
162     EXPECT_EQ(loaded, tracker().GetLoadedUiTabCount());
163   }
164 
165   void StateTransitionsTest(bool use_non_ui_tabs);
166 
tracker()167   TestTabLoadTracker& tracker() { return tracker_; }
observer()168   MockObserver& observer() { return observer_; }
169 
contents1()170   content::WebContents* contents1() { return contents1_.get(); }
contents2()171   content::WebContents* contents2() { return contents2_.get(); }
contents3()172   content::WebContents* contents3() { return contents3_.get(); }
173 
174  private:
175   TestTabLoadTracker tracker_;
176   MockObserver observer_;
177 
178   std::unique_ptr<content::WebContents> contents1_;
179   std::unique_ptr<content::WebContents> contents2_;
180   std::unique_ptr<content::WebContents> contents3_;
181 };
182 
183 // A macro that ensures that a meaningful line number gets included in the
184 // stack trace when ExpectTabCounts fails.
185 #define EXPECT_TAB_COUNTS(a, b, c, d) \
186   {                                   \
187     SCOPED_TRACE("");                 \
188     ExpectTabCounts(a, b, c, d);      \
189   }
190 #define EXPECT_UI_TAB_COUNTS(a, b, c, d) \
191   {                                      \
192     SCOPED_TRACE("");                    \
193     ExpectUiTabCounts(a, b, c, d);       \
194   }
195 #define EXPECT_TAB_AND_UI_TAB_COUNTS(a, b, c, d) \
196   {                                              \
197     SCOPED_TRACE("");                            \
198     ExpectTabCounts(a, b, c, d);                 \
199     ExpectUiTabCounts(a, b, c, d);               \
200   }
201 
TEST_F(TabLoadTrackerTest,DetermineLoadingState)202 TEST_F(TabLoadTrackerTest, DetermineLoadingState) {
203   EXPECT_EQ(LoadingState::UNLOADED,
204             tracker().DetermineLoadingState(contents1()));
205 
206   // Navigate to a page and expect it to be loading.
207   auto navigation =
208       NavigateAndKeepLoading(contents1(), GURL("http://chromium.org"));
209   EXPECT_EQ(LoadingState::LOADING,
210             tracker().DetermineLoadingState(contents1()));
211 
212   // Indicate that loading is finished and expect the state to transition.
213   navigation->StopLoading();
214   EXPECT_EQ(LoadingState::LOADED, tracker().DetermineLoadingState(contents1()));
215 }
216 
StateTransitionsTest(bool use_non_ui_tabs)217 void TabLoadTrackerTest::StateTransitionsTest(bool use_non_ui_tabs) {
218   tracker().SetAllTabsAreNonUiTabs(use_non_ui_tabs);
219 
220   // Set up the contents in UNLOADED, LOADING and LOADED states. This tests
221   // each possible "entry" state.
222   auto navigation_tab_2 =
223       NavigateAndKeepLoading(contents2(), GURL("http://foo.com"));
224   NavigateAndFinishLoading(contents3(), GURL("http://bar.com"));
225 
226   // Add the contents to the tracker.
227   EXPECT_CALL(observer(), OnStartTracking(contents1(), LoadingState::UNLOADED));
228   tracker().StartTracking(contents1());
229   if (use_non_ui_tabs) {
230     EXPECT_TAB_COUNTS(1, 1, 0, 0);
231     EXPECT_UI_TAB_COUNTS(0, 0, 0, 0);
232   } else {
233     EXPECT_TAB_AND_UI_TAB_COUNTS(1, 1, 0, 0);
234   }
235   testing::Mock::VerifyAndClearExpectations(&observer());
236 
237   EXPECT_CALL(observer(), OnStartTracking(contents2(), LoadingState::LOADING));
238   tracker().StartTracking(contents2());
239   if (use_non_ui_tabs) {
240     EXPECT_TAB_COUNTS(2, 1, 1, 0);
241     EXPECT_UI_TAB_COUNTS(0, 0, 0, 0);
242   } else {
243     EXPECT_TAB_AND_UI_TAB_COUNTS(2, 1, 1, 0);
244   }
245   testing::Mock::VerifyAndClearExpectations(&observer());
246 
247   EXPECT_CALL(observer(), OnStartTracking(contents3(), LoadingState::LOADED));
248   tracker().StartTracking(contents3());
249   if (use_non_ui_tabs) {
250     EXPECT_TAB_COUNTS(3, 1, 1, 1);
251     EXPECT_UI_TAB_COUNTS(0, 0, 0, 0);
252   } else {
253     EXPECT_TAB_AND_UI_TAB_COUNTS(3, 1, 1, 1);
254   }
255   testing::Mock::VerifyAndClearExpectations(&observer());
256 
257   // Start observers for the contents.
258   TestWebContentsObserver observer1(contents1(), &tracker());
259   TestWebContentsObserver observer2(contents2(), &tracker());
260   TestWebContentsObserver observer3(contents3(), &tracker());
261 
262   // Now test all of the possible state transitions.
263 
264   // Finish the loading for contents2.
265   EXPECT_CALL(observer(),
266               OnLoadingStateChange(contents2(), LoadingState::LOADING,
267                                    LoadingState::LOADED));
268   navigation_tab_2->StopLoading();
269   // The state transition should only occur *after* the PAI signal.
270   if (use_non_ui_tabs) {
271     EXPECT_TAB_COUNTS(3, 1, 1, 1);
272     EXPECT_UI_TAB_COUNTS(0, 0, 0, 0);
273   } else {
274     EXPECT_TAB_AND_UI_TAB_COUNTS(3, 1, 1, 1);
275   }
276   tracker().OnPageStoppedLoading(contents2());
277 
278   if (use_non_ui_tabs) {
279     EXPECT_TAB_COUNTS(3, 1, 0, 2);
280     EXPECT_UI_TAB_COUNTS(0, 0, 0, 0);
281   } else {
282     EXPECT_TAB_AND_UI_TAB_COUNTS(3, 1, 0, 2);
283   }
284   testing::Mock::VerifyAndClearExpectations(&observer());
285 
286   // Start the loading for contents1.
287   EXPECT_CALL(observer(),
288               OnLoadingStateChange(contents1(), LoadingState::UNLOADED,
289                                    LoadingState::LOADING));
290   auto navigation_tab_1 =
291       NavigateAndKeepLoading(contents1(), GURL("http://baz.com"));
292   if (use_non_ui_tabs) {
293     EXPECT_TAB_COUNTS(3, 0, 1, 2);
294     EXPECT_UI_TAB_COUNTS(0, 0, 0, 0);
295   } else {
296     EXPECT_TAB_AND_UI_TAB_COUNTS(3, 0, 1, 2);
297   }
298   testing::Mock::VerifyAndClearExpectations(&observer());
299 
300   // Crash the render process corresponding to the main frame of a tab. This
301   // should cause the tab to transition to the UNLOADED state.
302   EXPECT_CALL(observer(),
303               OnLoadingStateChange(contents1(), LoadingState::LOADING,
304                                    LoadingState::UNLOADED));
305   content::MockRenderProcessHost* rph =
306       static_cast<content::MockRenderProcessHost*>(
307           contents1()->GetMainFrame()->GetProcess());
308   rph->SimulateCrash();
309   if (use_non_ui_tabs) {
310     EXPECT_TAB_COUNTS(3, 1, 0, 2);
311     EXPECT_UI_TAB_COUNTS(0, 0, 0, 0);
312   } else {
313     EXPECT_TAB_AND_UI_TAB_COUNTS(3, 1, 0, 2);
314   }
315   testing::Mock::VerifyAndClearExpectations(&observer());
316 }
317 
TEST_F(TabLoadTrackerTest,StateTransitions)318 TEST_F(TabLoadTrackerTest, StateTransitions) {
319   StateTransitionsTest(false /* use_non_ui_tabs */);
320 }
321 
TEST_F(TabLoadTrackerTest,StateTransitionsNonUiTabs)322 TEST_F(TabLoadTrackerTest, StateTransitionsNonUiTabs) {
323   StateTransitionsTest(true /* use_non_ui_tabs */);
324 }
325 
TEST_F(TabLoadTrackerTest,PrerenderContentsDoesNotChangeUiTabCounts)326 TEST_F(TabLoadTrackerTest, PrerenderContentsDoesNotChangeUiTabCounts) {
327   NavigateAndKeepLoading(contents1(), GURL("http://baz.com"));
328 
329   // Add the contents to the tracker.
330   EXPECT_CALL(observer(), OnStartTracking(contents1(), LoadingState::LOADING));
331   tracker().StartTracking(contents1());
332   EXPECT_TAB_AND_UI_TAB_COUNTS(1, 0, 1, 0);
333   testing::Mock::VerifyAndClearExpectations(&observer());
334 
335   EXPECT_CALL(observer(), OnStartTracking(contents2(), LoadingState::UNLOADED));
336   tracker().StartTracking(contents2());
337   EXPECT_TAB_AND_UI_TAB_COUNTS(2, 1, 1, 0);
338   testing::Mock::VerifyAndClearExpectations(&observer());
339 
340   // Start observers for the contents.
341   TestWebContentsObserver observer1(contents1(), &tracker());
342   TestWebContentsObserver observer2(contents2(), &tracker());
343 
344   // Prerender some contents.
345   prerender::PrerenderManager* prerender_manager =
346       prerender::PrerenderManagerFactory::GetForBrowserContext(profile());
347   GURL url("http://www.example.com");
348   const gfx::Size kSize(640, 480);
349   std::unique_ptr<prerender::PrerenderHandle> prerender_handle(
350       prerender_manager->AddPrerenderFromOmnibox(
351           url, contents1()->GetController().GetDefaultSessionStorageNamespace(),
352           kSize));
353   EXPECT_NE(nullptr, prerender_handle);
354   const std::vector<content::WebContents*> contentses =
355       prerender_manager->GetAllNoStatePrefetchingContentsForTesting();
356   ASSERT_EQ(1U, contentses.size());
357 
358   // Prerendering should not change the UI tab counts, but should increase
359   // overall tab count. Note, contentses[0] is UNLOADED since it is not a test
360   // web contents and therefore hasn't started receiving data.
361   TestWebContentsObserver prerender_observer(contentses[0], &tracker());
362   EXPECT_CALL(observer(),
363               OnStartTracking(contentses[0], LoadingState::UNLOADED));
364   tracker().StartTracking(contentses[0]);
365   EXPECT_TAB_COUNTS(3, 2, 1, 0);
366   EXPECT_UI_TAB_COUNTS(2, 1, 1, 0);
367   testing::Mock::VerifyAndClearExpectations(&observer());
368 
369   prerender_manager->CancelAllPrerenders();
370 }
371 
TEST_F(TabLoadTrackerTest,SwapInUiTabContents)372 TEST_F(TabLoadTrackerTest, SwapInUiTabContents) {
373   NavigateAndKeepLoading(contents1(), GURL("http://baz.com"));
374 
375   // Add the contents to the tracker.
376   EXPECT_CALL(observer(), OnStartTracking(contents1(), LoadingState::LOADING));
377   tracker().StartTracking(contents1());
378   EXPECT_TAB_AND_UI_TAB_COUNTS(1, 0, 1, 0);
379   testing::Mock::VerifyAndClearExpectations(&observer());
380 
381   EXPECT_CALL(observer(), OnStartTracking(contents2(), LoadingState::UNLOADED));
382   tracker().StartTracking(contents2());
383   EXPECT_TAB_AND_UI_TAB_COUNTS(2, 1, 1, 0);
384   testing::Mock::VerifyAndClearExpectations(&observer());
385 
386   // Start observers for the contents.
387   TestWebContentsObserver observer1(contents1(), &tracker());
388   TestWebContentsObserver observer2(contents2(), &tracker());
389 
390   // Simulate non-ui tab contents running in the background and getting swapped
391   // in. Non-ui tabs should not change the ui tab counts, but should change the
392   // overall tab counts.
393   std::unique_ptr<content::WebContents> non_ui_tab_contents =
394       CreateTestWebContents();
395   EXPECT_CALL(observer(), OnStartTracking(non_ui_tab_contents.get(),
396                                           LoadingState::UNLOADED));
397   tracker().SetAllTabsAreNonUiTabs(true);
398   tracker().StartTracking(non_ui_tab_contents.get());
399   EXPECT_TAB_COUNTS(3, 2, 1, 0);
400   EXPECT_UI_TAB_COUNTS(2, 1, 1, 0);
401   testing::Mock::VerifyAndClearExpectations(&observer());
402   // Swap in the prerender contents and simulate resulting tab strip swap.
403   // |non_ui_tab_contents| is already being tracked. The UI tab count should
404   // remain stable through the swap.
405   EXPECT_CALL(observer(), OnStopTracking(contents1(), LoadingState::LOADING));
406   tracker().SetAllTabsAreNonUiTabs(false);
407   tracker().SwapTabContents(contents1(), non_ui_tab_contents.get());
408   // After swap, but before we stop tracking the swapped-out contents. The UI
409   // tab counts should be in the end-state, but the total tab counts will be in
410   // the pre-swap state while the swapped-out contents is still being tracked.
411   EXPECT_TAB_COUNTS(3, 2, 1, 0);
412   EXPECT_UI_TAB_COUNTS(2, 2, 0, 0);
413   tracker().StopTracking(contents1());
414   EXPECT_TAB_AND_UI_TAB_COUNTS(2, 2, 0, 0);
415   testing::Mock::VerifyAndClearExpectations(&observer());
416 }
417 
TEST_F(TabLoadTrackerTest,SwapInUntrackedContents)418 TEST_F(TabLoadTrackerTest, SwapInUntrackedContents) {
419   NavigateAndKeepLoading(contents1(), GURL("http://baz.com"));
420 
421   // Add the contents to the tracker.
422   EXPECT_CALL(observer(), OnStartTracking(contents1(), LoadingState::LOADING));
423   tracker().StartTracking(contents1());
424   EXPECT_TAB_AND_UI_TAB_COUNTS(1, 0, 1, 0);
425   testing::Mock::VerifyAndClearExpectations(&observer());
426 
427   EXPECT_CALL(observer(), OnStartTracking(contents2(), LoadingState::UNLOADED));
428   tracker().StartTracking(contents2());
429   EXPECT_TAB_AND_UI_TAB_COUNTS(2, 1, 1, 0);
430   testing::Mock::VerifyAndClearExpectations(&observer());
431 
432   // Create an untracked web contents in the UNLOADED state, and swap it with
433   // the contents in the LOADING state. Since |untracked_contents| has no tab
434   // helper attached, swapping it in shouldn't changed the tab count.
435   std::unique_ptr<content::WebContents> untracked_contents =
436       CreateTestWebContents();
437   tracker().SwapTabContents(contents1(), untracked_contents.get());
438   // The total counts will remain stable since swapping out doesn't cause any
439   // web contents to stop being tracking. However, the swapped-out contents are
440   // no longer included in UI tab counts, and the swapped-in contents won't be
441   // until it is tracked.
442   EXPECT_TAB_COUNTS(2, 1, 1, 0);
443   EXPECT_UI_TAB_COUNTS(1, 1, 0, 0);
444 
445   // Simulate swap in tab strip, which would cause |untracked_contents| to be
446   // tracked and the tab counts to change.
447   EXPECT_CALL(observer(), OnStopTracking(contents1(), LoadingState::LOADING));
448   EXPECT_CALL(observer(), OnStartTracking(untracked_contents.get(),
449                                           LoadingState::UNLOADED));
450   tracker().StopTracking(contents1());
451   tracker().StartTracking(untracked_contents.get());
452   EXPECT_TAB_AND_UI_TAB_COUNTS(2, 2, 0, 0);
453 }
454 
455 }  // namespace resource_coordinator
456