chromium/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/BrowsingDataBridgeTest.java

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.browsing_data;

import static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;

import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.UserActionTester;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabState;
import org.chromium.chrome.browser.tab.TabStateExtractor;
import org.chromium.chrome.browser.tab.WebContentsStateBridge;
import org.chromium.chrome.browser.tabmodel.TabClosureParams;
import org.chromium.chrome.browser.webapps.TestFetchStorageCallback;
import org.chromium.chrome.browser.webapps.WebappDataStorage;
import org.chromium.chrome.browser.webapps.WebappRegistry;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.browser.webapps.WebappTestHelper;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.NavigationEntry;
import org.chromium.content_public.browser.WebContents;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.util.TestWebServer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

/** Integration tests for ClearBrowsingDataPreferences. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Batch(Batch.PER_CLASS)
public class BrowsingDataBridgeTest {
    @ClassRule
    public static ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
            new BlankCTATabInitialStateRule(sActivityTestRule, false);

    private CallbackHelper mCallbackHelper;
    private BrowsingDataBridge.OnClearBrowsingDataListener mListener;
    private UserActionTester mActionTester;
    private EmbeddedTestServer mTestServer;

    @Before
    public void setUp() throws Exception {
        mCallbackHelper = new CallbackHelper();
        mListener = mCallbackHelper::notifyCalled;
        mTestServer = sActivityTestRule.getTestServer();
        mActionTester = new UserActionTester();
    }

    @After
    public void tearDown() {
        mActionTester.tearDown();
    }

    private BrowsingDataBridge getBrowsingDataBridge() {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> BrowsingDataBridge.getForProfile(ProfileManager.getLastUsedRegularProfile()));
    }

    /** Test no clear browsing data calls. */
    @Test
    @SmallTest
    public void testNoCalls() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(mListener, new int[] {}, TimePeriod.ALL_TIME);
                });
        mCallbackHelper.waitForCallback(0);
        assertThat(
                mActionTester.toString(),
                getActions(),
                Matchers.contains("ClearBrowsingData_Everything"));
    }

    /** Test cookies deletion. */
    @Test
    @SmallTest
    public void testCookiesDeleted() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {BrowsingDataType.SITE_DATA},
                                    TimePeriod.LAST_HOUR);
                });
        mCallbackHelper.waitForCallback(0);
        assertThat(
                mActionTester.toString(),
                getActions(),
                Matchers.containsInAnyOrder(
                        "ClearBrowsingData_LastHour",
                        "ClearBrowsingData_MaskContainsUnprotectedWeb",
                        "ClearBrowsingData_Cookies",
                        "ClearBrowsingData_SiteUsageData",
                        "ClearBrowsingData_ContentLicenses"));
    }

    /** Get ClearBrowsingData related actions, filter all other actions to avoid flakes. */
    private List<String> getActions() {
        List<String> actions = new ArrayList<>(mActionTester.getActions());
        Iterator<String> it = actions.iterator();
        while (it.hasNext()) {
            if (!it.next().startsWith("ClearBrowsingData_")) {
                it.remove();
            }
        }
        return actions;
    }

    /** Test history deletion. */
    @Test
    @SmallTest
    public void testHistoryDeleted() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {BrowsingDataType.HISTORY},
                                    TimePeriod.LAST_DAY);
                });
        mCallbackHelper.waitForCallback(0);
        assertThat(
                mActionTester.toString(),
                getActions(),
                Matchers.containsInAnyOrder(
                        "ClearBrowsingData_LastDay",
                        "ClearBrowsingData_MaskContainsUnprotectedWeb",
                        "ClearBrowsingData_History"));
    }

    /** Test deleting cache and content settings. */
    @Test
    @SmallTest
    public void testClearingSiteSettingsAndCache() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {
                                        BrowsingDataType.CACHE, BrowsingDataType.SITE_SETTINGS,
                                    },
                                    TimePeriod.FOUR_WEEKS);
                });
        mCallbackHelper.waitForCallback(0);
        assertThat(
                mActionTester.toString(),
                getActions(),
                Matchers.containsInAnyOrder(
                        "ClearBrowsingData_LastMonth",
                        "ClearBrowsingData_MaskContainsUnprotectedWeb",
                        "ClearBrowsingData_Cache",
                        "ClearBrowsingData_ShaderCache",
                        "ClearBrowsingData_ContentSettings"));
    }

    /** Test deleting cache and content settings with important sites. */
    @Test
    @SmallTest
    public void testClearingSiteSettingsAndCacheWithImportantSites() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingDataExcludingDomains(
                                    mListener,
                                    new int[] {
                                        BrowsingDataType.CACHE, BrowsingDataType.SITE_SETTINGS,
                                    },
                                    TimePeriod.FOUR_WEEKS,
                                    new String[] {"google.com"},
                                    new int[] {1},
                                    new String[0],
                                    new int[0]);
                });
        mCallbackHelper.waitForCallback(0);
        assertThat(
                mActionTester.toString(),
                getActions(),
                Matchers.containsInAnyOrder(
                        "ClearBrowsingData_LastMonth",
                        // ClearBrowsingData_MaskContainsUnprotectedWeb is logged
                        // twice because important storage is deleted separately.
                        "ClearBrowsingData_MaskContainsUnprotectedWeb",
                        "ClearBrowsingData_MaskContainsUnprotectedWeb",
                        "ClearBrowsingData_Cache",
                        "ClearBrowsingData_ShaderCache",
                        "ClearBrowsingData_ContentSettings"));
    }

    /** Test deleting all browsing data. (Except bookmarks, they are deleted in Java code) */
    @Test
    @SmallTest
    public void testClearingAll() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {
                                        BrowsingDataType.CACHE,
                                        BrowsingDataType.SITE_DATA,
                                        BrowsingDataType.FORM_DATA,
                                        BrowsingDataType.HISTORY,
                                        BrowsingDataType.PASSWORDS,
                                        BrowsingDataType.SITE_SETTINGS,
                                        BrowsingDataType.TABS
                                    },
                                    TimePeriod.LAST_WEEK);
                });
        mCallbackHelper.waitForCallback(0);
        assertThat(
                mActionTester.toString(),
                getActions(),
                Matchers.containsInAnyOrder(
                        "ClearBrowsingData_LastWeek",
                        "ClearBrowsingData_MaskContainsUnprotectedWeb",
                        "ClearBrowsingData_Cache",
                        "ClearBrowsingData_ShaderCache",
                        "ClearBrowsingData_Cookies",
                        "ClearBrowsingData_Autofill",
                        "ClearBrowsingData_History",
                        "ClearBrowsingData_Passwords",
                        "ClearBrowsingData_ContentSettings",
                        "ClearBrowsingData_SiteUsageData",
                        "ClearBrowsingData_ContentLicenses",
                        "ClearBrowsingData_Tabs"));
    }

    /** Tests navigation entries from frozen state are removed by history deletions. */
    @Test
    @MediumTest
    public void testFrozenNavigationDeletion() throws Exception {
        final String url1 = mTestServer.getURL("/chrome/test/data/browsing_data/a.html");
        final String url2 = mTestServer.getURL("/chrome/test/data/browsing_data/b.html");

        // Navigate to url1 and url2, close and recreate as frozen tab.
        Tab tab = sActivityTestRule.loadUrlInNewTab(url1);
        sActivityTestRule.loadUrl(url2);
        Tab[] frozen = new Tab[1];
        WebContents[] restored = new WebContents[1];
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    TabState state = TabStateExtractor.from(tab);
                    sActivityTestRule
                            .getActivity()
                            .getCurrentTabModel()
                            .closeTabs(TabClosureParams.closeTab(tab).allowUndo(false).build());
                    frozen[0] =
                            sActivityTestRule
                                    .getActivity()
                                    .getCurrentTabCreator()
                                    .createFrozenTab(state, tab.getId(), 1);
                    restored[0] =
                            WebContentsStateBridge.restoreContentsFromByteBuffer(
                                    TabStateExtractor.from(frozen[0]).contentsState, false);
                });

        // Check content of frozen state.
        NavigationController controller = restored[0].getNavigationController();
        assertEquals(1, controller.getLastCommittedEntryIndex());
        assertThat(getUrls(controller), Matchers.contains(url1, url2));
        assertNull(frozen[0].getWebContents());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {
                                        BrowsingDataType.HISTORY,
                                    },
                                    TimePeriod.LAST_WEEK);
                });

        mCallbackHelper.waitForCallback(0);

        // Check that frozen state was cleaned up.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    restored[0] =
                            WebContentsStateBridge.restoreContentsFromByteBuffer(
                                    TabStateExtractor.from(frozen[0]).contentsState, false);
                });

        controller = restored[0].getNavigationController();
        assertEquals(0, controller.getLastCommittedEntryIndex());
        assertThat(getUrls(controller), Matchers.contains(url2));
        assertNull(frozen[0].getWebContents());
    }

    /**
     * Tests that calling getContentsStateAsByteBuffer on a tab that has never committed a
     * navigation results in a null ByteBuffer. Regression test for https://crbug.com/1240138.
     */
    @Test
    @MediumTest
    public void testInitialNavigationEntryNotPersisted() throws Exception {
        TestWebServer webServer = TestWebServer.start();
        final String noContentUrl = webServer.setResponseWithNoContentStatus("/nocontent.html");
        Tab tab = sActivityTestRule.loadUrlInNewTab(noContentUrl);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    assertNull(
                            WebContentsStateBridge.getContentsStateAsByteBuffer(
                                    tab.getWebContents()));
                });
    }

    /** Tests navigation entries are removed by history deletions. */
    @Test
    @MediumTest
    public void testNavigationDeletion() throws Exception {
        final String url1 = mTestServer.getURL("/chrome/test/data/browsing_data/a.html");
        final String url2 = mTestServer.getURL("/chrome/test/data/browsing_data/b.html");

        // Navigate to url1 and url2.
        Tab tab = sActivityTestRule.loadUrlInNewTab(url1);
        sActivityTestRule.loadUrl(url2);
        NavigationController controller = tab.getWebContents().getNavigationController();
        assertTrue(tab.canGoBack());
        assertEquals(1, controller.getLastCommittedEntryIndex());
        assertThat(getUrls(controller), Matchers.contains(url1, url2));

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {
                                        BrowsingDataType.HISTORY,
                                    },
                                    TimePeriod.LAST_WEEK);
                });
        mCallbackHelper.waitForCallback(0);

        // Check navigation entries.
        assertFalse(tab.canGoBack());
        assertEquals(0, controller.getLastCommittedEntryIndex());
        assertThat(getUrls(controller), Matchers.contains(url2));
    }

    /** Tests that web apps are cleared when the "cookies and site data" option is selected. */
    @Test
    @MediumTest
    public void testClearingSiteDataClearsWebapps() throws Exception {
        TestFetchStorageCallback callback = new TestFetchStorageCallback();
        WebappRegistry.getInstance().register("first", callback);
        callback.waitForCallback(0);
        Assert.assertEquals(
                new HashSet<>(Arrays.asList("first")),
                WebappRegistry.getRegisteredWebappIdsForTesting());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {
                                        org.chromium.chrome.browser.browsing_data.BrowsingDataType
                                                .SITE_DATA,
                                    },
                                    TimePeriod.LAST_WEEK);
                });

        Assert.assertTrue(WebappRegistry.getRegisteredWebappIdsForTesting().isEmpty());
    }

    /**
     * Tests that web app scopes and last launch times are cleared when the "history" option is
     * selected. However, the web app is not removed from the registry.
     */
    @Test
    @MediumTest
    public void testClearingHistoryClearsWebappScopesAndLaunchTimes() throws Exception {
        BrowserServicesIntentDataProvider intentDataProvider =
                WebappTestHelper.createIntentDataProvider("id", "url");
        TestFetchStorageCallback callback = new TestFetchStorageCallback();
        WebappRegistry.getInstance().register("first", callback);
        callback.waitForCallback(0);
        callback.getStorage().updateFromWebappIntentDataProvider(intentDataProvider);

        Assert.assertEquals(
                new HashSet<>(Arrays.asList("first")),
                WebappRegistry.getRegisteredWebappIdsForTesting());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    getBrowsingDataBridge()
                            .clearBrowsingData(
                                    mListener,
                                    new int[] {
                                        org.chromium.chrome.browser.browsing_data.BrowsingDataType
                                                .HISTORY,
                                    },
                                    TimePeriod.LAST_WEEK);
                });
        Assert.assertEquals(
                new HashSet<>(Arrays.asList("first")),
                WebappRegistry.getRegisteredWebappIdsForTesting());

        // URL and scope should be empty, and last used time should be 0.
        WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage("first");
        Assert.assertEquals("", storage.getScope());
        Assert.assertEquals("", storage.getUrl());
        Assert.assertEquals(0, storage.getLastUsedTimeMs());
    }

    private List<String> getUrls(NavigationController controller) {
        List<String> urls = new ArrayList<>();
        int i = 0;
        while (true) {
            NavigationEntry entry = controller.getEntryAtIndex(i++);
            if (entry == null) return urls;
            urls.add(entry.getUrl().getSpec());
        }
    }
}