OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.tabmodel; | 5 package org.chromium.chrome.browser.tabmodel; |
6 | 6 |
| 7 import android.annotation.TargetApi; |
| 8 import android.os.Build; |
7 import android.test.suitebuilder.annotation.MediumTest; | 9 import android.test.suitebuilder.annotation.MediumTest; |
8 | 10 |
| 11 import org.chromium.base.ApplicationStatus; |
9 import org.chromium.base.ThreadUtils; | 12 import org.chromium.base.ThreadUtils; |
10 import org.chromium.base.test.util.FlakyTest; | 13 import org.chromium.base.test.util.FlakyTest; |
| 14 import org.chromium.base.test.util.MinAndroidSdkLevel; |
11 import org.chromium.base.test.util.Restriction; | 15 import org.chromium.base.test.util.Restriction; |
| 16 import org.chromium.base.test.util.UrlUtils; |
| 17 import org.chromium.chrome.browser.ChromeTabbedActivity; |
| 18 import org.chromium.chrome.browser.ChromeTabbedActivity2; |
| 19 import org.chromium.chrome.browser.multiwindow.MultiWindowUtilsTest; |
| 20 import org.chromium.chrome.browser.tab.EmptyTabObserver; |
12 import org.chromium.chrome.browser.tab.Tab; | 21 import org.chromium.chrome.browser.tab.Tab; |
13 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; | 22 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
14 import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; | 23 import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; |
15 import org.chromium.chrome.test.ChromeTabbedActivityTestBase; | 24 import org.chromium.chrome.test.ChromeTabbedActivityTestBase; |
16 import org.chromium.chrome.test.util.ChromeRestriction; | 25 import org.chromium.chrome.test.util.ChromeRestriction; |
17 import org.chromium.content.browser.test.util.CallbackHelper; | 26 import org.chromium.content.browser.test.util.CallbackHelper; |
| 27 import org.chromium.content.browser.test.util.Criteria; |
| 28 import org.chromium.content.browser.test.util.CriteriaHelper; |
18 import org.chromium.content_public.browser.LoadUrlParams; | 29 import org.chromium.content_public.browser.LoadUrlParams; |
19 | 30 |
| 31 import java.util.concurrent.Callable; |
20 import java.util.concurrent.TimeoutException; | 32 import java.util.concurrent.TimeoutException; |
21 | 33 |
22 /** | 34 /** |
23 * Tests undo and restoring of tabs in a {@link TabModel}. | 35 * Tests undo and restoring of tabs in a {@link TabModel}. |
24 */ | 36 */ |
25 public class UndoTabModelTest extends ChromeTabbedActivityTestBase { | 37 public class UndoTabModelTest extends ChromeTabbedActivityTestBase { |
26 private static final Tab[] EMPTY = new Tab[] { }; | 38 private static final Tab[] EMPTY = new Tab[] { }; |
| 39 private static final String TEST_URL_0 = UrlUtils.encodeHtmlDataUri("<html>t
est_url_0.</html>"); |
| 40 private static final String TEST_URL_1 = UrlUtils.encodeHtmlDataUri("<html>t
est_url_1.</html>"); |
27 | 41 |
28 @Override | 42 @Override |
29 public void startMainActivity() throws InterruptedException { | 43 public void startMainActivity() throws InterruptedException { |
30 startMainActivityOnBlankPage(); | 44 startMainActivityOnBlankPage(); |
31 } | 45 } |
32 | 46 |
33 private void checkState( | 47 private void checkState( |
34 final TabModel model, final Tab[] tabsList, final Tab selectedTab, | 48 final TabModel model, final Tab[] tabsList, final Tab selectedTab, |
35 final Tab[] closingTabs, final Tab[] fullTabsList, | 49 final Tab[] closingTabs, final Tab[] fullTabsList, |
36 final Tab fullSelectedTab) { | 50 final Tab fullSelectedTab) { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
68 private void createTabOnUiThread(final ChromeTabCreator tabCreator) { | 82 private void createTabOnUiThread(final ChromeTabCreator tabCreator) { |
69 ThreadUtils.runOnUiThreadBlocking(new Runnable() { | 83 ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
70 @Override | 84 @Override |
71 public void run() { | 85 public void run() { |
72 tabCreator.createNewTab(new LoadUrlParams("about:blank"), | 86 tabCreator.createNewTab(new LoadUrlParams("about:blank"), |
73 TabLaunchType.FROM_CHROME_UI, null); | 87 TabLaunchType.FROM_CHROME_UI, null); |
74 } | 88 } |
75 }); | 89 }); |
76 } | 90 } |
77 | 91 |
| 92 private void createFullyLoadedTabOnUiThread(final ChromeTabbedActivity activ
ity, |
| 93 final String url) { |
| 94 final CallbackHelper tabCallbackHelper = new CallbackHelper(); |
| 95 final TabLoadedObserver observer = new TabLoadedObserver(tabCallbackHelp
er); |
| 96 |
| 97 ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| 98 @Override |
| 99 public void run() { |
| 100 activity.getTabCreator(false).createNewTab(new LoadUrlParams(url
), |
| 101 TabLaunchType.FROM_CHROME_UI, null).addObserver(observer
); |
| 102 } |
| 103 }); |
| 104 |
| 105 // Must wait for the page to be fully loaded. |
| 106 try { |
| 107 tabCallbackHelper.waitForCallback(0); |
| 108 } catch (TimeoutException | InterruptedException e) { |
| 109 fail("Failed to load the tab."); |
| 110 } |
| 111 } |
| 112 |
78 private void selectTabOnUiThread(final TabModel model, final Tab tab) { | 113 private void selectTabOnUiThread(final TabModel model, final Tab tab) { |
79 ThreadUtils.runOnUiThreadBlocking(new Runnable() { | 114 ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
80 @Override | 115 @Override |
81 public void run() { | 116 public void run() { |
82 model.setIndex(model.indexOf(tab), TabSelectionType.FROM_USER); | 117 model.setIndex(model.indexOf(tab), TabSelectionType.FROM_USER); |
83 } | 118 } |
84 }); | 119 }); |
85 } | 120 } |
86 | 121 |
87 private void closeTabOnUiThread( | 122 private void closeTabOnUiThread( |
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
314 }); | 349 }); |
315 | 350 |
316 for (int i = 0; i < selector.getModels().size(); i++) { | 351 for (int i = 0; i < selector.getModels().size(); i++) { |
317 TabList tabs = selector.getModelAt(i).getComprehensiveModel(); | 352 TabList tabs = selector.getModelAt(i).getComprehensiveModel(); |
318 for (int j = 0; j < tabs.getCount(); j++) { | 353 for (int j = 0; j < tabs.getCount(); j++) { |
319 assertFalse(tabs.isClosurePending(tabs.getTabAt(j).getId())); | 354 assertFalse(tabs.isClosurePending(tabs.getTabAt(j).getId())); |
320 } | 355 } |
321 } | 356 } |
322 } | 357 } |
323 | 358 |
| 359 private void openMostRecentlyClosedTabOnUiThread(final TabModelSelector sele
ctor) { |
| 360 ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| 361 @Override |
| 362 public void run() { |
| 363 selector.getCurrentModel().openMostRecentlyClosedTab(); |
| 364 } |
| 365 }); |
| 366 } |
| 367 |
| 368 // Helper class that notifies when a page load is finished. |
| 369 private static class TabLoadedObserver extends EmptyTabObserver { |
| 370 private CallbackHelper mLoadedCallback; |
| 371 |
| 372 public TabLoadedObserver(CallbackHelper loadCallback) { |
| 373 super(); |
| 374 mLoadedCallback = loadCallback; |
| 375 } |
| 376 @Override |
| 377 public void onPageLoadFinished(Tab tab) { |
| 378 mLoadedCallback.notifyCalled(); |
| 379 } |
| 380 } |
| 381 |
| 382 // Helper class that notifies after the tab is closed, and a tab restore ser
vice entry has been |
| 383 // created in tab restore service. |
| 384 private static class TabClosedObserver extends EmptyTabModelObserver { |
| 385 private CallbackHelper mTabClosedCallback; |
| 386 |
| 387 public TabClosedObserver(CallbackHelper closedCallback) { |
| 388 mTabClosedCallback = closedCallback; |
| 389 } |
| 390 |
| 391 @Override |
| 392 public void didCloseTab(int tabId, boolean incognito) { |
| 393 mTabClosedCallback.notifyCalled(); |
| 394 } |
| 395 } |
| 396 |
324 /** | 397 /** |
325 * Test undo with a single tab with the following actions/expected states: | 398 * Test undo with a single tab with the following actions/expected states: |
326 * Action Model List Close List Compr
ehensive List | 399 * Action Model List Close List Compr
ehensive List |
327 * 1. Initial State [ 0s ] - [ 0s
] | 400 * 1. Initial State [ 0s ] - [ 0s
] |
328 * 2. CloseTab(0, allow undo) - [ 0 ] [ 0s
] | 401 * 2. CloseTab(0, allow undo) - [ 0 ] [ 0s
] |
329 * 3. CancelClose(0) [ 0s ] - [ 0s
] | 402 * 3. CancelClose(0) [ 0s ] - [ 0s
] |
330 * 4. CloseTab(0, allow undo) - [ 0 ] [ 0s
] | 403 * 4. CloseTab(0, allow undo) - [ 0 ] [ 0s
] |
331 * 5. CommitClose(0) - - - | 404 * 5. CommitClose(0) - - - |
332 * 6. CreateTab(0) [ 0s ] - [ 0s
] | 405 * 6. CreateTab(0) [ 0s ] - [ 0s
] |
333 * 7. CloseTab(0, allow undo) - [ 0 ] [ 0s
] | 406 * 7. CloseTab(0, allow undo) - [ 0 ] [ 0s
] |
(...skipping 1069 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1403 closeTabOnUiThread(model, tab0, true); | 1476 closeTabOnUiThread(model, tab0, true); |
1404 checkState(model, new Tab[] { tab1 }, tab1, EMPTY, fullList, tab1); | 1477 checkState(model, new Tab[] { tab1 }, tab1, EMPTY, fullList, tab1); |
1405 | 1478 |
1406 // 3. | 1479 // 3. |
1407 saveStateOnUiThread(selector); | 1480 saveStateOnUiThread(selector); |
1408 fullList = new Tab[] { tab1 }; | 1481 fullList = new Tab[] { tab1 }; |
1409 checkState(model, new Tab[] { tab1 }, tab1, EMPTY, fullList, tab1); | 1482 checkState(model, new Tab[] { tab1 }, tab1, EMPTY, fullList, tab1); |
1410 assertTrue(tab0.isClosing()); | 1483 assertTrue(tab0.isClosing()); |
1411 assertFalse(tab0.isInitialized()); | 1484 assertFalse(tab0.isInitialized()); |
1412 } | 1485 } |
| 1486 |
| 1487 /** |
| 1488 * Test opening recently closed tabs using the rewound list in Java. |
| 1489 * @throws InterruptedException |
| 1490 */ |
| 1491 @MediumTest |
| 1492 public void testOpenRecentlyClosedTab() throws InterruptedException { |
| 1493 TabModelSelector selector = getActivity().getTabModelSelector(); |
| 1494 TabModel model = selector.getModel(false); |
| 1495 ChromeTabCreator tabCreator = getActivity().getTabCreator(false); |
| 1496 |
| 1497 createTabOnUiThread(tabCreator); |
| 1498 |
| 1499 Tab tab0 = model.getTabAt(0); |
| 1500 Tab tab1 = model.getTabAt(1); |
| 1501 Tab[] allTabs = new Tab[]{tab0, tab1}; |
| 1502 |
| 1503 closeTabOnUiThread(model, tab1, true); |
| 1504 checkState(model, new Tab[]{tab0}, tab0, new Tab[]{tab1}, allTabs, tab0)
; |
| 1505 |
| 1506 // Ensure tab recovery, and reuse of {@link Tab} objects in Java. |
| 1507 openMostRecentlyClosedTabOnUiThread(selector); |
| 1508 checkState(model, allTabs, tab0, EMPTY, allTabs, tab0); |
| 1509 } |
| 1510 |
| 1511 /** |
| 1512 * Test opening recently closed tab using native tab restore service. |
| 1513 * @throws InterruptedException |
| 1514 */ |
| 1515 @MediumTest |
| 1516 public void testOpenRecentlyClosedTabNative() throws InterruptedException { |
| 1517 final TabModelSelector selector = getActivity().getTabModelSelector(); |
| 1518 final TabModel model = selector.getModel(false); |
| 1519 |
| 1520 // Create new tab and attach observer to listen to loaded event. |
| 1521 // Native can only successfully recover the tab after a page load has fi
nished and |
| 1522 // it has navigation history. |
| 1523 createFullyLoadedTabOnUiThread(getActivity(), TEST_URL_0); |
| 1524 |
| 1525 // Close the tab, and commit pending closure. |
| 1526 assertEquals(model.getCount(), 2); |
| 1527 closeTabOnUiThread(model, model.getTabAt(1), false); |
| 1528 assertEquals(1, model.getCount()); |
| 1529 Tab tab0 = model.getTabAt(0); |
| 1530 Tab[] tabs = new Tab[]{tab0}; |
| 1531 checkState(model, tabs, tab0, EMPTY, tabs, tab0); |
| 1532 |
| 1533 // Recover the page. |
| 1534 openMostRecentlyClosedTabOnUiThread(selector); |
| 1535 |
| 1536 assertEquals(2, model.getCount()); |
| 1537 tab0 = model.getTabAt(0); |
| 1538 Tab tab1 = model.getTabAt(1); |
| 1539 tabs = new Tab[]{tab0, tab1}; |
| 1540 assertEquals(TEST_URL_0, tab1.getUrl()); |
| 1541 checkState(model, tabs, tab0, EMPTY, tabs, tab0); |
| 1542 } |
| 1543 |
| 1544 /** |
| 1545 * Test opening recently closed tab when we have multiple windows. |
| 1546 * | Action | Result |
| 1547 * 1. Create second window. | |
| 1548 * 2. Open tab in window 1. | |
| 1549 * 3. Open tab in window 2. | |
| 1550 * 4. Close tab in window 1. | |
| 1551 * 5. Close tab in window 2. | |
| 1552 * 6. Restore tab. | Tab restored in window 2. |
| 1553 * 7. Restore tab. | Tab restored in window 1. |
| 1554 * @throws InterruptedException |
| 1555 */ |
| 1556 @MediumTest |
| 1557 @MinAndroidSdkLevel(24) |
| 1558 @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| 1559 public void testOpenRecentlyClosedTabMultiWindow() throws InterruptedExcepti
on { |
| 1560 final ChromeTabbedActivity2 secondActivity = |
| 1561 MultiWindowUtilsTest.createSecondChromeTabbedActivity(getActivit
y()); |
| 1562 |
| 1563 // Wait for the second window to be fully initialized. |
| 1564 CriteriaHelper.pollUiThread(new Criteria() { |
| 1565 @Override |
| 1566 public boolean isSatisfied() { |
| 1567 return secondActivity.getTabModelSelector().isTabStateInitialize
d(); |
| 1568 } |
| 1569 }); |
| 1570 // First window context. |
| 1571 final TabModelSelector firstSelector = getActivity().getTabModelSelector
(); |
| 1572 final TabModel firstModel = firstSelector.getModel(false); |
| 1573 |
| 1574 // Second window context. |
| 1575 final TabModel secondModel = secondActivity.getTabModelSelector().getMod
el(false); |
| 1576 |
| 1577 // Create tabs. |
| 1578 createFullyLoadedTabOnUiThread(getActivity(), TEST_URL_0); |
| 1579 createFullyLoadedTabOnUiThread(secondActivity, TEST_URL_1); |
| 1580 |
| 1581 assertEquals("Unexpected number of tabs in first window.", 2, firstModel
.getCount()); |
| 1582 assertEquals("Unexpected number of tabs in second window.", 2, secondMod
el.getCount()); |
| 1583 |
| 1584 // Close one tab in the first window. |
| 1585 closeTabOnUiThread(firstModel, firstModel.getTabAt(1), false); |
| 1586 assertEquals("Unexpected number of tabs in first window.", 1, firstModel
.getCount()); |
| 1587 assertEquals("Unexpected number of tabs in second window.", 2, secondMod
el.getCount()); |
| 1588 |
| 1589 // Close one tab in the second window. |
| 1590 closeTabOnUiThread(secondModel, secondModel.getTabAt(1), false); |
| 1591 assertEquals("Unexpected number of tabs in first window.", 1, firstModel
.getCount()); |
| 1592 assertEquals("Unexpected number of tabs in second window.", 1, secondMod
el.getCount()); |
| 1593 |
| 1594 // Restore one tab. |
| 1595 openMostRecentlyClosedTabOnUiThread(firstSelector); |
| 1596 assertEquals("Unexpected number of tabs in first window.", 1, firstModel
.getCount()); |
| 1597 assertEquals("Unexpected number of tabs in second window.", 2, secondMod
el.getCount()); |
| 1598 |
| 1599 // Restore one more tab. |
| 1600 openMostRecentlyClosedTabOnUiThread(firstSelector); |
| 1601 |
| 1602 // Check final states of both windows. |
| 1603 Tab firstModelTab = firstModel.getTabAt(0); |
| 1604 Tab secondModelTab = secondModel.getTabAt(0); |
| 1605 Tab[] firstWindowTabs = new Tab[]{firstModelTab, firstModel.getTabAt(1)}
; |
| 1606 Tab[] secondWindowTabs = new Tab[]{secondModelTab, secondModel.getTabAt(
1)}; |
| 1607 checkState(firstModel, firstWindowTabs, firstModelTab, EMPTY, firstWindo
wTabs, |
| 1608 firstModelTab); |
| 1609 checkState(secondModel, secondWindowTabs, secondModelTab, EMPTY, secondW
indowTabs, |
| 1610 secondModelTab); |
| 1611 assertEquals(TEST_URL_0, firstWindowTabs[1].getUrl()); |
| 1612 assertEquals(TEST_URL_1, secondWindowTabs[1].getUrl()); |
| 1613 |
| 1614 secondActivity.finishAndRemoveTask(); |
| 1615 } |
| 1616 |
| 1617 /** |
| 1618 * Test restoring closed tab from a closed window. |
| 1619 * | Action | Result |
| 1620 * 1. Create second window. | |
| 1621 * 2. Open tab in window 2. | |
| 1622 * 3. Close tab in window 2. | |
| 1623 * 4. Close second window. | |
| 1624 * 5. Restore tab. | Tab restored in first window. |
| 1625 * @throws InterruptedException |
| 1626 */ |
| 1627 @MediumTest |
| 1628 @MinAndroidSdkLevel(24) |
| 1629 @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| 1630 public void testOpenRecentlyClosedTabMultiWindowFallback() throws Interrupte
dException { |
| 1631 final ChromeTabbedActivity2 secondActivity = |
| 1632 MultiWindowUtilsTest.createSecondChromeTabbedActivity(getActivit
y()); |
| 1633 // Wait for the second window to be fully initialized. |
| 1634 CriteriaHelper.pollUiThread(new Criteria() { |
| 1635 @Override |
| 1636 public boolean isSatisfied() { |
| 1637 return secondActivity.getTabModelSelector().isTabStateInitialize
d(); |
| 1638 } |
| 1639 }); |
| 1640 |
| 1641 // First window context. |
| 1642 final TabModelSelector firstSelector = getActivity().getTabModelSelector
(); |
| 1643 final TabModel firstModel = firstSelector.getModel(false); |
| 1644 |
| 1645 // Second window context. |
| 1646 final TabModel secondModel = secondActivity.getTabModelSelector().getMod
el(false); |
| 1647 |
| 1648 // Create tab on second window. |
| 1649 createFullyLoadedTabOnUiThread(secondActivity, TEST_URL_1); |
| 1650 assertEquals("Window 2 should have 2 tab.", 2, secondModel.getCount()); |
| 1651 |
| 1652 // Close tab in second window, wait until tab restore service history is
created. |
| 1653 CallbackHelper closedCallback = new CallbackHelper(); |
| 1654 secondModel.addObserver(new TabClosedObserver(closedCallback)); |
| 1655 closeTabOnUiThread(secondModel, secondModel.getTabAt(1), false); |
| 1656 |
| 1657 try { |
| 1658 closedCallback.waitForCallback(0); |
| 1659 } catch (TimeoutException | InterruptedException e) { |
| 1660 fail("Failed to close the tab on the second window."); |
| 1661 } |
| 1662 |
| 1663 assertEquals("Window 2 should have 1 tab.", 1, secondModel.getCount()); |
| 1664 |
| 1665 // Closed the second window. Must wait until it's totally closed. |
| 1666 int numExpectedActivities = ApplicationStatus.getRunningActivities().siz
e() - 1; |
| 1667 secondActivity.finishAndRemoveTask(); |
| 1668 CriteriaHelper.pollUiThread(Criteria.equals(numExpectedActivities, new C
allable<Integer>() { |
| 1669 @Override |
| 1670 public Integer call() { |
| 1671 return ApplicationStatus.getRunningActivities().size(); |
| 1672 } |
| 1673 })); |
| 1674 assertEquals("Window 1 should have 1 tab.", 1, firstModel.getCount()); |
| 1675 |
| 1676 // Restore closed tab from second window. It should be created in first
window. |
| 1677 openMostRecentlyClosedTabOnUiThread(firstSelector); |
| 1678 assertEquals("Closed tab in second window should be restored in the firs
t window.", 2, |
| 1679 firstModel.getCount()); |
| 1680 Tab tab0 = firstModel.getTabAt(0); |
| 1681 Tab tab1 = firstModel.getTabAt(1); |
| 1682 Tab[] firstWindowTabs = new Tab[]{tab0, tab1}; |
| 1683 checkState(firstModel, firstWindowTabs, tab0, EMPTY, firstWindowTabs, ta
b0); |
| 1684 assertEquals(TEST_URL_1, tab1.getUrl()); |
| 1685 } |
1413 } | 1686 } |
OLD | NEW |