OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/memory/tab_manager.h" | |
6 | |
7 #include <algorithm> | |
8 #include <map> | |
9 #include <vector> | |
10 | |
11 #include "base/logging.h" | |
12 #include "base/macros.h" | |
13 #include "base/memory/ptr_util.h" | |
14 #include "base/metrics/field_trial.h" | |
15 #include "base/strings/string16.h" | |
16 #include "base/test/mock_entropy_provider.h" | |
17 #include "base/test/simple_test_tick_clock.h" | |
18 #include "base/time/time.h" | |
19 #include "build/build_config.h" | |
20 #include "chrome/browser/memory/tab_manager_web_contents_data.h" | |
21 #include "chrome/browser/memory/tab_stats.h" | |
22 #include "chrome/browser/profiles/profile.h" | |
23 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
24 #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" | |
25 #include "chrome/common/chrome_features.h" | |
26 #include "chrome/common/url_constants.h" | |
27 #include "chrome/test/base/chrome_render_view_host_test_harness.h" | |
28 #include "chrome/test/base/testing_profile.h" | |
29 #include "components/variations/variations_associated_data.h" | |
30 #include "content/public/browser/render_process_host.h" | |
31 #include "content/public/browser/web_contents.h" | |
32 #include "content/public/test/mock_render_process_host.h" | |
33 #include "content/public/test/web_contents_tester.h" | |
34 #include "testing/gtest/include/gtest/gtest.h" | |
35 #include "url/gurl.h" | |
36 | |
37 using content::WebContents; | |
38 using content::WebContentsTester; | |
39 | |
40 namespace memory { | |
41 namespace { | |
42 | |
43 class TabStripDummyDelegate : public TestTabStripModelDelegate { | |
44 public: | |
45 TabStripDummyDelegate() {} | |
46 | |
47 bool RunUnloadListenerBeforeClosing(WebContents* contents) override { | |
48 return false; | |
49 } | |
50 | |
51 private: | |
52 DISALLOW_COPY_AND_ASSIGN(TabStripDummyDelegate); | |
53 }; | |
54 | |
55 class MockTabStripModelObserver : public TabStripModelObserver { | |
56 public: | |
57 MockTabStripModelObserver() | |
58 : nb_events_(0), old_contents_(nullptr), new_contents_(nullptr) {} | |
59 | |
60 int NbEvents() const { return nb_events_; } | |
61 WebContents* OldContents() const { return old_contents_; } | |
62 WebContents* NewContents() const { return new_contents_; } | |
63 | |
64 void Reset() { | |
65 nb_events_ = 0; | |
66 old_contents_ = nullptr; | |
67 new_contents_ = nullptr; | |
68 } | |
69 | |
70 // TabStripModelObserver implementation: | |
71 void TabReplacedAt(TabStripModel* tab_strip_model, | |
72 WebContents* old_contents, | |
73 WebContents* new_contents, | |
74 int index) override { | |
75 nb_events_++; | |
76 old_contents_ = old_contents; | |
77 new_contents_ = new_contents; | |
78 } | |
79 | |
80 private: | |
81 int nb_events_; | |
82 WebContents* old_contents_; | |
83 WebContents* new_contents_; | |
84 | |
85 DISALLOW_COPY_AND_ASSIGN(MockTabStripModelObserver); | |
86 }; | |
87 | |
88 enum TestIndicies { | |
89 kSelected, | |
90 kAutoDiscardable, | |
91 kPinned, | |
92 kApp, | |
93 kPlayingAudio, | |
94 kFormEntry, | |
95 kRecent, | |
96 kOld, | |
97 kReallyOld, | |
98 kOldButPinned, | |
99 kInternalPage, | |
100 }; | |
101 | |
102 } // namespace | |
103 | |
104 class TabManagerTest : public ChromeRenderViewHostTestHarness { | |
105 public: | |
106 WebContents* CreateWebContents() { | |
107 return WebContents::Create(WebContents::CreateParams(profile())); | |
108 } | |
109 }; | |
110 | |
111 // TODO(georgesak): Add tests for protection to tabs with form input and | |
112 // playing audio; | |
113 | |
114 // Tests the sorting comparator to make sure it's producing the desired order. | |
115 TEST_F(TabManagerTest, Comparator) { | |
116 TabStatsList test_list; | |
117 const base::TimeTicks now = base::TimeTicks::Now(); | |
118 | |
119 // Add kSelected last to verify that the array is being sorted. | |
120 | |
121 { | |
122 TabStats stats; | |
123 stats.last_active = now; | |
124 stats.is_pinned = true; | |
125 stats.child_process_host_id = kPinned; | |
126 test_list.push_back(stats); | |
127 } | |
128 | |
129 { | |
130 TabStats stats; | |
131 stats.last_active = now; | |
132 stats.is_app = true; | |
133 stats.child_process_host_id = kApp; | |
134 test_list.push_back(stats); | |
135 } | |
136 | |
137 { | |
138 TabStats stats; | |
139 stats.last_active = now; | |
140 stats.is_media = true; | |
141 stats.child_process_host_id = kPlayingAudio; | |
142 test_list.push_back(stats); | |
143 } | |
144 | |
145 { | |
146 TabStats stats; | |
147 stats.last_active = now; | |
148 stats.has_form_entry = true; | |
149 stats.child_process_host_id = kFormEntry; | |
150 test_list.push_back(stats); | |
151 } | |
152 | |
153 { | |
154 TabStats stats; | |
155 stats.last_active = now - base::TimeDelta::FromSeconds(10); | |
156 stats.child_process_host_id = kRecent; | |
157 test_list.push_back(stats); | |
158 } | |
159 | |
160 { | |
161 TabStats stats; | |
162 stats.last_active = now - base::TimeDelta::FromMinutes(15); | |
163 stats.child_process_host_id = kOld; | |
164 test_list.push_back(stats); | |
165 } | |
166 | |
167 { | |
168 TabStats stats; | |
169 stats.last_active = now - base::TimeDelta::FromDays(365); | |
170 stats.child_process_host_id = kReallyOld; | |
171 test_list.push_back(stats); | |
172 } | |
173 | |
174 { | |
175 TabStats stats; | |
176 stats.is_pinned = true; | |
177 stats.last_active = now - base::TimeDelta::FromDays(365); | |
178 stats.child_process_host_id = kOldButPinned; | |
179 test_list.push_back(stats); | |
180 } | |
181 | |
182 { | |
183 TabStats stats; | |
184 stats.last_active = now; | |
185 stats.is_internal_page = true; | |
186 stats.child_process_host_id = kInternalPage; | |
187 test_list.push_back(stats); | |
188 } | |
189 | |
190 { | |
191 TabStats stats; | |
192 stats.last_active = now; | |
193 stats.is_auto_discardable = false; | |
194 stats.child_process_host_id = kAutoDiscardable; | |
195 test_list.push_back(stats); | |
196 } | |
197 | |
198 // This entry sorts to the front, so by adding it last, it verifies that the | |
199 // array is being sorted. | |
200 { | |
201 TabStats stats; | |
202 stats.last_active = now; | |
203 stats.is_selected = true; | |
204 stats.child_process_host_id = kSelected; | |
205 test_list.push_back(stats); | |
206 } | |
207 | |
208 std::sort(test_list.begin(), test_list.end(), TabManager::CompareTabStats); | |
209 | |
210 int index = 0; | |
211 EXPECT_EQ(kSelected, test_list[index++].child_process_host_id); | |
212 EXPECT_EQ(kAutoDiscardable, test_list[index++].child_process_host_id); | |
213 EXPECT_EQ(kFormEntry, test_list[index++].child_process_host_id); | |
214 EXPECT_EQ(kPlayingAudio, test_list[index++].child_process_host_id); | |
215 EXPECT_EQ(kPinned, test_list[index++].child_process_host_id); | |
216 EXPECT_EQ(kOldButPinned, test_list[index++].child_process_host_id); | |
217 EXPECT_EQ(kApp, test_list[index++].child_process_host_id); | |
218 EXPECT_EQ(kRecent, test_list[index++].child_process_host_id); | |
219 EXPECT_EQ(kOld, test_list[index++].child_process_host_id); | |
220 EXPECT_EQ(kReallyOld, test_list[index++].child_process_host_id); | |
221 EXPECT_EQ(kInternalPage, test_list[index++].child_process_host_id); | |
222 } | |
223 | |
224 TEST_F(TabManagerTest, IsInternalPage) { | |
225 EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUIDownloadsURL))); | |
226 EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUIHistoryURL))); | |
227 EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUINewTabURL))); | |
228 EXPECT_TRUE(TabManager::IsInternalPage(GURL(chrome::kChromeUISettingsURL))); | |
229 | |
230 // Debugging URLs are not included. | |
231 #if defined(OS_CHROMEOS) | |
232 EXPECT_FALSE(TabManager::IsInternalPage(GURL(chrome::kChromeUIDiscardsURL))); | |
233 #endif | |
234 EXPECT_FALSE( | |
235 TabManager::IsInternalPage(GURL(chrome::kChromeUINetInternalsURL))); | |
236 | |
237 // Prefix matches are included. | |
238 EXPECT_TRUE( | |
239 TabManager::IsInternalPage(GURL("chrome://settings/fakeSetting"))); | |
240 } | |
241 | |
242 // Ensures discarding tabs leaves TabStripModel in a good state. | |
243 TEST_F(TabManagerTest, DiscardWebContentsAt) { | |
244 TabManager tab_manager; | |
245 | |
246 TabStripDummyDelegate delegate; | |
247 TabStripModel tabstrip(&delegate, profile()); | |
248 tabstrip.AddObserver(&tab_manager); | |
249 | |
250 // Fill it with some tabs. | |
251 WebContents* contents1 = CreateWebContents(); | |
252 tabstrip.AppendWebContents(contents1, true); | |
253 WebContents* contents2 = CreateWebContents(); | |
254 tabstrip.AppendWebContents(contents2, true); | |
255 | |
256 // Start watching for events after the appends to avoid observing state | |
257 // transitions that aren't relevant to this test. | |
258 MockTabStripModelObserver tabstrip_observer; | |
259 tabstrip.AddObserver(&tabstrip_observer); | |
260 | |
261 // Discard one of the tabs. | |
262 WebContents* null_contents1 = tab_manager.DiscardWebContentsAt(0, &tabstrip); | |
263 ASSERT_EQ(2, tabstrip.count()); | |
264 EXPECT_TRUE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0))); | |
265 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1))); | |
266 ASSERT_EQ(null_contents1, tabstrip.GetWebContentsAt(0)); | |
267 ASSERT_EQ(contents2, tabstrip.GetWebContentsAt(1)); | |
268 ASSERT_EQ(1, tabstrip_observer.NbEvents()); | |
269 EXPECT_EQ(contents1, tabstrip_observer.OldContents()); | |
270 EXPECT_EQ(null_contents1, tabstrip_observer.NewContents()); | |
271 tabstrip_observer.Reset(); | |
272 | |
273 // Discard the same tab again, after resetting its discard state. | |
274 tab_manager.GetWebContentsData(tabstrip.GetWebContentsAt(0)) | |
275 ->SetDiscardState(false); | |
276 WebContents* null_contents2 = tab_manager.DiscardWebContentsAt(0, &tabstrip); | |
277 ASSERT_EQ(2, tabstrip.count()); | |
278 EXPECT_TRUE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0))); | |
279 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1))); | |
280 ASSERT_EQ(null_contents2, tabstrip.GetWebContentsAt(0)); | |
281 ASSERT_EQ(contents2, tabstrip.GetWebContentsAt(1)); | |
282 ASSERT_EQ(1, tabstrip_observer.NbEvents()); | |
283 EXPECT_EQ(null_contents1, tabstrip_observer.OldContents()); | |
284 EXPECT_EQ(null_contents2, tabstrip_observer.NewContents()); | |
285 | |
286 // Activating the tab should clear its discard state. | |
287 tabstrip.ActivateTabAt(0, true /* user_gesture */); | |
288 ASSERT_EQ(2, tabstrip.count()); | |
289 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0))); | |
290 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1))); | |
291 | |
292 // Don't discard active tab. | |
293 tab_manager.DiscardWebContentsAt(0, &tabstrip); | |
294 ASSERT_EQ(2, tabstrip.count()); | |
295 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(0))); | |
296 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1))); | |
297 | |
298 tabstrip.CloseAllTabs(); | |
299 EXPECT_TRUE(tabstrip.empty()); | |
300 } | |
301 | |
302 // Makes sure that reloading a discarded tab without activating it unmarks the | |
303 // tab as discarded so it won't reload on activation. | |
304 TEST_F(TabManagerTest, ReloadDiscardedTabContextMenu) { | |
305 // Note that we do not add |tab_manager| as an observer to |tabstrip| here as | |
306 // the event we are trying to test for is not related to the tab strip, but | |
307 // the web content instead and therefore should be handled by WebContentsData | |
308 // (which observes the web content). | |
309 TabManager tab_manager; | |
310 TabStripDummyDelegate delegate; | |
311 TabStripModel tabstrip(&delegate, profile()); | |
312 | |
313 // Create 2 tabs because the active tab cannot be discarded. | |
314 tabstrip.AppendWebContents(CreateWebContents(), true); | |
315 content::WebContents* test_contents = | |
316 WebContentsTester::CreateTestWebContents(browser_context(), nullptr); | |
317 tabstrip.AppendWebContents(test_contents, false); // Opened in background. | |
318 | |
319 // Navigate to a web page. This is necessary to set a current entry in memory | |
320 // so the reload can happen. | |
321 WebContentsTester::For(test_contents) | |
322 ->NavigateAndCommit(GURL("chrome://newtab")); | |
323 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1))); | |
324 | |
325 tab_manager.DiscardWebContentsAt(1, &tabstrip); | |
326 EXPECT_TRUE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1))); | |
327 | |
328 tabstrip.GetWebContentsAt(1)->GetController().Reload( | |
329 content::ReloadType::NORMAL, false); | |
330 EXPECT_FALSE(tab_manager.IsTabDiscarded(tabstrip.GetWebContentsAt(1))); | |
331 tabstrip.CloseAllTabs(); | |
332 EXPECT_TRUE(tabstrip.empty()); | |
333 } | |
334 | |
335 // Makes sure that the last active time property is saved even though the tab is | |
336 // discarded. | |
337 TEST_F(TabManagerTest, DiscardedTabKeepsLastActiveTime) { | |
338 TabManager tab_manager; | |
339 TabStripDummyDelegate delegate; | |
340 TabStripModel tabstrip(&delegate, profile()); | |
341 tabstrip.AddObserver(&tab_manager); | |
342 | |
343 tabstrip.AppendWebContents(CreateWebContents(), true); | |
344 WebContents* test_contents = CreateWebContents(); | |
345 tabstrip.AppendWebContents(test_contents, false); | |
346 | |
347 // Simulate an old inactive tab about to get discarded. | |
348 base::TimeTicks new_last_active_time = | |
349 base::TimeTicks::Now() - base::TimeDelta::FromMinutes(35); | |
350 test_contents->SetLastActiveTime(new_last_active_time); | |
351 EXPECT_EQ(new_last_active_time, test_contents->GetLastActiveTime()); | |
352 | |
353 WebContents* null_contents = tab_manager.DiscardWebContentsAt(1, &tabstrip); | |
354 EXPECT_EQ(new_last_active_time, null_contents->GetLastActiveTime()); | |
355 | |
356 tabstrip.CloseAllTabs(); | |
357 EXPECT_TRUE(tabstrip.empty()); | |
358 } | |
359 | |
360 // Test to see if a tab can only be discarded once. On Windows and Mac, this | |
361 // defaults to true unless overridden through a variation parameter. On other | |
362 // platforms, it's always false. | |
363 #if defined(OS_WIN) || defined(OS_MACOSX) | |
364 TEST_F(TabManagerTest, CanOnlyDiscardOnce) { | |
365 TabManager tab_manager; | |
366 const std::string kTrialName = features::kAutomaticTabDiscarding.name; | |
367 | |
368 // Not setting the variation parameter. | |
369 { | |
370 bool discard_once_value = tab_manager.CanOnlyDiscardOnce(); | |
371 EXPECT_TRUE(discard_once_value); | |
372 } | |
373 | |
374 // Setting the variation parameter to true. | |
375 { | |
376 std::unique_ptr<base::FieldTrialList> field_trial_list_; | |
377 field_trial_list_.reset( | |
378 new base::FieldTrialList( | |
379 base::MakeUnique<base::MockEntropyProvider>())); | |
380 variations::testing::ClearAllVariationParams(); | |
381 | |
382 std::map<std::string, std::string> params; | |
383 params["AllowMultipleDiscards"] = "true"; | |
384 ASSERT_TRUE(variations::AssociateVariationParams(kTrialName, "A", params)); | |
385 base::FieldTrialList::CreateFieldTrial(kTrialName, "A"); | |
386 | |
387 bool discard_once_value = tab_manager.CanOnlyDiscardOnce(); | |
388 EXPECT_FALSE(discard_once_value); | |
389 } | |
390 | |
391 // Setting the variation parameter to something else. | |
392 { | |
393 std::unique_ptr<base::FieldTrialList> field_trial_list_; | |
394 field_trial_list_.reset( | |
395 new base::FieldTrialList( | |
396 base::MakeUnique<base::MockEntropyProvider>())); | |
397 variations::testing::ClearAllVariationParams(); | |
398 | |
399 std::map<std::string, std::string> params; | |
400 params["AllowMultipleDiscards"] = "somethingElse"; | |
401 ASSERT_TRUE(variations::AssociateVariationParams(kTrialName, "B", params)); | |
402 base::FieldTrialList::CreateFieldTrial(kTrialName, "B"); | |
403 | |
404 bool discard_once_value = tab_manager.CanOnlyDiscardOnce(); | |
405 EXPECT_TRUE(discard_once_value); | |
406 } | |
407 } | |
408 #else | |
409 TEST_F(TabManagerTest, CanOnlyDiscardOnce) { | |
410 TabManager tab_manager; | |
411 | |
412 bool discard_once_value = tab_manager.CanOnlyDiscardOnce(); | |
413 EXPECT_FALSE(discard_once_value); | |
414 } | |
415 #endif // defined(OS_WIN) || defined(OS_MACOSX) | |
416 | |
417 TEST_F(TabManagerTest, DefaultTimeToPurgeInCorrectRange) { | |
418 TabManager tab_manager; | |
419 base::TimeDelta time_to_purge = | |
420 tab_manager.GetTimeToPurge(TabManager::kDefaultMinTimeToPurge); | |
421 EXPECT_GE(time_to_purge, base::TimeDelta::FromMinutes(30)); | |
422 EXPECT_LT(time_to_purge, base::TimeDelta::FromMinutes(60)); | |
423 } | |
424 | |
425 TEST_F(TabManagerTest, ShouldPurgeAtDefaultTime) { | |
426 TabManager tab_manager; | |
427 TabStripDummyDelegate delegate; | |
428 TabStripModel tabstrip(&delegate, profile()); | |
429 tabstrip.AddObserver(&tab_manager); | |
430 | |
431 WebContents* test_contents = CreateWebContents(); | |
432 tabstrip.AppendWebContents(test_contents, false); | |
433 | |
434 base::SimpleTestTickClock test_clock; | |
435 tab_manager.set_test_tick_clock(&test_clock); | |
436 | |
437 tab_manager.GetWebContentsData(test_contents)->set_is_purged(false); | |
438 tab_manager.GetWebContentsData(test_contents) | |
439 ->SetLastInactiveTime(test_clock.NowTicks()); | |
440 tab_manager.GetWebContentsData(test_contents) | |
441 ->set_time_to_purge(base::TimeDelta::FromMinutes(1)); | |
442 | |
443 // Wait 1 minute and verify that the tab is still not to be purged. | |
444 test_clock.Advance(base::TimeDelta::FromMinutes(1)); | |
445 EXPECT_FALSE(tab_manager.ShouldPurgeNow(test_contents)); | |
446 | |
447 // Wait another 1 second and verify that it should be purged now . | |
448 test_clock.Advance(base::TimeDelta::FromSeconds(1)); | |
449 EXPECT_TRUE(tab_manager.ShouldPurgeNow(test_contents)); | |
450 | |
451 tab_manager.GetWebContentsData(test_contents)->set_is_purged(true); | |
452 tab_manager.GetWebContentsData(test_contents) | |
453 ->SetLastInactiveTime(test_clock.NowTicks()); | |
454 | |
455 // Wait 1 day and verify that the tab is still be purged. | |
456 test_clock.Advance(base::TimeDelta::FromHours(24)); | |
457 EXPECT_FALSE(tab_manager.ShouldPurgeNow(test_contents)); | |
458 } | |
459 | |
460 TEST_F(TabManagerTest, ActivateTabResetPurgeState) { | |
461 TabManager tab_manager; | |
462 TabStripDummyDelegate delegate; | |
463 TabStripModel tabstrip(&delegate, profile()); | |
464 tabstrip.AddObserver(&tab_manager); | |
465 tab_manager.test_tab_strip_models_.push_back( | |
466 TabManager::TestTabStripModel(&tabstrip, false /* !is_app */)); | |
467 | |
468 base::SimpleTestTickClock test_clock; | |
469 tab_manager.set_test_tick_clock(&test_clock); | |
470 | |
471 WebContents* tab1 = CreateWebContents(); | |
472 WebContents* tab2 = CreateWebContents(); | |
473 tabstrip.AppendWebContents(tab1, true); | |
474 tabstrip.AppendWebContents(tab2, false); | |
475 | |
476 tab_manager.GetWebContentsData(tab2)->SetLastInactiveTime( | |
477 test_clock.NowTicks()); | |
478 static_cast<content::MockRenderProcessHost*>(tab2->GetRenderProcessHost()) | |
479 ->set_is_process_backgrounded(true); | |
480 EXPECT_TRUE(tab2->GetRenderProcessHost()->IsProcessBackgrounded()); | |
481 | |
482 // Initially PurgeAndSuspend state should be NOT_PURGED. | |
483 EXPECT_FALSE(tab_manager.GetWebContentsData(tab2)->is_purged()); | |
484 tab_manager.GetWebContentsData(tab2)->set_time_to_purge( | |
485 base::TimeDelta::FromMinutes(1)); | |
486 test_clock.Advance(base::TimeDelta::FromMinutes(2)); | |
487 tab_manager.PurgeBackgroundedTabsIfNeeded(); | |
488 // Since tab2 is kept inactive and background for more than time-to-purge, | |
489 // tab2 should be purged. | |
490 EXPECT_TRUE(tab_manager.GetWebContentsData(tab2)->is_purged()); | |
491 | |
492 // Activate tab2. Tab2's PurgeAndSuspend state should be NOT_PURGED. | |
493 tabstrip.ActivateTabAt(1, true /* user_gesture */); | |
494 EXPECT_FALSE(tab_manager.GetWebContentsData(tab2)->is_purged()); | |
495 } | |
496 | |
497 } // namespace memory | |
OLD | NEW |