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/tab:java",
"//chrome/browser/tab:junit",
"//chrome/browser/tab:optimization_guide_protos_java",
"//chrome/browser/tab_group:java",
"//chrome/browser/tab_group:junit",
"//chrome/browser/tabmodel:factory_java",
@@ -1210,6 +1211,7 @@ android_library("chrome_test_java") {
"//chrome/browser/signin/ui/android:javatests",
"//chrome/browser/tab:critical_persisted_tab_data_proto_java",
"//chrome/browser/tab:java",
"//chrome/browser/tab:optimization_guide_protos_java",
"//chrome/browser/tab_group:java",
"//chrome/browser/tabmodel: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/PersistedTabDataTest.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/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/tabmodel/AsyncTabCreationParamsManagerTest.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.RecyclerView;
import com.google.protobuf.ByteString;
import org.junit.After;
import org.junit.Assume;
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.TabLaunchType;
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.PersistedTabDataConfiguration;
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.Tracker;
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.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationController;
@@ -147,7 +153,6 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Tests for {@link TabListMediator}.
*/
@@ -181,10 +186,25 @@ public class TabListMediatorUnitTest {
private static final int TAB3_ID = 123;
private static final int POSITION1 = 0;
private static final int POSITION2 = 1;
private static final String EMPTY_ENDPOINT_RESPONSE = "{}";
private static final String ENDPOINT_RESPONSE =
"{\"representations\" : [{\"type\" : \"SHOPPING\", \"productTitle\" : \"Book of Pie\","
+ "\"price\" : 123456789012345, \"currency\" : \"USD\"}]}";
private static final BuyableProduct BUYABLE_PRODUCT_PROTO_INITIAL =
BuyableProduct.newBuilder()
.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,
TabListMediatorType.TAB_GRID_DIALOG})
@@ -294,7 +314,6 @@ public class TabListMediatorUnitTest {
mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
// Ensure native pointer is initialized
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE);
mTab1Domain = JUnitTestGURLs.getGURL(TAB1_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.TRACK_PRICES_ON_TABS, priceTrackingEnabled);
Profile.setLastUsedProfileForTesting(mProfile);
Map<String, String> responses = new HashMap<>();
responses.put(TAB1_URL, ENDPOINT_RESPONSE);
responses.put(TAB2_URL, EMPTY_ENDPOINT_RESPONSE);
mockEndpointResponse(responses);
Map<GURL, Any> responses = new HashMap<>();
GURL gurl1 = JUnitTestGURLs.getGURL(TAB1_URL);
GURL gurl2 = JUnitTestGURLs.getGURL(TAB2_URL);
responses.put(gurl1, ANY_BUYABLE_PRODUCT_INITIAL);
responses.put(gurl2, ANY_EMPTY);
mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE, responses);
PersistedTabDataConfiguration.setUseTestConfig(true);
initAndAssertAllProperties(mMediatorSpy);
List<Tab> tabs = new ArrayList<>();
@@ -2783,19 +2804,22 @@ public class TabListMediatorUnitTest {
}
}
private void mockOptimizationGuideResponse(@OptimizationGuideDecision int decision) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback =
(OptimizationGuideCallback) invocation.getArguments()[3];
callback.onOptimizationGuideDecision(decision, null);
return null;
}
})
.when(mOptimizationGuideBridgeJniMock)
.canApplyOptimization(
anyLong(), any(GURL.class), anyInt(), any(OptimizationGuideCallback.class));
private void mockOptimizationGuideResponse(
@OptimizationGuideDecision int decision, Map<GURL, Any> responses) {
for (Map.Entry<GURL, Any> responseEntry : responses.entrySet()) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback =
(OptimizationGuideCallback) invocation.getArguments()[3];
callback.onOptimizationGuideDecision(decision, responseEntry.getValue());
return null;
}
})
.when(mOptimizationGuideBridgeJniMock)
.canApplyOptimization(anyLong(), eq(responseEntry.getKey()), anyInt(),
any(OptimizationGuideCallback.class));
}
}
private void initWithThreeTabs() {

@@ -121,6 +121,7 @@ public class ChromeCachedFlags {
ShoppingPersistedTabData.TIME_TO_LIVE_MS,
ShoppingPersistedTabData.DISPLAY_TIME_MS,
ShoppingPersistedTabData.STALE_TAB_THRESHOLD_SECONDS,
ShoppingPersistedTabData.PRICE_TRACKING_WITH_OPTIMIZATION_GUIDE,
StartSurfaceConfiguration.HOME_BUTTON_ON_GRID_TAB_SWITCHER,
StartSurfaceConfiguration.NEW_SURFACE_FROM_HOME_BUTTON,
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.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;
@@ -58,42 +54,29 @@ public class ShoppingPersistedTabDataLegacyTest {
@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, OptimizationGuideDecision.TRUE);
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"})
public void testShoppingPriceChange() {
Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000/"
+ "price_tracking_with_optimization_guide/true"})
public void
testShoppingPriceChange() {
shoppingPriceChange(ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO));
@@ -102,8 +85,10 @@ public class ShoppingPersistedTabDataLegacyTest {
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000"})
public void testShoppingPriceChangeExtraFetchAfterChange() {
Add({"force-fieldtrial-params=Study.Group:price_tracking_time_to_live_ms/-1000/"
+ "price_tracking_with_optimization_guide/true"})
public void
testShoppingPriceChangeExtraFetchAfterChange() {
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -111,8 +96,8 @@ public class ShoppingPersistedTabDataLegacyTest {
final Semaphore semaphore = new Semaphore(0);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 3);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 3);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
@@ -130,13 +115,15 @@ public class ShoppingPersistedTabDataLegacyTest {
private long shoppingPriceChange(Tab tab) {
final Semaphore initialSemaphore = new Semaphore(0);
final Semaphore updateSemaphore = new Semaphore(0);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 1);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
shoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UNITED_STATES_CURRENCY_CODE,
@@ -150,13 +137,15 @@ public class ShoppingPersistedTabDataLegacyTest {
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
long firstUpdateTime = ShoppingPersistedTabDataTestUtils.getTimeLastUpdatedOnUiThread(tab);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (updatedShoppingPersistedTabData) -> {
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 2);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 2);
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
updatedShoppingPersistedTabData.getPriceMicros());
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
@@ -172,6 +161,8 @@ public class ShoppingPersistedTabDataLegacyTest {
@UiThreadTest
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testNoRefetch() {
final Semaphore initialSemaphore = new Semaphore(0);
final Semaphore updateSemaphore = new Semaphore(0);
@@ -179,8 +170,10 @@ public class ShoppingPersistedTabDataLegacyTest {
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
// Mock annotations response.
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> {
@@ -197,10 +190,12 @@ public class ShoppingPersistedTabDataLegacyTest {
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 1);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -217,8 +212,8 @@ public class ShoppingPersistedTabDataLegacyTest {
ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore);
// PageAnnotationsService should not have been called a second time - because we haven't
// passed the time to live
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 1);
}
@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.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.MockTab;
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.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;
@@ -68,12 +67,6 @@ public class ShoppingPersistedTabDataTest {
@Mock
protected Profile mProfileMock;
@Mock
protected PageAnnotationsServiceFactory mServiceFactoryMock;
@Mock
protected PageAnnotationsService mPageAnnotationsServiceMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -82,12 +75,11 @@ public class ShoppingPersistedTabDataTest {
// Ensure native pointer is initialized
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, OptimizationGuideDecision.TRUE);
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.TRUE, null);
PersistedTabDataConfiguration.setUseTestConfig(true);
Profile.setLastUsedProfileForTesting(mProfileMock);
doReturn(mPageAnnotationsServiceMock).when(mServiceFactoryMock).getForLastUsedProfile();
ShoppingPersistedTabData.sPageAnnotationsServiceFactory = mServiceFactoryMock;
}
@UiThreadTest
@@ -114,11 +106,15 @@ public class ShoppingPersistedTabDataTest {
@SmallTest
@Test
public void testShoppingBloomFilterNotShoppingWebsite() {
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, OptimizationGuideDecision.FALSE);
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.FALSE, null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -128,21 +124,26 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); });
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 0);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 0);
}
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testShoppingBloomFilterShoppingWebsite() {
for (@OptimizationGuideDecision int decision :
new int[] {OptimizationGuideDecision.TRUE, OptimizationGuideDecision.UNKNOWN}) {
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(
mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, decision);
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(), decision,
null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -152,8 +153,8 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); });
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 1);
}
}
@@ -171,15 +172,17 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); });
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 0);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 0);
}
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400"})
public void test2DayTabWithStaleOverride1day() {
Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400/"
+ "price_tracking_with_optimization_guide/true"})
public void
test2DayTabWithStaleOverride1day() {
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -191,20 +194,26 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); });
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 0);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 0);
}
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400"})
public void testHalfDayTabWithStaleOverride1day() {
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
Add({"force-fieldtrial-params=Study.Group:price_tracking_stale_tab_threshold_seconds/86400/"
+ "price_tracking_with_optimization_guide/true"})
public void
testHalfDayTabWithStaleOverride1day() {
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock, OptimizationGuideDecision.TRUE);
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
OptimizationGuideDecision.TRUE, null);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
@@ -216,24 +225,26 @@ public class ShoppingPersistedTabDataTest {
tab, (shoppingPersistedTabData) -> { semaphore.release(); });
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 1);
}
@UiThreadTest
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
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.
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
Assert.assertEquals(ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
@@ -248,10 +259,12 @@ public class ShoppingPersistedTabDataTest {
});
});
ShoppingPersistedTabDataTestUtils.acquireSemaphore(initialSemaphore);
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 1);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_PRICE_UPDATED);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -268,8 +281,8 @@ public class ShoppingPersistedTabDataTest {
ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore);
// EndpointFetcher should not have been called a second time - because we haven't passed the
// time to live
ShoppingPersistedTabDataTestUtils.verifyGetPageAnnotationsCalled(
mPageAnnotationsServiceMock, 1);
ShoppingPersistedTabDataTestUtils.verifyPriceTrackingOptimizationTypeCalled(
mOptimizationGuideBridgeJniMock, 1);
}
@UiThreadTest
@@ -416,13 +429,17 @@ public class ShoppingPersistedTabDataTest {
@UiThreadTest
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testSPTDSavingEnabledUponSuccessfulResponse() {
final Semaphore semaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_INITIAL);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -436,13 +453,17 @@ public class ShoppingPersistedTabDataTest {
@UiThreadTest
@SmallTest
@Test
@CommandLineFlags.
Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
public void testSPTDSavingEnabledUponSuccessfulProductUpdateResponse() {
final Semaphore semaphore = new Semaphore(0);
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE);
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
@@ -461,9 +482,31 @@ public class ShoppingPersistedTabDataTest {
Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(
ShoppingPersistedTabDataTestUtils.TAB_ID,
ShoppingPersistedTabDataTestUtils.IS_INCOGNITO);
ShoppingPersistedTabDataTestUtils.mockPageAnnotationsResponse(mPageAnnotationsServiceMock,
ShoppingPersistedTabDataTestUtils.MockPageAnnotationsResponse
.BUYABLE_PRODUCT_EMPTY);
ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
mOptimizationGuideBridgeJniMock,
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(() -> {
ShoppingPersistedTabData.from(tab, (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.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.protobuf.ByteString;
import org.mockito.invocation.InvocationOnMock;
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.optimization_guide.OptimizationGuideBridge;
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.tab.MockTab;
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.proto.CommonTypesProto.Any;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.url.GURL;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicReference;
/**
* Helper class for {@link ShoppingPersistedTabDataTest} & {@link
* ShoppingPersistedTabDataLegacyTest}.
*/
public abstract class ShoppingPersistedTabDataTestUtils {
@IntDef({MockPageAnnotationsResponse.BUYABLE_PRODUCT_INITIAL,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_PRICE_UPDATED,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE,
MockPageAnnotationsResponse.PRODUCT_PRICE_UPDATE,
MockPageAnnotationsResponse.BUYABLE_PRODUCT_EMPTY})
@IntDef({MockPriceTrackingResponse.BUYABLE_PRODUCT_INITIAL,
MockPriceTrackingResponse.BUYABLE_PRODUCT_PRICE_UPDATED,
MockPriceTrackingResponse.BUYABLE_PRODUCT_AND_PRODUCT_UPDATE,
MockPriceTrackingResponse.PRODUCT_PRICE_UPDATE,
MockPriceTrackingResponse.BUYABLE_PRODUCT_EMPTY, MockPriceTrackingResponse.NONE})
@Retention(RetentionPolicy.SOURCE)
@interface MockPageAnnotationsResponse {
@interface MockPriceTrackingResponse {
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;
int NONE = 5;
}
static final long PRICE_MICROS = 123456789012345L;
@@ -69,6 +76,72 @@ public abstract class ShoppingPersistedTabDataTestUtils {
static final boolean IS_INCOGNITO = false;
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() {
ShoppingPersistedTabData shoppingPersistedTabData =
new ShoppingPersistedTabData(createTabOnUiThread(TAB_ID, IS_INCOGNITO));
@@ -113,13 +186,56 @@ public abstract class ShoppingPersistedTabDataTestUtils {
}
static void mockOptimizationGuideResponse(OptimizationGuideBridge.Natives optimizationGuideJni,
@OptimizationGuideDecision int decision) {
int optimizationType, @OptimizationGuideDecision int decision, @Nullable Any metadata) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback =
(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;
}
})
@@ -128,55 +244,17 @@ public abstract class ShoppingPersistedTabDataTestUtils {
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) {
verify(endpointFetcher, times(numTimes))
.nativeFetchChromeAPIKey(any(Profile.class), anyString(), anyString(), anyString(),
anyString(), anyLong(), any(String[].class), any(Callback.class));
}
static void verifyGetPageAnnotationsCalled(
PageAnnotationsService pageAnnotationsService, int numTimes) {
verify(pageAnnotationsService, times(numTimes))
.getAnnotations(any(GURL.class), any(Callback.class));
static void verifyPriceTrackingOptimizationTypeCalled(
OptimizationGuideBridge.Natives optimizationGuideJni, int numTimes) {
verify(optimizationGuideJni, times(numTimes))
.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"}};
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[] =
{{"enable_tab_group_auto_creation", "false"}};
@@ -1751,6 +1757,10 @@ const FeatureEntry::FeatureVariation kTabGridLayoutAndroidVariations[] = {
base::size(kTabGridLayoutAndroid_SearchChip), nullptr},
{"Price alerts", kTabGridLayoutAndroid_PriceAlerts,
base::size(kTabGridLayoutAndroid_PriceAlerts), nullptr},
{"Price alerts with OptimizationGuide",
kTabGridLayoutAndroid_PriceAlerts_WithOptimizationGuide,
base::size(kTabGridLayoutAndroid_PriceAlerts_WithOptimizationGuide),
nullptr},
{"Without auto group", kTabGridLayoutAndroid_TabGroupAutoCreation,
base::size(kTabGridLayoutAndroid_TabGroupAutoCreation), nullptr},
{"Price notifications", kTabGridLayoutAndroid_PriceNotifications,

@@ -59,6 +59,7 @@ android_library("java") {
deps = [
":critical_persisted_tab_data_proto_java",
":java_resources",
":optimization_guide_protos_java",
"//base:base_java",
"//chrome/browser/android/crypto: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") {
bypass_platform_checks = true
testonly = true

@@ -15,6 +15,7 @@ import org.chromium.base.Log;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplierImpl;
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.IntCachedFieldTrialParameter;
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.tab.EmptyTabObserver;
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.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.HintsProto;
@@ -47,6 +51,8 @@ public class ShoppingPersistedTabData extends PersistedTabData {
"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 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_GREATER_THAN_TEN_UNITS = 0;
@@ -76,12 +82,20 @@ public class ShoppingPersistedTabData extends PersistedTabData {
new IntCachedFieldTrialParameter(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID,
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
public static final long NO_TRANSITIONS_OCCURRED = -1;
@VisibleForTesting
public static final long NO_PRICE_KNOWN = -1;
@VisibleForTesting
protected static PageAnnotationsServiceFactory sPageAnnotationsServiceFactory =
new PageAnnotationsServiceFactory();
public long mLastPriceChangeTimeMs = NO_TRANSITIONS_OCCURRED;
private long mPriceMicros = NO_PRICE_KNOWN;
@@ -94,10 +108,6 @@ public class ShoppingPersistedTabData extends PersistedTabData {
protected ObservableSupplierImpl<Boolean> mIsTabSaveEnabledSupplier =
new ObservableSupplierImpl<>();
@VisibleForTesting
protected static PageAnnotationsServiceFactory sPageAnnotationsServiceFactory =
new PageAnnotationsServiceFactory();
@VisibleForTesting
protected EmptyTabObserver mUrlUpdatedObserver;
@@ -216,11 +226,42 @@ public class ShoppingPersistedTabData extends PersistedTabData {
return;
}
sPageAnnotationsServiceFactory.getForLastUsedProfile().getAnnotations(
tab.getUrl(), (result) -> {
supplierCallback.onResult(
build(tab, result, previousShoppingPersistedTabData));
});
if (PRICE_TRACKING_WITH_OPTIMIZATION_GUIDE.getValue()) {
OptimizationGuideBridgeFactoryHolder.sOptimizationGuideBridgeFactory
.create()
.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);
@@ -236,6 +277,16 @@ public class ShoppingPersistedTabData extends PersistedTabData {
/**
* 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,
FoundBuyableProductAnnotation.FOUND_WITH_PRICE_UPDATE})
@Retention(RetentionPolicy.SOURCE)
@@ -303,6 +354,80 @@ public class ShoppingPersistedTabData extends PersistedTabData {
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
* @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();
}
// TODO(crbug.com/1196860) remove as OptimizationType.PRICE_TRACKING deprecates the need for
// this
private static void isShoppingPage(GURL url, Callback<Boolean> callback) {
OptimizationGuideBridgeFactoryHolder.sOptimizationGuideBridgeFactory.create()
.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";
case proto::OptimizationType::MERCHANT_TRUST_SIGNALS:
return "MerchantTrustSignals";
case proto::OptimizationType::PRICE_TRACKING:
return "PriceTracking";
}
NOTREACHED();
return std::string();

@@ -151,6 +151,8 @@ enum OptimizationType {
// Provides key information about the merchant represented by the current
// host.
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.

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