// Copyright 2020 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.site_settings;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import android.graphics.drawable.Drawable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
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.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.browsing_data.BrowsingDataBridge;
import org.chromium.chrome.browser.browsing_data.BrowsingDataType;
import org.chromium.chrome.browser.browsing_data.TimePeriod;
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.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.components.browsing_data.content.BrowsingDataInfo;
import org.chromium.components.browsing_data.content.BrowsingDataModel;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.ServerCertificate;
import org.chromium.url.GURL;
import org.chromium.url.Origin;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/** Tests for Chrome's SiteSettingsDelegate implementation. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({
ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1",
"ignore-certificate-errors"
})
@Batch(SiteSettingsTest.SITE_SETTINGS_BATCH_NAME)
public class ChromeSiteSettingsDelegateTest {
public static final String BROWSING_DATA_HOST = "browsing-data.com";
@ClassRule
public static ChromeTabbedActivityTestRule sActivityTestRule =
new ChromeTabbedActivityTestRule();
@Rule
public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
new BlankCTATabInitialStateRule(sActivityTestRule, false);
ChromeSiteSettingsDelegate mSiteSettingsDelegate;
@Before
public void setUp() throws Exception {
clearBrowsingData(BrowsingDataType.SITE_DATA, TimePeriod.LAST_HOUR);
}
// Tests that a fallback favicon is generated when a real one isn't found locally.
// This is a regression test for crbug.com/1077716.
@Test
@SmallTest
public void testFallbackFaviconLoads() throws TimeoutException {
ThreadUtils.runOnUiThreadBlocking(
() -> {
mSiteSettingsDelegate =
new ChromeSiteSettingsDelegate(
sActivityTestRule.getActivity(),
ProfileManager.getLastUsedRegularProfile());
});
// Hold the Bitmap in an array because it gets assigned to in a closure, and all captured
// variables have to be effectively final.
Drawable[] holder = new Drawable[1];
CallbackHelper helper = new CallbackHelper();
PostTask.postTask(
TaskTraits.UI_DEFAULT,
() -> {
mSiteSettingsDelegate.getFaviconImageForURL(
new GURL("http://url.with.no.favicon"),
favicon -> {
holder[0] = favicon;
helper.notifyCalled();
});
});
helper.waitForCallback(helper.getCallCount());
Drawable favicon = holder[0];
assertThat(favicon).isNotNull();
assertThat(favicon.getIntrinsicWidth()).isGreaterThan(0);
assertThat(favicon.getIntrinsicHeight()).isGreaterThan(0);
}
// Tests that getBrowsingDataInfo returns the correct sample test data in the hashmap.
@Test
@SmallTest
public void testGetBrowsingDataInfoCookie() throws TimeoutException {
setCookie(Scheme.HTTP, BROWSING_DATA_HOST, "'foo1=bar1'");
setCookie(Scheme.HTTPS, BROWSING_DATA_HOST, "'foo2=bar2'");
BrowsingDataModel[] browsingDataModel = {null};
Map<Origin, BrowsingDataInfo> result = getBrowsingDataInfo(browsingDataModel);
assertEquals(2, result.size());
// Ensure that the entry matches the set cookie.
var https_origin = Origin.create(new GURL("https://browsing-data.com"));
var http_origin = Origin.create(new GURL("http://browsing-data.com"));
var entries = result.entrySet().stream().collect(Collectors.toList());
assertEquals(https_origin, entries.get(0).getKey());
BrowsingDataInfo info = entries.get(0).getValue();
assertEquals(https_origin, info.getOrigin());
assertEquals(1, info.getCookieCount());
assertEquals(0, info.getStorageSize());
assertEquals(http_origin, entries.get(1).getKey());
}
// Tests that removeBrowsingData removes data correctly for a given host.
@Test
@SmallTest
public void testRemoveBrowsingData() throws TimeoutException {
setCookie(Scheme.HTTP, BROWSING_DATA_HOST, null);
// Validate the model is populated with one entry.
BrowsingDataModel[] browsingDataModel = {null};
Map<Origin, BrowsingDataInfo> result = getBrowsingDataInfo(browsingDataModel);
assertEquals(1, result.size());
CallbackHelper helper = new CallbackHelper();
// Remove browsing-data.com host data.
ThreadUtils.runOnUiThreadBlocking(
() -> {
browsingDataModel[0].removeBrowsingData(
/* host= */ BROWSING_DATA_HOST, helper::notifyCalled);
});
helper.waitForNext();
// Validate model is empty after removal.
result = getBrowsingDataInfo(browsingDataModel);
assertEquals(0, result.size());
}
private static void setCookie(Scheme scheme, String hostname, String data)
throws TimeoutException {
EmbeddedTestServer server =
scheme == Scheme.HTTPS
? EmbeddedTestServer.createAndStartHTTPSServer(
InstrumentationRegistry.getInstrumentation().getContext(),
ServerCertificate.CERT_OK)
: EmbeddedTestServer.createAndStartServer(
ApplicationProvider.getApplicationContext());
String url =
server.getURLWithHostName(
hostname, "/content/test/data/browsing_data/site_data.html");
Tab tab = sActivityTestRule.loadUrlInNewTab(url, /* incognito= */ false);
JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "setCookie(" + data + ")");
server.stopAndDestroyServer();
}
private enum Scheme {
HTTP,
HTTPS
}
private void clearBrowsingData(int dataType, int timePeriod) throws TimeoutException {
CallbackHelper helper = new CallbackHelper();
ThreadUtils.runOnUiThreadBlocking(
() -> {
BrowsingDataBridge.getForProfile(ProfileManager.getLastUsedRegularProfile())
.clearBrowsingData(
helper::notifyCalled, new int[] {dataType}, timePeriod);
});
helper.waitForCallback(0);
}
private Map<Origin, BrowsingDataInfo> getBrowsingDataInfo(BrowsingDataModel[] browsingDataModel)
throws TimeoutException {
CallbackHelper helper = new CallbackHelper();
// Run browsing data methods require running on UI thread.
ThreadUtils.runOnUiThreadBlocking(
() -> {
mSiteSettingsDelegate =
new ChromeSiteSettingsDelegate(
sActivityTestRule.getActivity(),
ProfileManager.getLastUsedRegularProfile());
mSiteSettingsDelegate.getBrowsingDataModel(
model -> {
browsingDataModel[0] = model;
helper.notifyCalled();
});
});
helper.waitForNext();
Map<Origin, BrowsingDataInfo> result =
browsingDataModel[0].getBrowsingDataInfo(
mSiteSettingsDelegate.getBrowserContextHandle(), false);
return result;
}
}