0

Migration from PageAnnotationService to Cacao

Also - previous integration path is still maintained.

Binary-Size: Increase is due to new protobufs for shopping projects.
Bug: 1196765
Change-Id: I4c0fec7c80b2ded95797a0c886943e15fd9dc498
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2810532
Commit-Queue: David Maunder <davidjm@chromium.org>
Reviewed-by: Sophie Chang <sophiechang@chromium.org>
Reviewed-by: Yusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#876447}
This commit is contained in:
David Maunder
2021-04-27 04:51:51 +00:00
committed by Chromium LUCI CQ
parent 271625d704
commit bbbb7436da
17 changed files with 1058 additions and 188 deletions

@@ -910,6 +910,7 @@ junit_binary("chrome_junit_tests") {
"//chrome/browser/signin/ui/android:junit", "//chrome/browser/signin/ui/android:junit",
"//chrome/browser/tab:java", "//chrome/browser/tab:java",
"//chrome/browser/tab:junit", "//chrome/browser/tab:junit",
"//chrome/browser/tab:optimization_guide_protos_java",
"//chrome/browser/tab_group:java", "//chrome/browser/tab_group:java",
"//chrome/browser/tab_group:junit", "//chrome/browser/tab_group:junit",
"//chrome/browser/tabmodel:factory_java", "//chrome/browser/tabmodel:factory_java",
@@ -1210,6 +1211,7 @@ android_library("chrome_test_java") {
"//chrome/browser/signin/ui/android:javatests", "//chrome/browser/signin/ui/android:javatests",
"//chrome/browser/tab:critical_persisted_tab_data_proto_java", "//chrome/browser/tab:critical_persisted_tab_data_proto_java",
"//chrome/browser/tab:java", "//chrome/browser/tab:java",
"//chrome/browser/tab:optimization_guide_protos_java",
"//chrome/browser/tab_group:java", "//chrome/browser/tab_group:java",
"//chrome/browser/tabmodel:java", "//chrome/browser/tabmodel:java",
"//chrome/browser/tabmodel/internal:java", "//chrome/browser/tabmodel/internal:java",

@@ -565,8 +565,11 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/tab/state/LoadCallbackHelper.java", "javatests/src/org/chromium/chrome/browser/tab/state/LoadCallbackHelper.java",
"javatests/src/org/chromium/chrome/browser/tab/state/PersistedTabDataTest.java", "javatests/src/org/chromium/chrome/browser/tab/state/PersistedTabDataTest.java",
"javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataLegacyTest.java", "javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataLegacyTest.java",
"javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataLegacyWithPASTest.java",
"javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java", "javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java",
"javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTestUtils.java", "javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTestUtils.java",
"javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataWithPASTest.java",
"javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataWithPASTestUtils.java",
"javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java", "javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java",
"javatests/src/org/chromium/chrome/browser/tabmodel/AsyncTabCreationParamsManagerTest.java", "javatests/src/org/chromium/chrome/browser/tabmodel/AsyncTabCreationParamsManagerTest.java",
"javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java", "javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java",

@@ -64,6 +64,8 @@ import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.protobuf.ByteString;
import org.junit.After; import org.junit.After;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
@@ -104,6 +106,9 @@ import org.chromium.chrome.browser.tab.TabCreationState;
import org.chromium.chrome.browser.tab.TabImpl; import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabLaunchType; import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tab.TabObserver; import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.proto.PriceTracking.BuyableProduct;
import org.chromium.chrome.browser.tab.proto.PriceTracking.PriceTrackingData;
import org.chromium.chrome.browser.tab.proto.PriceTracking.ProductPrice;
import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData; import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
import org.chromium.chrome.browser.tab.state.PersistedTabDataConfiguration; import org.chromium.chrome.browser.tab.state.PersistedTabDataConfiguration;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData; import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
@@ -128,6 +133,7 @@ import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
import org.chromium.components.feature_engagement.EventConstants; import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker; import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.optimization_guide.OptimizationGuideDecision; import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.CommonTypesProto.Any;
import org.chromium.components.search_engines.TemplateUrlService; import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationController; import org.chromium.content_public.browser.NavigationController;
@@ -147,7 +153,6 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* Tests for {@link TabListMediator}. * Tests for {@link TabListMediator}.
*/ */
@@ -181,10 +186,25 @@ public class TabListMediatorUnitTest {
private static final int TAB3_ID = 123; private static final int TAB3_ID = 123;
private static final int POSITION1 = 0; private static final int POSITION1 = 0;
private static final int POSITION2 = 1; private static final int POSITION2 = 1;
private static final String EMPTY_ENDPOINT_RESPONSE = "{}";
private static final String ENDPOINT_RESPONSE = private static final BuyableProduct BUYABLE_PRODUCT_PROTO_INITIAL =
"{\"representations\" : [{\"type\" : \"SHOPPING\", \"productTitle\" : \"Book of Pie\"," BuyableProduct.newBuilder()
+ "\"price\" : 123456789012345, \"currency\" : \"USD\"}]}"; .setCurrentPrice(createProductPrice(123456789012345L, "USD"))
.build();
private static ProductPrice createProductPrice(long amountMicros, String currencyCode) {
return ProductPrice.newBuilder()
.setCurrencyCode(currencyCode)
.setAmountMicros(amountMicros)
.build();
}
private static final PriceTrackingData PRICE_TRACKING_BUYABLE_PRODUCT_INITIAL =
PriceTrackingData.newBuilder().setBuyableProduct(BUYABLE_PRODUCT_PROTO_INITIAL).build();
private static final Any ANY_BUYABLE_PRODUCT_INITIAL =
Any.newBuilder()
.setValue(ByteString.copyFrom(
PRICE_TRACKING_BUYABLE_PRODUCT_INITIAL.toByteArray()))
.build();
private static final Any ANY_EMPTY = Any.newBuilder().build();
@IntDef({TabListMediatorType.TAB_SWITCHER, TabListMediatorType.TAB_STRIP, @IntDef({TabListMediatorType.TAB_SWITCHER, TabListMediatorType.TAB_STRIP,
TabListMediatorType.TAB_GRID_DIALOG}) TabListMediatorType.TAB_GRID_DIALOG})
@@ -294,7 +314,6 @@ public class TabListMediatorUnitTest {
mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock); mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
// Ensure native pointer is initialized // Ensure native pointer is initialized
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init(); doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE);
mTab1Domain = JUnitTestGURLs.getGURL(TAB1_URL).getHost().replace("www.", ""); mTab1Domain = JUnitTestGURLs.getGURL(TAB1_URL).getHost().replace("www.", "");
mTab2Domain = JUnitTestGURLs.getGURL(TAB2_URL).getHost().replace("www.", ""); mTab2Domain = JUnitTestGURLs.getGURL(TAB2_URL).getHost().replace("www.", "");
@@ -1899,10 +1918,12 @@ public class TabListMediatorUnitTest {
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean( PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.TRACK_PRICES_ON_TABS, priceTrackingEnabled); PriceTrackingUtilities.TRACK_PRICES_ON_TABS, priceTrackingEnabled);
Profile.setLastUsedProfileForTesting(mProfile); Profile.setLastUsedProfileForTesting(mProfile);
Map<String, String> responses = new HashMap<>(); Map<GURL, Any> responses = new HashMap<>();
responses.put(TAB1_URL, ENDPOINT_RESPONSE); GURL gurl1 = JUnitTestGURLs.getGURL(TAB1_URL);
responses.put(TAB2_URL, EMPTY_ENDPOINT_RESPONSE); GURL gurl2 = JUnitTestGURLs.getGURL(TAB2_URL);
mockEndpointResponse(responses); responses.put(gurl1, ANY_BUYABLE_PRODUCT_INITIAL);
responses.put(gurl2, ANY_EMPTY);
mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE, responses);
PersistedTabDataConfiguration.setUseTestConfig(true); PersistedTabDataConfiguration.setUseTestConfig(true);
initAndAssertAllProperties(mMediatorSpy); initAndAssertAllProperties(mMediatorSpy);
List<Tab> tabs = new ArrayList<>(); List<Tab> tabs = new ArrayList<>();
@@ -2783,19 +2804,22 @@ public class TabListMediatorUnitTest {
} }
} }
private void mockOptimizationGuideResponse(@OptimizationGuideDecision int decision) { private void mockOptimizationGuideResponse(
doAnswer(new Answer<Void>() { @OptimizationGuideDecision int decision, Map<GURL, Any> responses) {
@Override for (Map.Entry<GURL, Any> responseEntry : responses.entrySet()) {
public Void answer(InvocationOnMock invocation) { doAnswer(new Answer<Void>() {
OptimizationGuideCallback callback = @Override
(OptimizationGuideCallback) invocation.getArguments()[3]; public Void answer(InvocationOnMock invocation) {
callback.onOptimizationGuideDecision(decision, null); OptimizationGuideCallback callback =
return null; (OptimizationGuideCallback) invocation.getArguments()[3];
} callback.onOptimizationGuideDecision(decision, responseEntry.getValue());
}) return null;
.when(mOptimizationGuideBridgeJniMock) }
.canApplyOptimization( })
anyLong(), any(GURL.class), anyInt(), any(OptimizationGuideCallback.class)); .when(mOptimizationGuideBridgeJniMock)
.canApplyOptimization(anyLong(), eq(responseEntry.getKey()), anyInt(),
any(OptimizationGuideCallback.class));
}
} }
private void initWithThreeTabs() { private void initWithThreeTabs() {

@@ -121,6 +121,7 @@ public class ChromeCachedFlags {
ShoppingPersistedTabData.TIME_TO_LIVE_MS, ShoppingPersistedTabData.TIME_TO_LIVE_MS,
ShoppingPersistedTabData.DISPLAY_TIME_MS, ShoppingPersistedTabData.DISPLAY_TIME_MS,
ShoppingPersistedTabData.STALE_TAB_THRESHOLD_SECONDS, ShoppingPersistedTabData.STALE_TAB_THRESHOLD_SECONDS,
ShoppingPersistedTabData.PRICE_TRACKING_WITH_OPTIMIZATION_GUIDE,
StartSurfaceConfiguration.HOME_BUTTON_ON_GRID_TAB_SWITCHER, StartSurfaceConfiguration.HOME_BUTTON_ON_GRID_TAB_SWITCHER,
StartSurfaceConfiguration.NEW_SURFACE_FROM_HOME_BUTTON, StartSurfaceConfiguration.NEW_SURFACE_FROM_HOME_BUTTON,
StartSurfaceConfiguration.OMNIBOX_FOCUSED_ON_NEW_TAB, StartSurfaceConfiguration.OMNIBOX_FOCUSED_ON_NEW_TAB,

@@ -21,20 +21,16 @@ import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.UiThreadTest; import org.chromium.base.test.UiThreadTest;
import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.JniMocker; import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcher;
import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcherJni;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge; import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeJni; import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeJni;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsService;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsServiceFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeBrowserTestRule; import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.util.browser.Features; import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures; import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.optimization_guide.OptimizationGuideDecision; import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@@ -58,42 +54,29 @@ public class ShoppingPersistedTabDataLegacyTest {
@Rule @Rule
public TestRule mProcessor = new Features.InstrumentationProcessor(); public TestRule mProcessor = new Features.InstrumentationProcessor();
@Mock
protected EndpointFetcher.Natives mEndpointFetcherJniMock;
@Mock @Mock
protected OptimizationGuideBridge.Natives mOptimizationGuideBridgeJniMock; protected OptimizationGuideBridge.Natives mOptimizationGuideBridgeJniMock;
@Mock
protected Profile mProfileMock;
@Mock
protected PageAnnotationsServiceFactory mServiceFactoryMock;
@Mock
protected PageAnnotationsService mPageAnnotationsServiceMock;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mMocker.mock(EndpointFetcherJni.TEST_HOOKS, mEndpointFetcherJniMock);
mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock); mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
// Ensure native pointer is initialized // Ensure native pointer is initialized
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init(); doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse( ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, OptimizationGuideDecision.TRUE); mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.TRUE, null);
PersistedTabDataConfiguration.setUseTestConfig(true); PersistedTabDataConfiguration.setUseTestConfig(true);
Profile.setLastUsedProfileForTesting(mProfileMock);
doReturn(mPageAnnotationsServiceMock).when(mServiceFactoryMock).getForLastUsedProfile();
ShoppingPersistedTabData.sPageAnnotationsServiceFactory = mServiceFactoryMock;
} }
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags. @CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000"}) Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000/"
public void testShoppingPriceChange() { + "price_tracking_with_optimization_guide/true"})
public void
testShoppingPriceChange() {
shoppingPriceChange(ShoppingPersistedTabDataTestUtils.createTabOnUiThread( shoppingPriceChange(ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO)); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO));
@@ -102,8 +85,10 @@ public class ShoppingPersistedTabDataLegacyTest {
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags. @CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000"}) Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000/"
public void testShoppingPriceChangeExtraFetchAfterChange() { + "price_tracking_with_optimization_guide/true"})
public void
testShoppingPriceChangeExtraFetchAfterChange() {
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -111,8 +96,8 @@ public class ShoppingPersistedTabDataLegacyTest {
final Semaphore semaphore = new Semaphore(0); final Semaphore semaphore = new Semaphore(0);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 3); mOptimizationGuideBridgeJniMock, 3);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS, Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros()); shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS, Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
@@ -130,13 +115,15 @@ public class ShoppingPersistedTabDataLegacyTest {
private long shoppingPriceChange(Tab tab) { private long shoppingPriceChange(Tab tab) {
final Semaphore initialSemaphore = new Semaphore(0); final Semaphore initialSemaphore = new Semaphore(0);
final Semaphore updateSemaphore = new Semaphore(0); final Semaphore updateSemaphore = new Semaphore(0);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL); .BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 1); mOptimizationGuideBridgeJniMock, 1);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS, Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros()); shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UNITED_STATES_CURRENCY_CODE, Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UNITED_STATES_CURRENCY_CODE,
@@ -150,13 +137,15 @@ public class ShoppingPersistedTabDataLegacyTest {
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
long firstUpdateTime = ShoppingPersistedTabDataTestUtils.getTimeLastUpdatedOnUiThread(tab); long firstUpdateTime = ShoppingPersistedTabDataTestUtils.getTimeLastUpdatedOnUiThread(tab);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_PRICE_UPDATED); .BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (updatedShoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (updatedShoppingPersistedTabData) -> {
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 2); mOptimizationGuideBridgeJniMock, 2);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS, Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
updatedShoppingPersistedTabData.getPriceMicros()); updatedShoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS, Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
@@ -172,6 +161,8 @@ public class ShoppingPersistedTabDataLegacyTest {
@UiThreadTest @UiThreadTest
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testNoRefetch() { public void testNoRefetch() {
final Semaphore initialSemaphore = new Semaphore(0); final Semaphore initialSemaphore = new Semaphore(0);
final Semaphore updateSemaphore = new Semaphore(0); final Semaphore updateSemaphore = new Semaphore(0);
@@ -179,8 +170,10 @@ public class ShoppingPersistedTabDataLegacyTest {
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
// Mock annotations response. // Mock annotations response.
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL); .BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
@@ -197,10 +190,12 @@ public class ShoppingPersistedTabDataLegacyTest {
}); });
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 1); mOptimizationGuideBridgeJniMock, 1);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_PRICE_UPDATED); .BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -217,8 +212,8 @@ public class ShoppingPersistedTabDataLegacyTest {
ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore);
// PageAnnotationsService should not have been called a second time - because we haven't // PageAnnotationsService should not have been called a second time - because we haven't
// passed the time to live // passed the time to live
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 1); mOptimizationGuideBridgeJniMock, 1);
} }
@UiThreadTest @UiThreadTest

@@ -0,0 +1,231 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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.tab.state;
import static org.mockito.Mockito.doReturn;
import android.support.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.UiThreadTest;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcher;
import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcherJni;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeJni;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsService;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsServiceFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.concurrent.Semaphore;
/**
* Legacy test relating to {@link ShoppingPersistedTabData} and {@link PageAnnotationService}
*/
@RunWith(BaseJUnit4ClassRunner.class)
@EnableFeatures({ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"})
@CommandLineFlags.
Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "force-fieldtrials=Study/Group"})
public class ShoppingPersistedTabDataLegacyWithPASTest {
@Rule
public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
@Rule
public JniMocker mMocker = new JniMocker();
@Rule
public TestRule mProcessor = new Features.InstrumentationProcessor();
@Mock
protected EndpointFetcher.Natives mEndpointFetcherJniMock;
@Mock
protected OptimizationGuideBridge.Natives mOptimizationGuideBridgeJniMock;
@Mock
protected Profile mProfileMock;
@Mock
protected PageAnnotationsServiceFactory mServiceFactoryMock;
@Mock
protected PageAnnotationsService mPageAnnotationsServiceMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mMocker.mock(EndpointFetcherJni.TEST_HOOKS, mEndpointFetcherJniMock);
mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
// Ensure native pointer is initialized
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.TRUE, null);
PersistedTabDataConfiguration.setUseTestConfig(true);
Profile.setLastUsedProfileForTesting(mProfileMock);
doReturn(mPageAnnotationsServiceMock).when(mServiceFactoryMock).getForLastUsedProfile();
ShoppingPersistedTabData.sPageAnnotationsServiceFactory = mServiceFactoryMock;
}
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000/"
+ "price_tracking_with_optimization_guide/false"})
public void
testShoppingPriceChange() {
shoppingPriceChange(ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO));
}
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000/"
+ "price_tracking_with_optimization_guide/false"})
public void
testShoppingPriceChangeExtraFetchAfterChange() {
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
long mLastPriceChangeTimeMs = shoppingPriceChange(tab);
final Semaphore semaphore = new Semaphore(0);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
ShoppingPersistedTabDataWithPASTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 3);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
shoppingPersistedTabData.getPreviousPriceMicros());
Assert.assertEquals(mLastPriceChangeTimeMs,
shoppingPersistedTabData.getLastPriceChangeTimeMs());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UNITED_STATES_CURRENCY_CODE,
shoppingPersistedTabData.getCurrencyCode());
semaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
}
private long shoppingPriceChange(Tab tab) {
final Semaphore initialSemaphore = new Semaphore(0);
final Semaphore updateSemaphore = new Semaphore(0);
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
ShoppingPersistedTabDataWithPASTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UNITED_STATES_CURRENCY_CODE,
shoppingPersistedTabData.getCurrencyCode());
Assert.assertEquals(ShoppingPersistedTabData.NO_PRICE_KNOWN,
shoppingPersistedTabData.getPreviousPriceMicros());
Assert.assertEquals(ShoppingPersistedTabData.NO_TRANSITIONS_OCCURRED,
shoppingPersistedTabData.getLastPriceChangeTimeMs());
initialSemaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
long firstUpdateTime = ShoppingPersistedTabDataTestUtils.getTimeLastUpdatedOnUiThread(tab);
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (updatedShoppingPersistedTabData) -> {
ShoppingPersistedTabDataWithPASTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 2);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
updatedShoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
updatedShoppingPersistedTabData.getPreviousPriceMicros());
Assert.assertTrue(firstUpdateTime
< updatedShoppingPersistedTabData.getLastPriceChangeTimeMs());
updateSemaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore);
return ShoppingPersistedTabDataTestUtils.getTimeLastUpdatedOnUiThread(tab);
}
@UiThreadTest
@SmallTest
@Test
public void testNoRefetch() {
final Semaphore initialSemaphore = new Semaphore(0);
final Semaphore updateSemaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
// Mock annotations response.
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabData.NO_PRICE_KNOWN,
shoppingPersistedTabData.getPreviousPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UNITED_STATES_CURRENCY_CODE,
shoppingPersistedTabData.getCurrencyCode());
// By setting time to live to be a negative number, an update
// will be forced in the subsequent call
initialSemaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
ShoppingPersistedTabDataWithPASTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabData.NO_PRICE_KNOWN,
shoppingPersistedTabData.getPreviousPriceMicros());
// By setting time to live to be a negative number, an update
// will be forced in the subsequent call
updateSemaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore);
// PageAnnotationsService should not have been called a second time - because we haven't
// passed the time to live
ShoppingPersistedTabDataWithPASTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
}
}

@@ -28,8 +28,6 @@ import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge; import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeJni; import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeJni;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsService;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsServiceFactory;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.MockTab; import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
@@ -37,6 +35,7 @@ import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.util.browser.Features; import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures; import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.optimization_guide.OptimizationGuideDecision; import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@@ -68,12 +67,6 @@ public class ShoppingPersistedTabDataTest {
@Mock @Mock
protected Profile mProfileMock; protected Profile mProfileMock;
@Mock
protected PageAnnotationsServiceFactory mServiceFactoryMock;
@Mock
protected PageAnnotationsService mPageAnnotationsServiceMock;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
@@ -82,12 +75,11 @@ public class ShoppingPersistedTabDataTest {
// Ensure native pointer is initialized // Ensure native pointer is initialized
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init(); doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse( ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, OptimizationGuideDecision.TRUE); mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.TRUE, null);
PersistedTabDataConfiguration.setUseTestConfig(true); PersistedTabDataConfiguration.setUseTestConfig(true);
Profile.setLastUsedProfileForTesting(mProfileMock); Profile.setLastUsedProfileForTesting(mProfileMock);
doReturn(mPageAnnotationsServiceMock).when(mServiceFactoryMock).getForLastUsedProfile();
ShoppingPersistedTabData.sPageAnnotationsServiceFactory = mServiceFactoryMock;
} }
@UiThreadTest @UiThreadTest
@@ -114,11 +106,15 @@ public class ShoppingPersistedTabDataTest {
@SmallTest @SmallTest
@Test @Test
public void testShoppingBloomFilterNotShoppingWebsite() { public void testShoppingBloomFilterNotShoppingWebsite() {
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL); .BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse( ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, OptimizationGuideDecision.FALSE); mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.FALSE, null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -128,21 +124,26 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); }); tab, (shoppingPersistedTabData) -> { semaphore.release(); });
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 0); mOptimizationGuideBridgeJniMock, 0);
} }
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testShoppingBloomFilterShoppingWebsite() { public void testShoppingBloomFilterShoppingWebsite() {
for (@OptimizationGuideDecision int decision : for (@OptimizationGuideDecision int decision :
new int[] {OptimizationGuideDecision.TRUE, OptimizationGuideDecision.UNKNOWN}) { new int[] {OptimizationGuideDecision.TRUE, OptimizationGuideDecision.UNKNOWN}) {
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse( ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mPageAnnotationsServiceMock, mOptimizationGuideBridgeJniMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL); .BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse( ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, decision); mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(), decision,
null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -152,8 +153,8 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); }); tab, (shoppingPersistedTabData) -> { semaphore.release(); });
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 1); mOptimizationGuideBridgeJniMock, 1);
} }
} }
@@ -171,15 +172,17 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); }); tab, (shoppingPersistedTabData) -> { semaphore.release(); });
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 0); mOptimizationGuideBridgeJniMock, 0);
} }
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags. @CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400"}) Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400/"
public void test2DayTabWithStaleOverride1day() { + "price_tracking_with_optimization_guide/true"})
public void
test2DayTabWithStaleOverride1day() {
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -191,20 +194,26 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); }); tab, (shoppingPersistedTabData) -> { semaphore.release(); });
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 0); mOptimizationGuideBridgeJniMock, 0);
} }
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags. @CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400"}) Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400/"
public void testHalfDayTabWithStaleOverride1day() { + "price_tracking_with_optimization_guide/true"})
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, public void
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse testHalfDayTabWithStaleOverride1day() {
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL); .BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse( ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, OptimizationGuideDecision.TRUE); mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.TRUE, null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -216,24 +225,26 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); }); tab, (shoppingPersistedTabData) -> { semaphore.release(); });
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 1); mOptimizationGuideBridgeJniMock, 1);
} }
@UiThreadTest @UiThreadTest
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testNoRefetch() { public void testNoRefetch() {
final Semaphore initialSemaphore = new Semaphore(0); final Semaphore initialSemaphore = new Semaphore(0);
final Semaphore updateSemaphore = new Semaphore(0); final Semaphore updateSemaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
// Mock annotations response. ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, mOptimizationGuideBridgeJniMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL); .BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS, Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
@@ -248,10 +259,12 @@ public class ShoppingPersistedTabDataTest {
}); });
}); });
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 1); mOptimizationGuideBridgeJniMock, 1);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_PRICE_UPDATED); .BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -268,8 +281,8 @@ public class ShoppingPersistedTabDataTest {
ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore); ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore);
// EndpointFetcher should not have been called a second time - because we haven't passed the // EndpointFetcher should not have been called a second time - because we haven't passed the
// time to live // time to live
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled( ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mPageAnnotationsServiceMock, 1); mOptimizationGuideBridgeJniMock, 1);
} }
@UiThreadTest @UiThreadTest
@@ -416,13 +429,17 @@ public class ShoppingPersistedTabDataTest {
@UiThreadTest @UiThreadTest
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testSPTDSavingEnabledUponSuccessfulResponse() { public void testSPTDSavingEnabledUponSuccessfulResponse() {
final Semaphore semaphore = new Semaphore(0); final Semaphore semaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL); .BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -436,13 +453,17 @@ public class ShoppingPersistedTabDataTest {
@UiThreadTest @UiThreadTest
@SmallTest @SmallTest
@Test @Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testSPTDSavingEnabledUponSuccessfulProductUpdateResponse() { public void testSPTDSavingEnabledUponSuccessfulProductUpdateResponse() {
final Semaphore semaphore = new Semaphore(0); final Semaphore semaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE); .BUYABLE_PRODUCT_AND_PRODUCT_UPDATE);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -461,9 +482,31 @@ public class ShoppingPersistedTabDataTest {
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread( Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID, ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO); ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock, ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse mOptimizationGuideBridgeJniMock,
.BUYABLE_PRODUCT_EMPTY); HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse.BUYABLE_PRODUCT_EMPTY);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertNull(shoppingPersistedTabData);
semaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
}
@UiThreadTest
@SmallTest
@Test
public void testSPTDNullOptimizationGuideFalse() {
final Semaphore semaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse.NONE);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertNull(shoppingPersistedTabData); Assert.assertNull(shoppingPersistedTabData);

@@ -8,11 +8,15 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.protobuf.ByteString;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@@ -21,39 +25,42 @@ import org.chromium.base.Callback;
import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcher; import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcher;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge; import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge.OptimizationGuideCallback; import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge.OptimizationGuideCallback;
import org.chromium.chrome.browser.page_annotations.BuyableProductPageAnnotation;
import org.chromium.chrome.browser.page_annotations.PageAnnotation;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsService;
import org.chromium.chrome.browser.page_annotations.ProductPriceUpdatePageAnnotation;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.MockTab; import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.proto.PriceTracking.BuyableProduct;
import org.chromium.chrome.browser.tab.proto.PriceTracking.PriceTrackingData;
import org.chromium.chrome.browser.tab.proto.PriceTracking.ProductPrice;
import org.chromium.chrome.browser.tab.proto.PriceTracking.ProductPriceUpdate;
import org.chromium.components.optimization_guide.OptimizationGuideDecision; import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.CommonTypesProto.Any;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.url.GURL; import org.chromium.url.GURL;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
* Helper class for {@link ShoppingPersistedTabDataTest} & {@link * Helper class for {@link ShoppingPersistedTabDataTest} & {@link
* ShoppingPersistedTabDataLegacyTest}. * ShoppingPersistedTabDataLegacyTest}.
*/ */
public abstract class ShoppingPersistedTabDataTestUtils { public abstract class ShoppingPersistedTabDataTestUtils {
@IntDef({MockPageAnnotationsResponse.BUYABLE_PRODUCT_INITIAL, @IntDef({MockPriceTrackingResponse.BUYABLE_PRODUCT_INITIAL,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_PRICE_UPDATED, MockPriceTrackingResponse.BUYABLE_PRODUCT_PRICE_UPDATED,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE, MockPriceTrackingResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE,
MockPageAnnotationsResponse.PRODUCT_PRICE_UPDATE, MockPriceTrackingResponse.PRODUCT_PRICE_UPDATE,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_EMPTY}) MockPriceTrackingResponse.BUYABLE_PRODUCT_EMPTY, MockPriceTrackingResponse.NONE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@interface MockPageAnnotationsResponse { @interface MockPriceTrackingResponse {
int BUYABLE_PRODUCT_INITIAL = 0; int BUYABLE_PRODUCT_INITIAL = 0;
int BUYABLE_PRODUCT_PRICE_UPDATED = 1; int BUYABLE_PRODUCT_PRICE_UPDATED = 1;
int BUYABLE_PRODUCT_AND_PRODUCT_UPDATE = 2; int BUYABLE_PRODUCT_AND_PRODUCT_UPDATE = 2;
int PRODUCT_PRICE_UPDATE = 3; int PRODUCT_PRICE_UPDATE = 3;
int BUYABLE_PRODUCT_EMPTY = 4; int BUYABLE_PRODUCT_EMPTY = 4;
int NONE = 5;
} }
static final long PRICE_MICROS = 123456789012345L; static final long PRICE_MICROS = 123456789012345L;
@@ -69,6 +76,72 @@ public abstract class ShoppingPersistedTabDataTestUtils {
static final boolean IS_INCOGNITO = false; static final boolean IS_INCOGNITO = false;
static final String FAKE_OFFER_ID = "100"; static final String FAKE_OFFER_ID = "100";
static final BuyableProduct BUYABLE_PRODUCT_PROTO_INITIAL =
BuyableProduct.newBuilder()
.setCurrentPrice(createProductPrice(PRICE_MICROS, UNITED_STATES_CURRENCY_CODE))
.build();
static final BuyableProduct BUYABLE_PRODUCT_PROTO_PRICE_UPDATED =
BuyableProduct.newBuilder()
.setCurrentPrice(
createProductPrice(UPDATED_PRICE_MICROS, UNITED_STATES_CURRENCY_CODE))
.build();
static final ProductPriceUpdate PRODUCT_UPDATE_PROTO =
ProductPriceUpdate.newBuilder()
.setOldPrice(createProductPrice(PRICE_MICROS, UNITED_STATES_CURRENCY_CODE))
.setNewPrice(
createProductPrice(UPDATED_PRICE_MICROS, UNITED_STATES_CURRENCY_CODE))
.build();
static final PriceTrackingData PRICE_TRACKING_BUYABLE_PRODUCT_INITIAL =
PriceTrackingData.newBuilder().setBuyableProduct(BUYABLE_PRODUCT_PROTO_INITIAL).build();
static final Any ANY_BUYABLE_PRODUCT_INITIAL =
Any.newBuilder()
.setValue(ByteString.copyFrom(
PRICE_TRACKING_BUYABLE_PRODUCT_INITIAL.toByteArray()))
.build();
static final PriceTrackingData PRICE_TRACKING_BUYABLE_PRODUCT_UPDATE =
PriceTrackingData.newBuilder()
.setBuyableProduct(BUYABLE_PRODUCT_PROTO_PRICE_UPDATED)
.build();
static final Any ANY_BUYABLE_PRODUCT_UPDATE =
Any.newBuilder()
.setValue(ByteString.copyFrom(
PRICE_TRACKING_BUYABLE_PRODUCT_UPDATE.toByteArray()))
.build();
static final PriceTrackingData PRICE_TRACKING_BUYABLE_PRODUCT_AND_PRODUCT_UPDATE =
PriceTrackingData.newBuilder()
.setBuyableProduct(BUYABLE_PRODUCT_PROTO_INITIAL)
.setProductUpdate(PRODUCT_UPDATE_PROTO)
.build();
static final Any ANY_PRICE_TRACKING_BUYABLE_PRODUCT_AND_PRODUCT_UPDATE =
Any.newBuilder()
.setValue(ByteString.copyFrom(
PRICE_TRACKING_BUYABLE_PRODUCT_AND_PRODUCT_UPDATE.toByteArray()))
.build();
static final PriceTrackingData PRICE_TRACKING_PRODUCT_UPDATE =
PriceTrackingData.newBuilder().setProductUpdate(PRODUCT_UPDATE_PROTO).build();
static final Any ANY_PRICE_TRACKING_PRODUCT_UPDATE =
Any.newBuilder()
.setValue(ByteString.copyFrom(PRICE_TRACKING_PRODUCT_UPDATE.toByteArray()))
.build();
static final PriceTrackingData PRICE_TRACKING_EMPTY = PriceTrackingData.newBuilder().build();
static final Any ANY_PRICE_TRACKING_EMPTY =
Any.newBuilder()
.setValue(ByteString.copyFrom(PRICE_TRACKING_EMPTY.toByteArray()))
.build();
static final Any ANY_EMPTY = Any.newBuilder().build();
static ProductPrice createProductPrice(long amountMicros, String currencyCode) {
return ProductPrice.newBuilder()
.setCurrencyCode(currencyCode)
.setAmountMicros(amountMicros)
.build();
}
static ShoppingPersistedTabData createShoppingPersistedTabDataWithDefaults() { static ShoppingPersistedTabData createShoppingPersistedTabDataWithDefaults() {
ShoppingPersistedTabData shoppingPersistedTabData = ShoppingPersistedTabData shoppingPersistedTabData =
new ShoppingPersistedTabData(createTabOnUiThread(TAB_ID, IS_INCOGNITO)); new ShoppingPersistedTabData(createTabOnUiThread(TAB_ID, IS_INCOGNITO));
@@ -113,13 +186,56 @@ public abstract class ShoppingPersistedTabDataTestUtils {
} }
static void mockOptimizationGuideResponse(OptimizationGuideBridge.Natives optimizationGuideJni, static void mockOptimizationGuideResponse(OptimizationGuideBridge.Natives optimizationGuideJni,
@OptimizationGuideDecision int decision) { int optimizationType, @OptimizationGuideDecision int decision, @Nullable Any metadata) {
doAnswer(new Answer<Void>() { doAnswer(new Answer<Void>() {
@Override @Override
public Void answer(InvocationOnMock invocation) { public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback = OptimizationGuideCallback callback =
(OptimizationGuideCallback) invocation.getArguments()[3]; (OptimizationGuideCallback) invocation.getArguments()[3];
callback.onOptimizationGuideDecision(decision, null); callback.onOptimizationGuideDecision(decision, metadata);
return null;
}
})
.when(optimizationGuideJni)
.canApplyOptimization(anyLong(), any(GURL.class), eq(optimizationType),
any(OptimizationGuideCallback.class));
}
static void mockOptimizationGuideResponse(OptimizationGuideBridge.Natives optimizationGuideJni,
int optimizationType, @MockPriceTrackingResponse int expectedResponse) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback =
(OptimizationGuideCallback) invocation.getArguments()[3];
switch (expectedResponse) {
case MockPriceTrackingResponse.BUYABLE_PRODUCT_INITIAL:
callback.onOptimizationGuideDecision(
OptimizationGuideDecision.TRUE, ANY_BUYABLE_PRODUCT_INITIAL);
break;
case MockPriceTrackingResponse.BUYABLE_PRODUCT_PRICE_UPDATED:
callback.onOptimizationGuideDecision(
OptimizationGuideDecision.TRUE, ANY_BUYABLE_PRODUCT_UPDATE);
break;
case MockPriceTrackingResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE:
callback.onOptimizationGuideDecision(OptimizationGuideDecision.TRUE,
ANY_PRICE_TRACKING_BUYABLE_PRODUCT_AND_PRODUCT_UPDATE);
break;
case MockPriceTrackingResponse.PRODUCT_PRICE_UPDATE:
callback.onOptimizationGuideDecision(
OptimizationGuideDecision.TRUE, ANY_PRICE_TRACKING_PRODUCT_UPDATE);
break;
case MockPriceTrackingResponse.BUYABLE_PRODUCT_EMPTY:
callback.onOptimizationGuideDecision(
OptimizationGuideDecision.TRUE, ANY_PRICE_TRACKING_EMPTY);
break;
case MockPriceTrackingResponse.NONE:
callback.onOptimizationGuideDecision(
OptimizationGuideDecision.FALSE, ANY_EMPTY);
break;
default:
break;
}
return null; return null;
} }
}) })
@@ -128,55 +244,17 @@ public abstract class ShoppingPersistedTabDataTestUtils {
anyLong(), any(GURL.class), anyInt(), any(OptimizationGuideCallback.class)); anyLong(), any(GURL.class), anyInt(), any(OptimizationGuideCallback.class));
} }
static void mockPageAnnotationsResponse(PageAnnotationsService pageAnnotationsService,
@MockPageAnnotationsResponse int expectedResponse) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
Callback callback = (Callback) invocation.getArguments()[1];
callback.onResult(new LinkedList<PageAnnotation>() {
{
switch (expectedResponse) {
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_INITIAL:
add(new BuyableProductPageAnnotation(
PRICE_MICROS, UNITED_STATES_CURRENCY_CODE, FAKE_OFFER_ID));
break;
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_PRICE_UPDATED:
add(new BuyableProductPageAnnotation(UPDATED_PRICE_MICROS,
UNITED_STATES_CURRENCY_CODE, FAKE_OFFER_ID));
break;
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE:
add(new BuyableProductPageAnnotation(
PRICE_MICROS, UNITED_STATES_CURRENCY_CODE, FAKE_OFFER_ID));
add(new ProductPriceUpdatePageAnnotation(PRICE_MICROS,
UPDATED_PRICE_MICROS, UNITED_STATES_CURRENCY_CODE));
break;
case MockPageAnnotationsResponse.PRODUCT_PRICE_UPDATE:
add(new ProductPriceUpdatePageAnnotation(PRICE_MICROS,
UPDATED_PRICE_MICROS, UNITED_STATES_CURRENCY_CODE));
break;
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_EMPTY:
default:
break;
}
}
});
return null;
}
})
.when(pageAnnotationsService)
.getAnnotations(any(GURL.class), any(Callback.class));
}
static void verifyEndpointFetcherCalled(EndpointFetcher.Natives endpointFetcher, int numTimes) { static void verifyEndpointFetcherCalled(EndpointFetcher.Natives endpointFetcher, int numTimes) {
verify(endpointFetcher, times(numTimes)) verify(endpointFetcher, times(numTimes))
.nativeFetchChromeAPIKey(any(Profile.class), anyString(), anyString(), anyString(), .nativeFetchChromeAPIKey(any(Profile.class), anyString(), anyString(), anyString(),
anyString(), anyLong(), any(String[].class), any(Callback.class)); anyString(), anyLong(), any(String[].class), any(Callback.class));
} }
static void verifyGetPageAnnotationsCalled( static void verifyPriceTrackingOptimizationTypeCalled(
PageAnnotationsService pageAnnotationsService, int numTimes) { OptimizationGuideBridge.Natives optimizationGuideJni, int numTimes) {
verify(pageAnnotationsService, times(numTimes)) verify(optimizationGuideJni, times(numTimes))
.getAnnotations(any(GURL.class), any(Callback.class)); .canApplyOptimization(anyLong(), any(GURL.class),
eq(HintsProto.OptimizationType.PRICE_TRACKING.getNumber()),
any(OptimizationGuideCallback.class));
} }
} }

@@ -0,0 +1,187 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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.tab.state;
import static org.mockito.Mockito.doReturn;
import android.support.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.UiThreadTest;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcher;
import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcherJni;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeJni;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsService;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsServiceFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.concurrent.Semaphore;
/**
* Test relating to {@link ShoppingPersistedTabData} and {@link PageAnnotationService}
*/
@RunWith(BaseJUnit4ClassRunner.class)
@EnableFeatures({ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"})
@CommandLineFlags.
Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "force-fieldtrials=Study/Group"})
public class ShoppingPersistedTabDataWithPASTest {
@Rule
public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
@Rule
public JniMocker mMocker = new JniMocker();
@Rule
public TestRule mProcessor = new Features.InstrumentationProcessor();
@Mock
protected EndpointFetcher.Natives mEndpointFetcherJniMock;
@Mock
protected OptimizationGuideBridge.Natives mOptimizationGuideBridgeJniMock;
@Mock
protected Profile mProfileMock;
@Mock
protected PageAnnotationsServiceFactory mServiceFactoryMock;
@Mock
protected PageAnnotationsService mPageAnnotationsServiceMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mMocker.mock(EndpointFetcherJni.TEST_HOOKS, mEndpointFetcherJniMock);
mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
// Ensure native pointer is initialized
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.TRUE, null);
PersistedTabDataConfiguration.setUseTestConfig(true);
Profile.setLastUsedProfileForTesting(mProfileMock);
doReturn(mPageAnnotationsServiceMock).when(mServiceFactoryMock).getForLastUsedProfile();
ShoppingPersistedTabData.sPageAnnotationsServiceFactory = mServiceFactoryMock;
}
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/false"})
public void testShoppingBloomFilterNotShoppingWebsite() {
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.FALSE, null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
Semaphore semaphore = new Semaphore(0);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(
tab, (shoppingPersistedTabData) -> { semaphore.release(); });
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataWithPASTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 0);
}
@SmallTest
@Test
public void testShoppingBloomFilterShoppingWebsite() {
for (@OptimizationGuideDecision int decision :
new int[] {OptimizationGuideDecision.TRUE, OptimizationGuideDecision.UNKNOWN}) {
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(), decision,
null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
Semaphore semaphore = new Semaphore(0);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(
tab, (shoppingPersistedTabData) -> { semaphore.release(); });
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataWithPASTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
}
}
@UiThreadTest
@SmallTest
@Test
public void testSPTDSavingEnabledUponSuccessfulProductUpdateResponse() {
final Semaphore semaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertTrue(shoppingPersistedTabData.mIsTabSaveEnabledSupplier.get());
semaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
}
@UiThreadTest
@SmallTest
@Test
public void testSPTDNullUponUnsuccessfulResponse() {
final Semaphore semaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataWithPASTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataWithPASTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_EMPTY);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertNull(shoppingPersistedTabData);
semaphore.release();
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
}
}

@@ -0,0 +1,98 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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.tab.state;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import androidx.annotation.IntDef;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.page_annotations.BuyableProductPageAnnotation;
import org.chromium.chrome.browser.page_annotations.PageAnnotation;
import org.chromium.chrome.browser.page_annotations.PageAnnotationsService;
import org.chromium.chrome.browser.page_annotations.ProductPriceUpdatePageAnnotation;
import org.chromium.url.GURL;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
/**
* Helper class for {@link ShoppingPersistedTabDataWithPASTest} & {@link
* ShoppingPersistedTabDataLegacyWithPASTest}.
*/
public abstract class ShoppingPersistedTabDataWithPASTestUtils {
@IntDef({MockPageAnnotationsResponse.BUYABLE_PRODUCT_INITIAL,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_PRICE_UPDATED,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE,
MockPageAnnotationsResponse.PRODUCT_PRICE_UPDATE,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_EMPTY})
@Retention(RetentionPolicy.SOURCE)
@interface MockPageAnnotationsResponse {
int BUYABLE_PRODUCT_INITIAL = 0;
int BUYABLE_PRODUCT_PRICE_UPDATED = 1;
int BUYABLE_PRODUCT_AND_PRODUCT_UPDATE = 2;
int PRODUCT_PRICE_UPDATE = 3;
int BUYABLE_PRODUCT_EMPTY = 4;
}
static final long PRICE_MICROS = 123456789012345L;
static final long UPDATED_PRICE_MICROS = 287000000L;
static final long HIGH_PRICE_MICROS = 141000000L;
static final long LOW_PRICE_MICROS = 100000000L;
static final String UNITED_STATES_CURRENCY_CODE = "USD";
static final String FAKE_OFFER_ID = "100";
static void mockPageAnnotationsResponse(PageAnnotationsService pageAnnotationsService,
@MockPageAnnotationsResponse int expectedResponse) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
Callback callback = (Callback) invocation.getArguments()[1];
callback.onResult(new LinkedList<PageAnnotation>() {
{
switch (expectedResponse) {
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_INITIAL:
add(new BuyableProductPageAnnotation(
PRICE_MICROS, UNITED_STATES_CURRENCY_CODE, FAKE_OFFER_ID));
break;
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_PRICE_UPDATED:
add(new BuyableProductPageAnnotation(UPDATED_PRICE_MICROS,
UNITED_STATES_CURRENCY_CODE, FAKE_OFFER_ID));
break;
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE:
add(new BuyableProductPageAnnotation(
PRICE_MICROS, UNITED_STATES_CURRENCY_CODE, FAKE_OFFER_ID));
add(new ProductPriceUpdatePageAnnotation(PRICE_MICROS,
UPDATED_PRICE_MICROS, UNITED_STATES_CURRENCY_CODE));
break;
case MockPageAnnotationsResponse.PRODUCT_PRICE_UPDATE:
add(new ProductPriceUpdatePageAnnotation(PRICE_MICROS,
UPDATED_PRICE_MICROS, UNITED_STATES_CURRENCY_CODE));
break;
case MockPageAnnotationsResponse.BUYABLE_PRODUCT_EMPTY:
default:
break;
}
}
});
return null;
}
})
.when(pageAnnotationsService)
.getAnnotations(any(GURL.class), any(Callback.class));
}
static void verifyGetPageAnnotationsCalled(
PageAnnotationsService pageAnnotationsService, int numTimes) {
verify(pageAnnotationsService, times(numTimes))
.getAnnotations(any(GURL.class), any(Callback.class));
}
}

@@ -1732,7 +1732,13 @@ const FeatureEntry::FeatureParam kTabGridLayoutAndroid_SearchChip[] = {
{"enable_search_term_chip", "true"}}; {"enable_search_term_chip", "true"}};
const FeatureEntry::FeatureParam kTabGridLayoutAndroid_PriceAlerts[] = { const FeatureEntry::FeatureParam kTabGridLayoutAndroid_PriceAlerts[] = {
{"enable_price_tracking", "true"}}; {"enable_price_tracking", "true"},
{"price_tracking_with_optimization_guide", "false"}};
const FeatureEntry::FeatureParam
kTabGridLayoutAndroid_PriceAlerts_WithOptimizationGuide[] = {
{"enable_price_tracking", "true"},
{"price_tracking_with_optimization_guide", "true"}};
const FeatureEntry::FeatureParam kTabGridLayoutAndroid_TabGroupAutoCreation[] = const FeatureEntry::FeatureParam kTabGridLayoutAndroid_TabGroupAutoCreation[] =
{{"enable_tab_group_auto_creation", "false"}}; {{"enable_tab_group_auto_creation", "false"}};
@@ -1751,6 +1757,10 @@ const FeatureEntry::FeatureVariation kTabGridLayoutAndroidVariations[] = {
base::size(kTabGridLayoutAndroid_SearchChip), nullptr}, base::size(kTabGridLayoutAndroid_SearchChip), nullptr},
{"Price alerts", kTabGridLayoutAndroid_PriceAlerts, {"Price alerts", kTabGridLayoutAndroid_PriceAlerts,
base::size(kTabGridLayoutAndroid_PriceAlerts), nullptr}, base::size(kTabGridLayoutAndroid_PriceAlerts), nullptr},
{"Price alerts with OptimizationGuide",
kTabGridLayoutAndroid_PriceAlerts_WithOptimizationGuide,
base::size(kTabGridLayoutAndroid_PriceAlerts_WithOptimizationGuide),
nullptr},
{"Without auto group", kTabGridLayoutAndroid_TabGroupAutoCreation, {"Without auto group", kTabGridLayoutAndroid_TabGroupAutoCreation,
base::size(kTabGridLayoutAndroid_TabGroupAutoCreation), nullptr}, base::size(kTabGridLayoutAndroid_TabGroupAutoCreation), nullptr},
{"Price notifications", kTabGridLayoutAndroid_PriceNotifications, {"Price notifications", kTabGridLayoutAndroid_PriceNotifications,

@@ -59,6 +59,7 @@ android_library("java") {
deps = [ deps = [
":critical_persisted_tab_data_proto_java", ":critical_persisted_tab_data_proto_java",
":java_resources", ":java_resources",
":optimization_guide_protos_java",
"//base:base_java", "//base:base_java",
"//chrome/browser/android/crypto:java", "//chrome/browser/android/crypto:java",
"//chrome/browser/contextmenu:java", "//chrome/browser/contextmenu:java",
@@ -124,6 +125,11 @@ proto_java_library("critical_persisted_tab_data_proto_java") {
] ]
} }
proto_java_library("optimization_guide_protos_java") {
proto_path = "java/src/org/chromium/chrome/browser/tab/state/proto"
sources = [ "$proto_path/price_tracking.proto" ]
}
android_library("junit") { android_library("junit") {
bypass_platform_checks = true bypass_platform_checks = true
testonly = true testonly = true

@@ -15,6 +15,7 @@ import org.chromium.base.Log;
import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.Supplier; import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.flags.BooleanCachedFieldTrialParameter;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.IntCachedFieldTrialParameter; import org.chromium.chrome.browser.flags.IntCachedFieldTrialParameter;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeFactory; import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeFactory;
@@ -25,6 +26,9 @@ import org.chromium.chrome.browser.page_annotations.PageAnnotationsServiceFactor
import org.chromium.chrome.browser.page_annotations.ProductPriceUpdatePageAnnotation; import org.chromium.chrome.browser.page_annotations.ProductPriceUpdatePageAnnotation;
import org.chromium.chrome.browser.tab.EmptyTabObserver; import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.proto.PriceTracking.BuyableProduct;
import org.chromium.chrome.browser.tab.proto.PriceTracking.PriceTrackingData;
import org.chromium.chrome.browser.tab.proto.PriceTracking.ProductPriceUpdate;
import org.chromium.chrome.browser.tab.proto.ShoppingPersistedTabData.ShoppingPersistedTabDataProto; import org.chromium.chrome.browser.tab.proto.ShoppingPersistedTabData.ShoppingPersistedTabDataProto;
import org.chromium.components.optimization_guide.OptimizationGuideDecision; import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.HintsProto; import org.chromium.components.optimization_guide.proto.HintsProto;
@@ -47,6 +51,8 @@ public class ShoppingPersistedTabData extends PersistedTabData {
"price_tracking_stale_tab_threshold_seconds"; "price_tracking_stale_tab_threshold_seconds";
private static final String TIME_TO_LIVE_MS_PARAM = "price_tracking_time_to_live_ms"; private static final String TIME_TO_LIVE_MS_PARAM = "price_tracking_time_to_live_ms";
private static final String DISPLAY_TIME_MS_PARAM = "price_tracking_display_time_ms"; private static final String DISPLAY_TIME_MS_PARAM = "price_tracking_display_time_ms";
private static final String PRICE_TRACKING_WITH_OPTIMIZATION_GUIDE_PARAM =
"price_tracking_with_optimization_guide";
private static final int FRACTIONAL_DIGITS_LESS_THAN_TEN_UNITS = 2; private static final int FRACTIONAL_DIGITS_LESS_THAN_TEN_UNITS = 2;
private static final int FRACTIONAL_DIGITS_GREATER_THAN_TEN_UNITS = 0; private static final int FRACTIONAL_DIGITS_GREATER_THAN_TEN_UNITS = 0;
@@ -76,12 +82,20 @@ public class ShoppingPersistedTabData extends PersistedTabData {
new IntCachedFieldTrialParameter(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, new IntCachedFieldTrialParameter(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID,
DISPLAY_TIME_MS_PARAM, (int) ONE_WEEK_MS); DISPLAY_TIME_MS_PARAM, (int) ONE_WEEK_MS);
public static final BooleanCachedFieldTrialParameter PRICE_TRACKING_WITH_OPTIMIZATION_GUIDE =
new BooleanCachedFieldTrialParameter(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID,
PRICE_TRACKING_WITH_OPTIMIZATION_GUIDE_PARAM, false);
@VisibleForTesting @VisibleForTesting
public static final long NO_TRANSITIONS_OCCURRED = -1; public static final long NO_TRANSITIONS_OCCURRED = -1;
@VisibleForTesting @VisibleForTesting
public static final long NO_PRICE_KNOWN = -1; public static final long NO_PRICE_KNOWN = -1;
@VisibleForTesting
protected static PageAnnotationsServiceFactory sPageAnnotationsServiceFactory =
new PageAnnotationsServiceFactory();
public long mLastPriceChangeTimeMs = NO_TRANSITIONS_OCCURRED; public long mLastPriceChangeTimeMs = NO_TRANSITIONS_OCCURRED;
private long mPriceMicros = NO_PRICE_KNOWN; private long mPriceMicros = NO_PRICE_KNOWN;
@@ -94,10 +108,6 @@ public class ShoppingPersistedTabData extends PersistedTabData {
protected ObservableSupplierImpl<Boolean> mIsTabSaveEnabledSupplier = protected ObservableSupplierImpl<Boolean> mIsTabSaveEnabledSupplier =
new ObservableSupplierImpl<>(); new ObservableSupplierImpl<>();
@VisibleForTesting
protected static PageAnnotationsServiceFactory sPageAnnotationsServiceFactory =
new PageAnnotationsServiceFactory();
@VisibleForTesting @VisibleForTesting
protected EmptyTabObserver mUrlUpdatedObserver; protected EmptyTabObserver mUrlUpdatedObserver;
@@ -216,11 +226,42 @@ public class ShoppingPersistedTabData extends PersistedTabData {
return; return;
} }
sPageAnnotationsServiceFactory.getForLastUsedProfile().getAnnotations( if (PRICE_TRACKING_WITH_OPTIMIZATION_GUIDE.getValue()) {
tab.getUrl(), (result) -> { OptimizationGuideBridgeFactoryHolder.sOptimizationGuideBridgeFactory
supplierCallback.onResult( .create()
build(tab, result, previousShoppingPersistedTabData)); .canApplyOptimization(tab.getUrl(),
}); HintsProto.OptimizationType.PRICE_TRACKING,
(decision, metadata) -> {
if (decision != OptimizationGuideDecision.TRUE) {
supplierCallback.onResult(null);
return;
}
try {
PriceTrackingData priceTrackingDataProto =
PriceTrackingData.parseFrom(
metadata.getValue());
supplierCallback.onResult(build(tab,
priceTrackingDataProto,
previousShoppingPersistedTabData));
} catch (InvalidProtocolBufferException e) {
Log.i(TAG,
String.format(Locale.US,
"There was a problem "
+ "parsing "
+ "PriceTracking"
+ "DataProto. "
+ "Details %s.",
e));
supplierCallback.onResult(null);
}
});
} else {
sPageAnnotationsServiceFactory.getForLastUsedProfile().getAnnotations(
tab.getUrl(), (result) -> {
supplierCallback.onResult(build(
tab, result, previousShoppingPersistedTabData));
});
}
}); });
}, },
ShoppingPersistedTabData.class, callback); ShoppingPersistedTabData.class, callback);
@@ -236,6 +277,16 @@ public class ShoppingPersistedTabData extends PersistedTabData {
/** /**
* Whether a BuyableProductAnnotation was found or not * Whether a BuyableProductAnnotation was found or not
*/ */
@IntDef({FoundBuyableProduct.NOT_FOUND, FoundBuyableProduct.FOUND,
FoundBuyableProduct.FOUND_WITH_PRICE_UPDATE})
@Retention(RetentionPolicy.SOURCE)
@interface FoundBuyableProduct {
int NOT_FOUND = 0;
int FOUND = 1;
int FOUND_WITH_PRICE_UPDATE = 2;
int NUM_ENTRIES = 3;
}
@IntDef({FoundBuyableProductAnnotation.NOT_FOUND, FoundBuyableProductAnnotation.FOUND, @IntDef({FoundBuyableProductAnnotation.NOT_FOUND, FoundBuyableProductAnnotation.FOUND,
FoundBuyableProductAnnotation.FOUND_WITH_PRICE_UPDATE}) FoundBuyableProductAnnotation.FOUND_WITH_PRICE_UPDATE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@@ -303,6 +354,80 @@ public class ShoppingPersistedTabData extends PersistedTabData {
return null; return null;
} }
private static ShoppingPersistedTabData build(Tab tab, PriceTrackingData priceTrackingData,
ShoppingPersistedTabData previousShoppingPersistedTabData) {
ShoppingPersistedTabData res = new ShoppingPersistedTabData(tab);
@FoundBuyableProduct
int foundBuyableProduct = FoundBuyableProduct.NOT_FOUND;
ProductPriceUpdate productUpdate = priceTrackingData.getProductUpdate();
BuyableProduct buyableProduct = priceTrackingData.getBuyableProduct();
if (hasPriceUpdate(priceTrackingData)) {
res.setPriceMicros(productUpdate.getNewPrice().getAmountMicros());
res.setPreviousPriceMicros(productUpdate.getOldPrice().getAmountMicros());
res.setCurrencyCode(productUpdate.getOldPrice().getCurrencyCode());
res.setLastUpdatedMs(System.currentTimeMillis());
res.setMainOfferId(String.valueOf(buyableProduct.getOfferId()));
foundBuyableProduct = FoundBuyableProduct.FOUND_WITH_PRICE_UPDATE;
} else if (hasPrice(priceTrackingData)) {
res.setPriceMicros(buyableProduct.getCurrentPrice().getAmountMicros(),
previousShoppingPersistedTabData);
res.setCurrencyCode(buyableProduct.getCurrentPrice().getCurrencyCode());
res.setLastUpdatedMs(System.currentTimeMillis());
res.setMainOfferId(String.valueOf(buyableProduct.getOfferId()));
foundBuyableProduct = FoundBuyableProduct.FOUND;
}
RecordHistogram.recordEnumeratedHistogram(
"Tabs.ShoppingPersistedTabData.FoundBuyableProduct", foundBuyableProduct,
FoundBuyableProduct.NUM_ENTRIES);
// Only persist this ShoppingPersistedTabData if it was correctly populated from the
// response
if (foundBuyableProduct == FoundBuyableProduct.FOUND
|| foundBuyableProduct == FoundBuyableProduct.FOUND_WITH_PRICE_UPDATE) {
res.enableSaving();
return res;
}
return null;
}
private static boolean hasPriceUpdate(PriceTrackingData priceTrackingDataProto) {
if (!priceTrackingDataProto.hasBuyableProduct()
|| !priceTrackingDataProto.hasProductUpdate()) {
return false;
}
ProductPriceUpdate productUpdateProto = priceTrackingDataProto.getProductUpdate();
if (!productUpdateProto.hasNewPrice() || !productUpdateProto.hasOldPrice()) {
return false;
}
if (!productUpdateProto.getNewPrice().hasCurrencyCode()
|| !productUpdateProto.getOldPrice().hasCurrencyCode()) {
return false;
}
if (!productUpdateProto.getNewPrice().getCurrencyCode().equals(
productUpdateProto.getOldPrice().getCurrencyCode())) {
return false;
}
return true;
}
private static boolean hasPrice(PriceTrackingData priceTrackingDataProto) {
if (!priceTrackingDataProto.hasBuyableProduct()) {
return false;
}
if (!priceTrackingDataProto.getBuyableProduct().hasCurrentPrice()) {
return false;
}
if (!priceTrackingDataProto.getBuyableProduct().getCurrentPrice().hasAmountMicros()
|| !priceTrackingDataProto.getBuyableProduct()
.getCurrentPrice()
.hasCurrencyCode()) {
return false;
}
return true;
}
/** /**
* Set the price string * Set the price string
* @param priceString a string representing the price of the shopping offer * @param priceString a string representing the price of the shopping offer
@@ -539,6 +664,8 @@ public class ShoppingPersistedTabData extends PersistedTabData {
return System.currentTimeMillis() - CriticalPersistedTabData.from(tab).getTimestampMillis(); return System.currentTimeMillis() - CriticalPersistedTabData.from(tab).getTimestampMillis();
} }
// TODO(crbug.com/1196860) remove as OptimizationType.PRICE_TRACKING deprecates the need for
// this
private static void isShoppingPage(GURL url, Callback<Boolean> callback) { private static void isShoppingPage(GURL url, Callback<Boolean> callback) {
OptimizationGuideBridgeFactoryHolder.sOptimizationGuideBridgeFactory.create() OptimizationGuideBridgeFactoryHolder.sOptimizationGuideBridgeFactory.create()
.canApplyOptimization(url, HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR, .canApplyOptimization(url, HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR,

@@ -0,0 +1,59 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
syntax = "proto2";
package org.chromium.chrome.browser.tab.proto;
option java_package = "org.chromium.chrome.browser.tab.proto";
message PriceTrackingData {
// metadata relating to the product
optional BuyableProduct buyable_product = 1;
// price update data for the product
optional ProductPriceUpdate product_update = 2;
}
message BuyableProduct {
enum ProductReferenceType {
UNKNOWN = 0;
// The product referenced in the product details page.
MAIN_PRODUCT = 1;
}
// The title of the product.
optional string title = 1;
// Direct link to the product image.
optional string image_url = 2;
// Price as shown in the page.
optional ProductPrice current_price = 3;
// Determines how the product is referenced in the current page.
optional ProductReferenceType reference_type = 4;
// Docid of the offer.
optional fixed64 offer_id = 5;
}
message ProductPriceUpdate {
// Docid of the offer represented by this update.
optional fixed64 offer_id = 1;
// Old price as seen on the shopping backend.
optional ProductPrice old_price = 2;
// New price as seen on the shopping backend.
optional ProductPrice new_price = 3;
}
message ProductPrice {
// Code for the currency e.g. USD.
optional string currency_code = 1;
// Price in micros.
optional int64 amount_micros = 2;
}

@@ -59,6 +59,8 @@ std::string GetStringNameForOptimizationType(
return "LoginDetection"; return "LoginDetection";
case proto::OptimizationType::MERCHANT_TRUST_SIGNALS: case proto::OptimizationType::MERCHANT_TRUST_SIGNALS:
return "MerchantTrustSignals"; return "MerchantTrustSignals";
case proto::OptimizationType::PRICE_TRACKING:
return "PriceTracking";
} }
NOTREACHED(); NOTREACHED();
return std::string(); return std::string();

@@ -151,6 +151,8 @@ enum OptimizationType {
// Provides key information about the merchant represented by the current // Provides key information about the merchant represented by the current
// host. // host.
MERCHANT_TRUST_SIGNALS = 17; MERCHANT_TRUST_SIGNALS = 17;
// Provides pricing data so the user can track prices and price updates.
PRICE_TRACKING = 18;
} }
// Presents semantics for how page load URLs should be matched. // Presents semantics for how page load URLs should be matched.

@@ -12534,6 +12534,8 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
</suffix> </suffix>
<suffix name="PerformanceHints" <suffix name="PerformanceHints"
label="Provides aggregated performance information about the page"/> label="Provides aggregated performance information about the page"/>
<suffix name="PriceTracking"
label="Returns price related data for shopping websites"/>
<suffix name="ResourceLoading" <suffix name="ResourceLoading"
label="Applies a set of resource loading hints to load the page"> label="Applies a set of resource loading hints to load the page">
<obsolete> <obsolete>