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