// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox.suggestions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.os.Handler;
import android.util.SparseArray;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPausedSystemClock;
import org.chromium.base.ActivityState;
import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.omnibox.DeferredIMEWindowInsetApplicationCallback;
import org.chromium.chrome.browser.omnibox.LocationBarDataProvider;
import org.chromium.chrome.browser.omnibox.OmniboxMetrics;
import org.chromium.chrome.browser.omnibox.R;
import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteMediator.EditSessionState;
import org.chromium.chrome.browser.omnibox.suggestions.action.OmniboxActionFactoryImpl;
import org.chromium.chrome.browser.omnibox.suggestions.action.OmniboxAnswerAction;
import org.chromium.chrome.browser.omnibox.suggestions.header.HeaderProcessor;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.Tab.LoadUrlResult;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabWindowManager;
import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.components.favicon.LargeIconBridgeJni;
import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
import org.chromium.components.omnibox.AutocompleteMatch;
import org.chromium.components.omnibox.AutocompleteMatchBuilder;
import org.chromium.components.omnibox.AutocompleteResult;
import org.chromium.components.omnibox.OmniboxFeatureList;
import org.chromium.components.omnibox.OmniboxFeatures;
import org.chromium.components.omnibox.OmniboxSuggestionType;
import org.chromium.components.omnibox.action.OmniboxActionDelegate;
import org.chromium.components.omnibox.action.OmniboxActionFactoryJni;
import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.ui.InsetObserver;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/** Tests for {@link AutocompleteMediator}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
shadows = {
AutocompleteMediatorUnitTest.ShadowCachedSuggestionsManager.class,
ShadowLooper.class
})
public class AutocompleteMediatorUnitTest {
private static final int SUGGESTION_MIN_HEIGHT = 20;
private static final int HEADER_MIN_HEIGHT = 15;
private static final GURL PAGE_URL = new GURL("https://www.site.com/page.html");
private static final String PAGE_TITLE = "Page Title";
public @Rule JniMocker mJniMocker = new JniMocker();
public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
private @Mock AutocompleteDelegate mAutocompleteDelegate;
private @Mock UrlBarEditingTextStateProvider mTextStateProvider;
private @Mock SuggestionProcessor mMockProcessor;
private @Mock HeaderProcessor mMockHeaderProcessor;
private @Mock AutocompleteController mAutocompleteController;
private @Mock AutocompleteController.Natives mControllerJniMock;
private @Mock LocationBarDataProvider mLocationBarDataProvider;
private @Mock ModalDialogManager mModalDialogManager;
private @Mock Profile mProfile;
private @Mock Tab mTab;
private @Mock TabModel mTabModel;
private @Mock TabWindowManager mTabManager;
private @Mock WindowAndroid mMockWindowAndroid;
private @Mock OmniboxActionDelegate mOmniboxActionDelegate;
private @Mock LargeIconBridge.Natives mLargeIconBridgeJniMock;
private @Mock OmniboxActionFactoryJni mActionFactoryJni;
private @Mock NavigationHandle mNavigationHandle;
private @Mock ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
private @Mock WindowAndroid mWindowAndroid;
private @Mock OmniboxSuggestionsDropdownEmbedder mEmbedder;
private @Mock InsetObserver mInsetObserver;
private @Mock AutocompleteCoordinator.OmniboxSuggestionsVisualStateObserver
mVisualStateObserver;
private @Mock DeferredIMEWindowInsetApplicationCallback mDeferredImeCallback;
private @Captor ArgumentCaptor<OmniboxLoadUrlParams> mOmniboxLoadUrlParamsCaptor;
private @Captor ArgumentCaptor<SuggestionsListAnimationDriver> mDriverCaptor;
private PropertyModel mListModel;
private AutocompleteMediator mMediator;
private List<AutocompleteMatch> mSuggestionsList;
private AutocompleteResult mAutocompleteResult;
private ModelList mSuggestionModels;
private ObservableSupplierImpl<TabWindowManager> mTabWindowManagerSupplier;
private Activity mActivity = Robolectric.buildActivity(Activity.class).setup().get();
// Interface abstracting calls to CachedZeroSuggestionsManager, making interactions with that
// more idiomatic.
interface CachedZeroSuggestionsManagerCalls {
void saveToCache(AutocompleteResult r);
AutocompleteResult readFromCache();
}
// CachedZeroSuggestionsManager shadow that helps us intercept interactions with manager's
// static methods.
@Implements(CachedZeroSuggestionsManager.class)
public static class ShadowCachedSuggestionsManager {
public static CachedZeroSuggestionsManagerCalls mock =
mock(CachedZeroSuggestionsManagerCalls.class);
@Implementation
public static void saveToCache(AutocompleteResult r) {
mock.saveToCache(r);
}
@Implementation
public static AutocompleteResult readFromCache() {
return mock.readFromCache();
}
}
@Before
public void setUp() {
mJniMocker.mock(LargeIconBridgeJni.TEST_HOOKS, mLargeIconBridgeJniMock);
mJniMocker.mock(OmniboxActionFactoryJni.TEST_HOOKS, mActionFactoryJni);
mJniMocker.mock(AutocompleteControllerJni.TEST_HOOKS, mControllerJniMock);
doReturn(mAutocompleteController).when(mControllerJniMock).getForProfile(any());
mSuggestionModels = new ModelList();
mListModel = new PropertyModel(SuggestionListProperties.ALL_KEYS);
mListModel.set(SuggestionListProperties.SUGGESTION_MODELS, mSuggestionModels);
mTabWindowManagerSupplier = new ObservableSupplierImpl<>();
doReturn(mInsetObserver).when(mWindowAndroid).getInsetObserver();
mMediator =
new AutocompleteMediator(
mActivity,
mAutocompleteDelegate,
mTextStateProvider,
mListModel,
new Handler(),
() -> mModalDialogManager,
null,
null,
mLocationBarDataProvider,
tab -> {},
mTabWindowManagerSupplier,
url -> false,
mOmniboxActionDelegate,
mActivityLifecycleDispatcher,
mEmbedder,
mWindowAndroid,
mDeferredImeCallback);
mMediator
.getDropdownItemViewInfoListBuilderForTest()
.registerSuggestionProcessor(mMockProcessor);
mMediator
.getDropdownItemViewInfoListBuilderForTest()
.setHeaderProcessorForTest(mMockHeaderProcessor);
doReturn(SUGGESTION_MIN_HEIGHT).when(mMockProcessor).getMinimumViewHeight();
doReturn(true).when(mMockProcessor).doesProcessSuggestion(any(), anyInt());
doAnswer((invocation) -> new PropertyModel(SuggestionCommonProperties.ALL_KEYS))
.when(mMockProcessor)
.createModel();
doReturn(OmniboxSuggestionUiType.DEFAULT).when(mMockProcessor).getViewTypeId();
doAnswer((invocation) -> new PropertyModel(SuggestionCommonProperties.ALL_KEYS))
.when(mMockHeaderProcessor)
.createModel();
doReturn(HEADER_MIN_HEIGHT).when(mMockHeaderProcessor).getMinimumViewHeight();
doReturn(OmniboxSuggestionUiType.HEADER).when(mMockHeaderProcessor).getViewTypeId();
mSuggestionsList = buildSampleSuggestionsList(10, "Suggestion");
mAutocompleteResult = spy(AutocompleteResult.fromCache(mSuggestionsList, null));
doReturn(true).when(mAutocompleteDelegate).isKeyboardActive();
setUpLocationBarDataProvider(
JUnitTestGURLs.NTP_URL, "New Tab Page", PageClassification.NTP_VALUE);
mMediator.setOmniboxSuggestionsVisualStateObserver(Optional.of(mVisualStateObserver));
}
/**
* Build a fake suggestions list with elements named 'Suggestion #', where '#' is the suggestion
* index (1-based).
*
* @return List of suggestions.
*/
private List<AutocompleteMatch> buildSampleSuggestionsList(int count, String prefix) {
List<AutocompleteMatch> list = new ArrayList<>();
for (int index = 0; index < count; ++index) {
AutocompleteMatchBuilder builder =
AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST)
.setDisplayText(prefix + (index + 1));
if (index == 0) {
builder.setInlineAutocompletion("inline_autocomplete")
.setAllowedToBeDefaultMatch(true);
}
list.add(builder.build());
}
return list;
}
/**
* Build a fake group headers map with elements named 'Header #', where '#' is the group header
* index (1-based) and 'Header' is the supplied prefix. Each header has a corresponding key
* computed as baseKey + #.
*
* @param count Number of group headers to build.
* @param baseKey Key of the first group header.
* @param prefix Name prefix for each group.
* @return Map of group headers (populated in random order).
*/
private SparseArray<String> buildSampleGroupHeaders(int count, int baseKey, String prefix) {
SparseArray<String> headers = new SparseArray<>(count);
for (int index = 0; index < count; index++) {
headers.put(baseKey + index, prefix + " " + (index + 1));
}
return headers;
}
/**
* Set up LocationBarDataProvider to report supplied values.
*
* @param url The URL to report as a current URL.
* @param title The Page Title to report.
* @param pageClassification The Page classification to report.
*/
void setUpLocationBarDataProvider(GURL url, String title, int pageClassification) {
when(mLocationBarDataProvider.hasTab()).thenReturn(true);
when(mLocationBarDataProvider.getCurrentGurl()).thenReturn(url);
when(mLocationBarDataProvider.getTitle()).thenReturn(title);
when(mLocationBarDataProvider.getPageClassification(false)).thenReturn(pageClassification);
}
/** Sets the native object reference for all suggestions in mSuggestionList. */
void setSuggestionNativeObjectRef() {
for (int index = 0; index < mSuggestionsList.size(); index++) {
mSuggestionsList.get(index).updateNativeObjectRef(index + 1);
}
}
@Test
@SmallTest
public void updateSuggestionsList_worksWithNullList() {
mMediator.onNativeInitialized();
final int maximumListHeight = SUGGESTION_MIN_HEIGHT * 7;
mMediator.onSuggestionDropdownHeightChanged(maximumListHeight);
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(null, null), /* isFinal= */ true);
Assert.assertEquals(0, mSuggestionModels.size());
Assert.assertFalse(mListModel.get(SuggestionListProperties.OMNIBOX_SESSION_ACTIVE));
}
@Test
@SmallTest
public void updateSuggestionsList_worksWithEmptyList() {
mMediator.onNativeInitialized();
final int maximumListHeight = SUGGESTION_MIN_HEIGHT * 7;
mMediator.onSuggestionDropdownHeightChanged(maximumListHeight);
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(null, null), /* isFinal= */ true);
Assert.assertEquals(0, mSuggestionModels.size());
Assert.assertFalse(mListModel.get(SuggestionListProperties.OMNIBOX_SESSION_ACTIVE));
}
@Test
@SmallTest
public void updateSuggestionsList_scrolEventsWithConcealedItemsTogglesKeyboardVisibility() {
mMediator.onNativeInitialized();
final int heightWithOneConcealedItem =
(mSuggestionsList.size() - 2) * SUGGESTION_MIN_HEIGHT;
mMediator.onSuggestionDropdownHeightChanged(heightWithOneConcealedItem);
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
// With fully concealed elements, scroll should trigger keyboard hide.
reset(mAutocompleteDelegate);
mMediator.onSuggestionDropdownScroll();
verify(mAutocompleteDelegate, times(1)).setKeyboardVisibility(eq(false), anyBoolean());
verify(mAutocompleteDelegate, never()).setKeyboardVisibility(eq(true), anyBoolean());
// Pretend that the user scrolled back to top with an overscroll.
// This should bring back the soft keyboard.
reset(mAutocompleteDelegate);
mMediator.onSuggestionDropdownOverscrolledToTop();
verify(mAutocompleteDelegate, times(1)).setKeyboardVisibility(eq(true), anyBoolean());
verify(mAutocompleteDelegate, never()).setKeyboardVisibility(eq(false), anyBoolean());
}
@Test
@SmallTest
public void updateSuggestionsList_updateHeightWhenHardwareKeyboardIsConnected() {
// Simulates behavior of physical keyboard being attached to the device.
// In this scenario, requesting keyboard to come up will not result with an actual
// keyboard showing on the screen. As a result, the updated height should be used
// when estimating presence of fully concealed items on the suggestions list.
//
// Attaching and detaching physical keyboard will affect the space on the screen, but since
// the list of suggestions does not change, we are keeping them in exactly the same order
// (and keep the grouping prior to the change).
// The grouping is only affected, when the new list is provided (as a result of user's
// input).
mMediator.onNativeInitialized();
final int heightOfOAllSuggestions = mSuggestionsList.size() * SUGGESTION_MIN_HEIGHT;
final int heightWithOneConcealedItem =
(mSuggestionsList.size() - 1) * SUGGESTION_MIN_HEIGHT;
// This will request keyboard to show up upon receiving next suggestions list.
when(mAutocompleteDelegate.isKeyboardActive()).thenReturn(true);
// Report the height of the suggestions list, indicating that the keyboard is not visible.
// In both cases, the updated suggestions list height should be used to estimate presence of
// fully concealed items on the suggestions list.
mMediator.onSuggestionDropdownHeightChanged(heightOfOAllSuggestions);
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
// Build separate list of suggestions so that these are accepted as a new set.
// We want to follow the same restrictions as the original list (specifically: have a
// resulting list of suggestions taller than the space in dropdown view), so make sure
// the list sizes are same.
List<AutocompleteMatch> newList =
buildSampleSuggestionsList(mSuggestionsList.size(), "SuggestionB");
mMediator.onSuggestionDropdownHeightChanged(heightWithOneConcealedItem);
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(newList, null), /* isFinal= */ true);
}
@Test
@SmallTest
public void updateSuggestionsList_rejectsHeightUpdatesWhenKeyboardIsHidden() {
// Simulates scenario where we receive dropdown height update after software keyboard is
// explicitly hidden. In this scenario the updates should be rejected when estimating
// presence of fully concealed items on the suggestions list.
mMediator.onNativeInitialized();
final int heightOfOAllSuggestions = mSuggestionsList.size() * SUGGESTION_MIN_HEIGHT;
final int heightWithOneConcealedItem =
(mSuggestionsList.size() - 1) * SUGGESTION_MIN_HEIGHT;
// Report height change with keyboard visible
mMediator.onSuggestionDropdownHeightChanged(heightWithOneConcealedItem);
mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
// "Hide keyboard", report larger area and re-evaluate the results. We should see no
// difference, as the logic should only evaluate presence of items concealed when keyboard
// is active.
when(mAutocompleteDelegate.isKeyboardActive()).thenReturn(false);
mMediator.onSuggestionDropdownHeightChanged(heightOfOAllSuggestions);
mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
}
@Test
@SmallTest
@EnableFeatures(OmniboxFeatureList.ANIMATE_SUGGESTIONS_LIST_APPEARANCE)
public void onOmniboxSessionStateChange_startsAnimationDriver() {
mListModel.set(SuggestionListProperties.ALPHA, 1.0f);
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
verify(mInsetObserver).addWindowInsetsAnimationListener(mDriverCaptor.capture());
mMediator.onOmniboxSessionStateChange(false);
verify(mInsetObserver).removeWindowInsetsAnimationListener(mDriverCaptor.getValue());
}
@Test
@SmallTest
public void onTextChanged_emptyTextTriggersZeroSuggest() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
mMediator.onOmniboxSessionStateChange(true);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
mMediator.onNativeInitialized();
mMediator.onTextChanged("");
verify(mAutocompleteController).startZeroSuggest("", url, pageClassification, title);
}
@Test
@SmallTest
public void onTextChanged_nonEmptyTextTriggersSuggestions() {
mMediator.setAutocompleteProfile(mProfile);
GURL url = JUnitTestGURLs.BLUE_1;
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, url.getSpec(), pageClassification);
mMediator.onOmniboxSessionStateChange(true);
when(mTextStateProvider.shouldAutocomplete()).thenReturn(true);
when(mTextStateProvider.getSelectionStart()).thenReturn(4);
when(mTextStateProvider.getSelectionEnd()).thenReturn(4);
mMediator.onNativeInitialized();
mMediator.onTextChanged("test");
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verify(mAutocompleteController).start(url, pageClassification, "test", 4, false);
}
@Test
@SmallTest
public void onTextChanged_cancelsPendingRequests() {
mMediator.setAutocompleteProfile(mProfile);
GURL url = JUnitTestGURLs.BLUE_1;
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, url.getSpec(), pageClassification);
mMediator.onOmniboxSessionStateChange(true);
when(mTextStateProvider.shouldAutocomplete()).thenReturn(true);
when(mTextStateProvider.getSelectionStart()).thenReturn(4);
when(mTextStateProvider.getSelectionEnd()).thenReturn(4);
mMediator.onNativeInitialized();
mMediator.onTextChanged("test");
mMediator.onTextChanged("nottest");
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verify(mAutocompleteController, times(1))
.start(url, pageClassification, "nottest", 4, false);
verify(mAutocompleteController, times(1))
.start(any(), anyInt(), any(), anyInt(), anyBoolean());
}
@Test
@SmallTest
public void onOmniboxSessionStateChange_onlyOneZeroSuggestRequestIsInvoked() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
// Simulate URL being focus changes.
mMediator.onOmniboxSessionStateChange(true);
mMediator.onOmniboxSessionStateChange(false);
mMediator.onOmniboxSessionStateChange(true);
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, never()).startZeroSuggest(any(), any(), anyInt(), any());
// Simulate native being initialized. Make sure we only ever issue one request, even if
// there are multiple requests to activate the autocomplete session.
mMediator.onNativeInitialized();
ShadowLooper.runUiThreadTasks();
mMediator.onOmniboxSessionStateChange(true);
verify(mAutocompleteController, times(1))
.startZeroSuggest("", url, pageClassification, title);
}
@Test
@SmallTest
public void onOmniboxSessionStateChange_preventsZeroSuggestRequestOnDeactivation() {
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
// Simulate URL being focus changes.
mMediator.onOmniboxSessionStateChange(true);
mMediator.onOmniboxSessionStateChange(false);
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, never()).startZeroSuggest(any(), any(), anyInt(), any());
// Simulate native being inititalized. Make sure no suggest requests are sent.
mMediator.onNativeInitialized();
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, never()).startZeroSuggest(any(), any(), anyInt(), any());
}
@Test
@SmallTest
public void onOmniboxSessionStateChange_textChangeCancelsOutstandingZeroSuggestRequest() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("A");
// Simulate URL being focus changes, and that user typed text and deleted it.
mMediator.onOmniboxSessionStateChange(true);
mMediator.onTextChanged("A");
mMediator.onTextChanged("");
mMediator.onTextChanged("A");
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, never())
.start(any(), anyInt(), any(), anyInt(), anyBoolean());
verify(mAutocompleteController, never()).startZeroSuggest(any(), any(), anyInt(), any());
mMediator.onNativeInitialized();
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, times(1)).start(url, pageClassification, "A", 0, true);
verify(mAutocompleteController, times(1))
.start(any(), anyInt(), any(), anyInt(), anyBoolean());
verify(mAutocompleteController, never()).startZeroSuggest(any(), any(), anyInt(), any());
}
@Test
@SmallTest
public void onOmniboxSessionStateChange_textChangeCancelsIntermediateZeroSuggestRequests() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
mMediator.onOmniboxSessionStateChange(true);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
// Simulate URL being focus changes, and that user typed text and deleted it.
mMediator.onTextChanged("A");
mMediator.onTextChanged("");
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, never())
.start(any(), anyInt(), any(), anyInt(), anyBoolean());
verify(mAutocompleteController, never()).startZeroSuggest(any(), any(), anyInt(), any());
mMediator.onNativeInitialized();
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, never())
.start(any(), anyInt(), any(), anyInt(), anyBoolean());
verify(mAutocompleteController, times(1)).startZeroSuggest(any(), any(), anyInt(), any());
}
@Test
@SmallTest
public void onSuggestionsReceived_sendsOnSuggestionsChanged() {
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
verify(mAutocompleteDelegate).onSuggestionsChanged(any());
// Ensure duplicate requests are not suppressed, to preserve the
// relationship between Native and Java AutocompleteResult objects.
AutocompleteMatch defaultMatch =
AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST)
.setDisplayText("Suggestion1")
.setInlineAutocompletion("inline_autocomplete2")
.setAllowedToBeDefaultMatch(true)
.build();
mSuggestionsList.remove(0);
mSuggestionsList.add(0, defaultMatch);
mMediator.onSuggestionsReceived(AutocompleteResult.fromCache(mSuggestionsList, null), true);
verify(mAutocompleteDelegate).onSuggestionsChanged(defaultMatch);
}
@Test
@SmallTest
public void onSuggestionClicked_doesNotOpenInNewTab() {
mMediator.setAutocompleteProfile(mProfile);
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
GURL url = JUnitTestGURLs.BLUE_1;
mMediator.onSuggestionClicked(mSuggestionsList.get(0), 0, url);
// Verify that the URL is not loaded in a new tab.
verify(mAutocompleteDelegate).loadUrl(mOmniboxLoadUrlParamsCaptor.capture());
assertEquals(mOmniboxLoadUrlParamsCaptor.getValue().url, url.getSpec());
assertFalse(mOmniboxLoadUrlParamsCaptor.getValue().openInNewTab);
// Verify the callback.
mOmniboxLoadUrlParamsCaptor
.getValue()
.callback
.onLoadUrl(
null,
new LoadUrlResult(Tab.TabLoadStatus.DEFAULT_PAGE_LOAD, mNavigationHandle));
verify(mAutocompleteController)
.createNavigationObserver(mNavigationHandle, mSuggestionsList.get(0));
}
@Test
public void onSuggestionClicked_ClipboardImageSuggestion() {
mMediator.setAutocompleteProfile(mProfile);
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
var url = new GURL("http://test");
var match =
AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.CLIPBOARD_IMAGE)
.build();
// Verify that loadUrlWithPostData is called for the clipboard image suggestion.
mMediator.onSuggestionClicked(match, 0, url);
verify(mAutocompleteDelegate).loadUrl(mOmniboxLoadUrlParamsCaptor.capture());
assertEquals(mOmniboxLoadUrlParamsCaptor.getValue().url, url.getSpec());
// Verify the callback.
mOmniboxLoadUrlParamsCaptor
.getValue()
.callback
.onLoadUrl(
null,
new LoadUrlResult(Tab.TabLoadStatus.DEFAULT_PAGE_LOAD, mNavigationHandle));
verify(mAutocompleteController).createNavigationObserver(mNavigationHandle, match);
}
@Test
public void onSuggestionClicked_deferLoadingUntilNativeLibrariesLoaded() {
var url = new GURL("http://test");
var match =
AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST)
.build();
// Simulate interaction with match before native initialization completed.
mMediator.onSuggestionClicked(match, 0, url);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verifyNoMoreInteractions(mAutocompleteDelegate);
// Simulate native initialization complete, but still no profile.
mMediator.onNativeInitialized();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verifyNoMoreInteractions(mAutocompleteDelegate);
// Simulate profile loaded.
mMediator.setAutocompleteProfile(mProfile);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verify(mAutocompleteDelegate).loadUrl(mOmniboxLoadUrlParamsCaptor.capture());
assertEquals(mOmniboxLoadUrlParamsCaptor.getValue().url, url.getSpec());
assertFalse(mOmniboxLoadUrlParamsCaptor.getValue().openInNewTab);
verify(mAutocompleteDelegate).clearOmniboxFocus();
verifyNoMoreInteractions(mAutocompleteDelegate);
// Verify the callback.
mOmniboxLoadUrlParamsCaptor
.getValue()
.callback
.onLoadUrl(
null,
new LoadUrlResult(Tab.TabLoadStatus.DEFAULT_PAGE_LOAD, mNavigationHandle));
verify(mAutocompleteController).createNavigationObserver(mNavigationHandle, match);
// Verify no reload on profile change.
Profile newProfile = mock(Profile.class);
mMediator.setAutocompleteProfile(newProfile);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verifyNoMoreInteractions(mAutocompleteDelegate);
}
@Test
@SmallTest
public void setLayoutDirection_beforeInitialization() {
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
mMediator.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
mMediator.onSuggestionDropdownHeightChanged(Integer.MAX_VALUE);
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
Assert.assertEquals(mSuggestionsList.size(), mSuggestionModels.size());
for (int i = 0; i < mSuggestionModels.size(); i++) {
Assert.assertEquals(
i + "th model does not have the expected layout direction.",
View.LAYOUT_DIRECTION_RTL,
mSuggestionModels
.get(i)
.model
.get(SuggestionCommonProperties.LAYOUT_DIRECTION));
}
}
@Test
@SmallTest
public void setLayoutDirection_afterInitialization() {
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
mMediator.onSuggestionDropdownHeightChanged(Integer.MAX_VALUE);
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
Assert.assertEquals(mSuggestionsList.size(), mSuggestionModels.size());
mMediator.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
for (int i = 0; i < mSuggestionModels.size(); i++) {
Assert.assertEquals(
i + "th model does not have the expected layout direction.",
View.LAYOUT_DIRECTION_RTL,
mSuggestionModels
.get(i)
.model
.get(SuggestionCommonProperties.LAYOUT_DIRECTION));
}
mMediator.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
for (int i = 0; i < mSuggestionModels.size(); i++) {
Assert.assertEquals(
i + "th model does not have the expected layout direction.",
View.LAYOUT_DIRECTION_LTR,
mSuggestionModels
.get(i)
.model
.get(SuggestionCommonProperties.LAYOUT_DIRECTION));
}
}
@Test
public void onSuggestionDropdownHeightChanged_noNativeCallsUntilNativeIsReady() {
mMediator.onSuggestionDropdownHeightChanged(Integer.MAX_VALUE);
verifyNoMoreInteractions(mAutocompleteController);
}
@Test
public void onSuggestionDropdownHeightChanged_noNativeCallsUntilProfileIsReady() {
mMediator.onNativeInitialized();
mMediator.onSuggestionDropdownHeightChanged(Integer.MAX_VALUE);
verifyNoMoreInteractions(mAutocompleteController);
}
@Test
public void onSuggestionDropdownHeightChanged_updatedHeightPassedToNative() {
mMediator.onNativeInitialized();
mMediator.setAutocompleteProfile(mProfile);
var res = ContextUtils.getApplicationContext().getResources();
int suggestionHeight = res.getDimensionPixelSize(R.dimen.omnibox_suggestion_content_height);
float displayDensity = res.getDisplayMetrics().density;
mMediator.onSuggestionDropdownHeightChanged(100);
verify(mAutocompleteController)
.onSuggestionDropdownHeightChanged((int) (100 * displayDensity), suggestionHeight);
}
@Test
@SmallTest
public void onOmniboxSessionStateChange_triggersZeroSuggest_nativeInitialized() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn(url.getSpec());
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController)
.startZeroSuggest(url.getSpec(), url, pageClassification, title);
}
@Test
@SmallTest
public void onOmniboxSessionStateChange_triggersZeroSuggest_nativeNotInitialized() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
// Signal focus prior to initializing native; confirm that zero suggest is not triggered.
mMediator.onOmniboxSessionStateChange(true);
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController, never()).startZeroSuggest(any(), any(), anyInt(), any());
// Initialize native and ensure zero suggest is triggered.
mMediator.onNativeInitialized();
ShadowLooper.runUiThreadTasks();
verify(mAutocompleteController).startZeroSuggest("", url, pageClassification, title);
}
@Test
@SmallTest
public void onTextChanged_editSessionActivatedByUserInput() {
mMediator.setAutocompleteProfile(mProfile);
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
Assert.assertEquals(mMediator.getEditSessionStateForTest(), EditSessionState.INACTIVE);
mMediator.onTextChanged("n");
Assert.assertEquals(
mMediator.getEditSessionStateForTest(), EditSessionState.ACTIVATED_BY_USER_INPUT);
mMediator.onOmniboxSessionStateChange(false);
Assert.assertEquals(mMediator.getEditSessionStateForTest(), EditSessionState.INACTIVE);
}
@Test
@SmallTest
public void switchToTab_noTargetTab() {
mMediator.setAutocompleteProfile(mProfile);
// There is no Tab to switch to.
doReturn(null).when(mAutocompleteController).getMatchingTabForSuggestion(any());
Assert.assertFalse(mMediator.maybeSwitchToTab(null));
}
@Test
@SmallTest
public void switchToTab_noTabManager() {
mMediator.setAutocompleteProfile(mProfile);
// We have a tab, but no tab manager.
doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
Assert.assertFalse(mMediator.maybeSwitchToTab(null));
}
@Test
@SmallTest
public void switchToTab_tabAttachedToStoppedActivity() {
mMediator.setAutocompleteProfile(mProfile);
// We have a tab, and tab manager. The tab is part of the stopped activity.
doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
mTabWindowManagerSupplier.set(mTabManager);
doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
doReturn(ActivityState.STOPPED).when(mMockWindowAndroid).getActivityState();
Assert.assertTrue(mMediator.maybeSwitchToTab(null));
}
@Test
@SmallTest
public void switchToTab_noTabModelForTab() {
mMediator.setAutocompleteProfile(mProfile);
// We have a tab, and tab manager. The tab is part of the running activity.
// The tab is not a part of the model though (eg. it has just been closed).
// https://crbug.com/1300447
doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
mTabWindowManagerSupplier.set(mTabManager);
doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
doReturn(ActivityState.RESUMED).when(mMockWindowAndroid).getActivityState();
doReturn(null).when(mTabManager).getTabModelForTab(any());
Assert.assertFalse(mMediator.maybeSwitchToTab(null));
}
@Test
@SmallTest
public void switchToTab_invalidTabModelAssociation() {
mMediator.setAutocompleteProfile(mProfile);
// We have a tab, and tab manager. The tab is part of the running activity.
// The tab reports association with an existing model, but the model thinks otherwise.
// https://crbug.com/1300447
doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
mTabWindowManagerSupplier.set(mTabManager);
doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
doReturn(ActivityState.RESUMED).when(mMockWindowAndroid).getActivityState();
doReturn(mTabModel).when(mTabManager).getTabModelForTab(any());
// Make sure that this indeed returns no association.
Assert.assertEquals(
TabModel.INVALID_TAB_INDEX, TabModelUtils.getTabIndexById(mTabModel, mTab.getId()));
Assert.assertFalse(mMediator.maybeSwitchToTab(null));
}
@Test
@SmallTest
public void switchToTab_validTabModelAssociation() {
mMediator.setAutocompleteProfile(mProfile);
// We have a tab, and tab manager. The tab is part of the running activity.
// The tab reports association with an existing model; the model confirms this.
doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
mTabWindowManagerSupplier.set(mTabManager);
doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
doReturn(ActivityState.RESUMED).when(mMockWindowAndroid).getActivityState();
doReturn(mTabModel).when(mTabManager).getTabModelForTab(any());
doReturn(1).when(mTabModel).getCount();
doReturn(mTab).when(mTabModel).getTabAt(anyInt());
Assert.assertTrue(mMediator.maybeSwitchToTab(null));
}
/**
* Verify the values recorded by SuggestionList.RequestToUiModel.* histograms.
*
* @param firstHistogramTotalCount total number of recorded values for the
* RequestToUiModel.First histogram
* @param firstHistogramTime the value to expect to be recorded as RequestToUiModel.First, or
* null if this histogram should not be recorded
* @param lastHistogramTotalCount total number of recorded values for the RequestToUiModel.Last
* histogram
* @param lastHistogramTime the value to expect to be recorded as RequestToUiModel.Last, or null
* if this histogram should not be recorded
*/
private void verifySuggestionRequestToUiModelHistograms(
int firstHistogramTotalCount,
@Nullable Integer firstHistogramTime,
int lastHistogramTotalCount,
@Nullable Integer lastHistogramTime) {
Assert.assertEquals(
firstHistogramTotalCount,
RecordHistogram.getHistogramTotalCountForTesting(
OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST));
Assert.assertEquals(
lastHistogramTotalCount,
RecordHistogram.getHistogramTotalCountForTesting(
OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST));
if (firstHistogramTime != null) {
Assert.assertEquals(
1,
RecordHistogram.getHistogramValueCountForTesting(
OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_FIRST,
firstHistogramTime));
}
if (lastHistogramTime != null) {
Assert.assertEquals(
1,
RecordHistogram.getHistogramValueCountForTesting(
OmniboxMetrics.HISTOGRAM_SUGGESTIONS_REQUEST_TO_UI_MODEL_LAST,
lastHistogramTime));
}
}
@Test
@SmallTest
public void requestToUiModelTime_recordedForZps() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
verify(mAutocompleteController).startZeroSuggest("", url, pageClassification, title);
verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
// Report first results. Observe first results histogram reported.
ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100));
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
verifySuggestionRequestToUiModelHistograms(1, 100, 0, null);
// Report next results. Observe first results histogram not reported.
ShadowPausedSystemClock.advanceBy(Duration.ofMillis(300));
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
verifySuggestionRequestToUiModelHistograms(1, 100, 0, null);
// Report last results. Observe two histograms reported.
ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100));
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
verifySuggestionRequestToUiModelHistograms(1, 100, 1, 500);
}
@Test
@SmallTest
public void requestToUiModelTime_notRecordedWhenCanceled_LastResult() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
verify(mAutocompleteController).startZeroSuggest("", url, pageClassification, title);
verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
// Report first results. Observe first results histogram reported.
ShadowPausedSystemClock.advanceBy(Duration.ofMillis(10));
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
verifySuggestionRequestToUiModelHistograms(1, 10, 0, null);
// Cancel the interaction.
mMediator.onOmniboxSessionStateChange(false);
// Report last results. Observe no final report.
verifySuggestionRequestToUiModelHistograms(1, 10, 0, null);
}
@Test
@SmallTest
public void requestToUiModelTime_notRecordedWhenCanceled_FirstResult() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
verify(mAutocompleteController).startZeroSuggest("", url, pageClassification, title);
verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
// Cancel the interaction.
mMediator.onOmniboxSessionStateChange(false);
// Report first results. Observe no report (no focus).
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
// Report last results. Observe no final report (no focus).
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
}
@Test
@SmallTest
public void requestToUiModelTime_recordsBothHistogramsWhenFirstResponseIsFinal() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
verify(mAutocompleteController).startZeroSuggest("", url, pageClassification, title);
verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
// Report first result as final. Observe both metrics reported.
ShadowPausedSystemClock.advanceBy(Duration.ofMillis(150));
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
verifySuggestionRequestToUiModelHistograms(1, 150, 1, 150);
}
@Test
@SmallTest
public void requestToUiModelTime_subsequentKeyStrokesReportTimeSinceLastKeystroke() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
verify(mAutocompleteController).startZeroSuggest("", url, pageClassification, title);
verifySuggestionRequestToUiModelHistograms(0, null, 0, null);
// Report first result as final. Observe both metrics reported.
ShadowPausedSystemClock.advanceBy(Duration.ofMillis(150));
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ false);
verifySuggestionRequestToUiModelHistograms(1, 150, 0, null);
// No change on key press. No unexpected recordings.
// Need to run looper here to flush the pending operation.
mMediator.onTextChanged("a");
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verifySuggestionRequestToUiModelHistograms(1, 150, 0, null);
// No change on key press. No unexpected recordings.
ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100));
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
verifySuggestionRequestToUiModelHistograms(2, 100, 1, 100);
}
@Test
@SmallTest
public void touchDownForPrefetch_PrefetchHit() {
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(
OmniboxMetrics.HISTOGRAM_SEARCH_PREFETCH_TOUCH_DOWN_PREFETCH_RESULT,
OmniboxMetrics.PrefetchResult.HIT)
.expectIntRecord(
OmniboxMetrics
.HISTOGRAM_SEARCH_PREFETCH_NUM_PREFETCHES_STARTED_IN_OMNIBOX_SESSION,
1)
.build();
mMediator.setAutocompleteProfile(mProfile);
when(mLocationBarDataProvider.hasTab()).thenReturn(false);
when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
.thenReturn(true);
setSuggestionNativeObjectRef();
mMediator.onNativeInitialized();
// Simulate omnibox session start, and offer suggestions.
mMediator.onOmniboxSessionStateChange(true);
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
// Simulate a suggestion being touched down.
mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), /* matchIndex= */ 0);
// Ensure that no extra signals are sent to native.
verify(mAutocompleteController, times(1))
.onSuggestionTouchDown(mSuggestionsList.get(0), 0, null);
// Simulate a navigation to the suggestion that was prefetched. This causes metrics about
// prefetch to be recorded.
mMediator.onSuggestionClicked(
mSuggestionsList.get(0), /* matchIndex= */ 0, JUnitTestGURLs.URL_1);
// Ends the omnibox session to reset state of touch down prefetch, and record metrics.
mMediator.onOmniboxSessionStateChange(false);
histogramWatcher.assertExpected();
}
@Test
@SmallTest
public void touchDownForPrefetch_PrefetchMiss() {
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(
OmniboxMetrics.HISTOGRAM_SEARCH_PREFETCH_TOUCH_DOWN_PREFETCH_RESULT,
OmniboxMetrics.PrefetchResult.MISS)
.expectIntRecord(
OmniboxMetrics
.HISTOGRAM_SEARCH_PREFETCH_NUM_PREFETCHES_STARTED_IN_OMNIBOX_SESSION,
1)
.build();
mMediator.setAutocompleteProfile(mProfile);
when(mLocationBarDataProvider.hasTab()).thenReturn(false);
when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
.thenReturn(true);
setSuggestionNativeObjectRef();
mMediator.onNativeInitialized();
// Simulate omnibox session start, and offer suggestions.
mMediator.onOmniboxSessionStateChange(true);
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
// Simulate a suggestion being touched down.
mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), /* matchIndex= */ 0);
// Ensure that no extra signals are sent to native.
verify(mAutocompleteController, times(1))
.onSuggestionTouchDown(mSuggestionsList.get(0), 0, null);
// Simulate a navigation to a suggestion that was not prefetched. This causes metrics about
// prefetch to be recorded.
mMediator.onSuggestionClicked(
mSuggestionsList.get(1), /* matchIndex= */ 1, JUnitTestGURLs.URL_1);
// Ends the omnibox session to reset state of touch down prefetch, and record metrics.
mMediator.onOmniboxSessionStateChange(false);
histogramWatcher.assertExpected();
}
@Test
@SmallTest
public void touchDownForPrefetch_NoPrefetch() {
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(
OmniboxMetrics.HISTOGRAM_SEARCH_PREFETCH_TOUCH_DOWN_PREFETCH_RESULT,
OmniboxMetrics.PrefetchResult.NO_PREFETCH)
.expectIntRecord(
OmniboxMetrics
.HISTOGRAM_SEARCH_PREFETCH_NUM_PREFETCHES_STARTED_IN_OMNIBOX_SESSION,
0)
.build();
mMediator.setAutocompleteProfile(mProfile);
when(mLocationBarDataProvider.hasTab()).thenReturn(false);
setSuggestionNativeObjectRef();
mMediator.onNativeInitialized();
// Simulate omnibox session start, and offer suggestions.
mMediator.onOmniboxSessionStateChange(true);
mMediator.onSuggestionsReceived(mAutocompleteResult, /* isFinal= */ true);
// This will simulate the touch down trigger not starting a prefetch.
when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
.thenReturn(false);
// Simulate a suggestion being touched down.
mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), /* matchIndex= */ 0);
// Ensure that no extra signals are sent to native.
verify(mAutocompleteController, times(1))
.onSuggestionTouchDown(mSuggestionsList.get(0), 0, null);
// Simulate a navigation to the suggestion that was not prefetched. This causes metrics
// about prefetch to be recorded.
mMediator.onSuggestionClicked(
mSuggestionsList.get(0), /* matchIndex= */ 0, JUnitTestGURLs.URL_1);
// Ends the omnibox session to reset state of touch down prefetch, and record metrics.
mMediator.onOmniboxSessionStateChange(false);
histogramWatcher.assertExpected();
}
@Test
@SmallTest
public void touchDownForPrefetch_LimitNumPrefetches() {
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(
OmniboxMetrics
.HISTOGRAM_SEARCH_PREFETCH_NUM_PREFETCHES_STARTED_IN_OMNIBOX_SESSION,
OmniboxFeatures.DEFAULT_MAX_PREFETCHES_PER_OMNIBOX_SESSION)
.expectIntRecord(
OmniboxMetrics
.HISTOGRAM_SEARCH_PREFETCH_NUM_PREFETCHES_STARTED_IN_OMNIBOX_SESSION,
1)
.build();
mMediator.setAutocompleteProfile(mProfile);
when(mLocationBarDataProvider.hasTab()).thenReturn(false);
when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
.thenReturn(true);
setSuggestionNativeObjectRef();
mMediator.onNativeInitialized();
// Simulate omnibox session start.
mMediator.onOmniboxSessionStateChange(true);
// Triggeer one touch down event the maximum allowed. The extra event should not be sent to
// native.
int numTouchDownEvents = OmniboxFeatures.DEFAULT_MAX_PREFETCHES_PER_OMNIBOX_SESSION + 1;
Assert.assertTrue(numTouchDownEvents < mSuggestionsList.size());
for (int i = 0; i < numTouchDownEvents; i++) {
mMediator.onSuggestionTouchDown(mSuggestionsList.get(i), i);
}
// Ensure that no extra signals are sent to native.
verify(
mAutocompleteController,
times(OmniboxFeatures.DEFAULT_MAX_PREFETCHES_PER_OMNIBOX_SESSION))
.onSuggestionTouchDown(any(), anyInt(), any());
// Ends the omnibox session to reset state of touch down prefetch, and record metrics.
mMediator.onOmniboxSessionStateChange(false);
// Since the state is reset, new prefetches are allowed.
// Simulate a new omnibox session start.
mMediator.onOmniboxSessionStateChange(true);
mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), 0);
verify(
mAutocompleteController,
times(OmniboxFeatures.DEFAULT_MAX_PREFETCHES_PER_OMNIBOX_SESSION + 1))
.onSuggestionTouchDown(any(), anyInt(), any());
mMediator.onOmniboxSessionStateChange(false);
histogramWatcher.assertExpected();
}
@Test
public void onTopResumedActivityChanged_toActive() {
mMediator.onTopResumedActivityChanged(true);
verify(mAutocompleteDelegate, never()).clearOmniboxFocus();
}
@Test
public void onTopResumedActivityChanged_toNonActive() {
mMediator.onTopResumedActivityChanged(false);
Assert.assertEquals(mMediator.getEditSessionStateForTest(), EditSessionState.INACTIVE);
verify(mAutocompleteDelegate, times(1)).clearOmniboxFocus();
}
@Test
public void onTextChanged_cachedZpsEligibleOnSelectPageClasses() {
Set<Integer> eligibleClasses =
Set.of(
PageClassification.ANDROID_SEARCH_WIDGET_VALUE,
PageClassification.ANDROID_SHORTCUTS_WIDGET_VALUE);
doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
for (var pageClass : PageClassification.values()) {
setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
mMediator.onTextChanged("");
// Should only be invoked if page class is eligible.
int numTimesInvoked = eligibleClasses.contains(pageClass.getNumber()) ? 1 : 0;
verify(ShadowCachedSuggestionsManager.mock, times(numTimesInvoked)).readFromCache();
verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
clearInvocations(ShadowCachedSuggestionsManager.mock);
}
}
@Test
public void onTextChanged_cachedZpsNotInvokedInTypedContext() {
doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
for (var pageClass : PageClassification.values()) {
setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
mMediator.onTextChanged("text");
// Should only be invoked if page class is eligible.
verify(ShadowCachedSuggestionsManager.mock, never()).readFromCache();
verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
clearInvocations(ShadowCachedSuggestionsManager.mock);
}
}
@Test
public void onTextChanged_cachedZpsNotInvokedWithAutocompleteControllerReady() {
doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
mMediator.setAutocompleteProfile(mProfile);
for (var pageClass : PageClassification.values()) {
setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
mMediator.onTextChanged("");
// Should only be invoked if page class is eligible.
verify(ShadowCachedSuggestionsManager.mock, never()).readFromCache();
verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
clearInvocations(ShadowCachedSuggestionsManager.mock);
}
}
@Test
public void onTextChanged_cacheZpsFromEligiblePageClasses() {
Set<Integer> eligibleClasses = Set.of(PageClassification.ANDROID_SEARCH_WIDGET_VALUE);
mMediator.onOmniboxSessionStateChange(true);
doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
doReturn(false).when(mAutocompleteResult).isFromCachedResult();
for (var pageClass : PageClassification.values()) {
setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
mMediator.onTextChanged("");
// Should only be invoked if page class is eligible.
int numTimesInvoked = eligibleClasses.contains(pageClass.getNumber()) ? 1 : 0;
verify(ShadowCachedSuggestionsManager.mock, times(numTimesInvoked)).saveToCache(any());
clearInvocations(ShadowCachedSuggestionsManager.mock);
}
}
@Test
public void onTextChanged_dontCacheTypedSuggestions() {
doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
doReturn(false).when(mAutocompleteResult).isFromCachedResult();
for (var pageClass : PageClassification.values()) {
setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
mMediator.onTextChanged("x");
verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
clearInvocations(ShadowCachedSuggestionsManager.mock);
}
}
@Test
public void onTextChanged_dontCacheCachedSuggestions() {
doReturn(mAutocompleteResult).when(ShadowCachedSuggestionsManager.mock).readFromCache();
doReturn(true).when(mAutocompleteResult).isFromCachedResult();
for (var pageClass : PageClassification.values()) {
setUpLocationBarDataProvider(PAGE_URL, PAGE_TITLE, pageClass.getNumber());
mMediator.onTextChanged("");
verify(ShadowCachedSuggestionsManager.mock, never()).saveToCache(any());
clearInvocations(ShadowCachedSuggestionsManager.mock);
}
}
@Test
public void updateVisualsForState_informsVisualStateObserver() {
mMediator.updateVisualsForState(BrandedColorScheme.LIGHT_BRANDED_THEME);
verify(mVisualStateObserver)
.onOmniboxSuggestionsBackgroundColorChanged(
eq(
OmniboxResourceProvider
.getSuggestionsDropdownStandardBackgroundColor(mActivity)));
mMediator.updateVisualsForState(BrandedColorScheme.INCOGNITO);
verify(mVisualStateObserver)
.onOmniboxSuggestionsBackgroundColorChanged(
eq(
OmniboxResourceProvider
.getSuggestionsDropdownIncognitoBackgroundColor(
mActivity)));
}
@Test
public void clearSuggestions_informsVisualStateObserver() {
mMediator.onNativeInitialized();
mMediator.setAutocompleteProfile(mProfile);
mMediator.clearSuggestions();
}
@Test
public void propagateOmniboxSessionStateChange_informsVisualStateObserver() {
mMediator.propagateOmniboxSessionStateChange(true);
verify(mVisualStateObserver, atLeastOnce()).onOmniboxSessionStateChange(eq(true));
mMediator.propagateOmniboxSessionStateChange(false);
verify(mVisualStateObserver, atLeastOnce()).onOmniboxSessionStateChange(eq(false));
}
@Test
public void onOmniboxAnswerActionClicked() {
mMediator.setAutocompleteProfile(mProfile);
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
OmniboxAnswerAction answerAction =
(OmniboxAnswerAction)
OmniboxActionFactoryImpl.get()
.buildOmniboxAnswerAction(123L, "7 day forecast", "7 day forecast");
mMediator.onSuggestionsReceived(
AutocompleteResult.fromCache(mSuggestionsList, null), /* isFinal= */ true);
mMediator.onOmniboxActionClicked(answerAction, 0);
verify(mAutocompleteDelegate).loadUrl(mOmniboxLoadUrlParamsCaptor.capture());
}
@SmallTest
@EnableFeatures(OmniboxFeatureList.ANIMATE_SUGGESTIONS_LIST_APPEARANCE)
public void onOmniboxSessionStateChange_attachesImeCallback() {
mMediator.onNativeInitialized();
mMediator.onOmniboxSessionStateChange(true);
verify(mDeferredImeCallback).attach(mWindowAndroid);
mMediator.onOmniboxSessionStateChange(false);
verify(mDeferredImeCallback).detach();
}
@Test
public void bailWhenPageClassGone() {
mMediator.setAutocompleteProfile(mProfile);
when(mAutocompleteDelegate.isUrlBarFocused()).thenReturn(true);
when(mAutocompleteDelegate.didFocusUrlFromFakebox()).thenReturn(false);
GURL url = JUnitTestGURLs.BLUE_1;
String title = "Title";
int pageClassification = PageClassification.BLANK_VALUE;
setUpLocationBarDataProvider(url, title, pageClassification);
when(mTextStateProvider.getTextWithAutocomplete()).thenReturn("");
mMediator.onNativeInitialized();
mMediator.onTextChanged("");
verify(mAutocompleteController, never())
.startZeroSuggest("", url, pageClassification, title);
mMediator.onTextChanged("t");
verify(mAutocompleteController, never())
.start(any(), anyInt(), any(), anyInt(), anyBoolean());
}
}