Commit 60b47f98 authored by Sky Malice's avatar Sky Malice Committed by Commit Bot

[Feed] Check loadUrlParams for null, fallback to opening online.

Bug: 901179
Change-Id: Ieb3a421a7930ddc0014f7ef57b6a8f6d89dc3615
Reviewed-on: https://chromium-review.googlesource.com/c/1315358
Commit-Queue: Sky Malice <skym@chromium.org>
Reviewed-by: default avatarFilip Gorski <fgorski@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606886}
parent 0475284a
...@@ -109,27 +109,14 @@ public class FeedLoggingBridge implements BasicLoggingApi { ...@@ -109,27 +109,14 @@ public class FeedLoggingBridge implements BasicLoggingApi {
* Reports how long a user spends on the page. * Reports how long a user spends on the page.
* *
* @param visitTimeMs Time spent reading the page. * @param visitTimeMs Time spent reading the page.
* @param isOffline If the page is viewed in offline mode or not.
*/ */
public void onContentTargetVisited(long visitTimeMs) { public void onContentTargetVisited(long visitTimeMs, boolean isOffline) {
// We cannot assume that the |mNativeFeedLoggingBridge| is always available like other // We cannot assume that the|mNativeFeedLoggingBridge| is always available like other
// methods. This method is called by objects not controlled by Feed lifetimes, and destroy() // methods. This method is called by objects not controlled by Feed lifetimes, and destroy()
// may have already been called if Feed is disabled by policy. // may have already been called if Feed is disabled by policy.
if (mNativeFeedLoggingBridge != 0) { if (mNativeFeedLoggingBridge != 0) {
nativeOnContentTargetVisited(mNativeFeedLoggingBridge, visitTimeMs); nativeOnContentTargetVisited(mNativeFeedLoggingBridge, visitTimeMs, isOffline);
}
}
/**
* Reports how long a user spends on the offline page.
*
* @param visitTimeMs Time spent reading the page.
*/
public void onOfflinePageVisited(long visitTimeMs) {
// We cannot assume that the |mNativeFeedLoggingBridge| is always available like other
// methods. This method is called by objects not controlled by Feed lifetimes, and destroy()
// may have already been called if Feed is disabled by policy.
if (mNativeFeedLoggingBridge != 0) {
nativeOnOfflinePageVisited(mNativeFeedLoggingBridge, visitTimeMs);
} }
} }
...@@ -171,6 +158,5 @@ public class FeedLoggingBridge implements BasicLoggingApi { ...@@ -171,6 +158,5 @@ public class FeedLoggingBridge implements BasicLoggingApi {
private native void nativeOnOpenedWithNoImmediateContent(long nativeFeedLoggingBridge); private native void nativeOnOpenedWithNoImmediateContent(long nativeFeedLoggingBridge);
private native void nativeOnOpenedWithNoContent(long nativeFeedLoggingBridge); private native void nativeOnOpenedWithNoContent(long nativeFeedLoggingBridge);
private native void nativeOnContentTargetVisited( private native void nativeOnContentTargetVisited(
long nativeFeedLoggingBridge, long visitTimeMs); long nativeFeedLoggingBridge, long visitTimeMs, boolean isOffline);
private native void nativeOnOfflinePageVisited(long nativeFeedLoggingBridge, long visitTimeMs);
} }
...@@ -140,26 +140,38 @@ public class FeedActionHandler implements ActionApi { ...@@ -140,26 +140,38 @@ public class FeedActionHandler implements ActionApi {
private void openOfflineIfPossible(int disposition, String url) { private void openOfflineIfPossible(int disposition, String url) {
Long maybeOfflineId = mOfflineIndicator.getOfflineIdIfPageIsOfflined(url); Long maybeOfflineId = mOfflineIndicator.getOfflineIdIfPageIsOfflined(url);
if (maybeOfflineId == null) { if (maybeOfflineId == null) {
Tab loadingTab = mDelegate.openUrl(disposition, createLoadUrlParams(url)); openAndRecord(disposition, createLoadUrlParams(url), /*isOffline*/ false);
if (loadingTab != null) {
// Records how long the user spending on the suggested page.
NavigationRecorder.record(loadingTab, visitData -> {
mLoggingBridge.onContentTargetVisited(visitData.duration);
});
}
} else { } else {
mOfflinePageBridge.getLoadUrlParamsByOfflineId( mOfflinePageBridge.getLoadUrlParamsByOfflineId(
maybeOfflineId, LaunchLocation.SUGGESTION, (loadUrlParams) -> { maybeOfflineId, LaunchLocation.SUGGESTION, (loadUrlParams) -> {
if (loadUrlParams == null) {
// Fall back to opening online if the lookup failed.
openAndRecord(
disposition, createLoadUrlParams(url), /*isOffline*/ false);
} else {
// Offline headers need to be moved to be read correctly.
loadUrlParams.setVerbatimHeaders(loadUrlParams.getExtraHeadersString()); loadUrlParams.setVerbatimHeaders(loadUrlParams.getExtraHeadersString());
Tab loadingTab = mDelegate.openUrl(disposition, loadUrlParams); openAndRecord(disposition, loadUrlParams, /*isOffline*/ true);
if (loadingTab != null) {
// Records how long the user spending on the offline page.
NavigationRecorder.record(loadingTab, visitData -> {
mLoggingBridge.onOfflinePageVisited(visitData.duration);
});
} }
}); });
} }
}
/**
* Opens the given resource, specified by params, and records how much time the user spends on
* the suggested page.
*
* @param disposition How to open the article.
* @param loadUrlParams Parameters specifying the URL to load and other navigation details.
* @param isOffline If the page should open in offline mode or not, for metrics reporting.
*/
private void openAndRecord(int disposition, LoadUrlParams loadUrlParams, boolean isOffline) {
Tab loadingTab = mDelegate.openUrl(disposition, loadUrlParams);
if (loadingTab != null) {
// Records how long the user spending on the suggested page.
NavigationRecorder.record(loadingTab,
visitData
-> mLoggingBridge.onContentTargetVisited(visitData.duration, isOffline));
}
mSuggestionConsumedObserver.run(); mSuggestionConsumedObserver.run();
} }
} }
...@@ -6,10 +6,13 @@ package org.chromium.chrome.browser.feed.action; ...@@ -6,10 +6,13 @@ package org.chromium.chrome.browser.feed.action;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
...@@ -27,6 +30,7 @@ import org.mockito.ArgumentCaptor; ...@@ -27,6 +30,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.chromium.base.Callback; import org.chromium.base.Callback;
...@@ -36,7 +40,11 @@ import org.chromium.chrome.browser.feed.FeedLoggingBridge; ...@@ -36,7 +40,11 @@ import org.chromium.chrome.browser.feed.FeedLoggingBridge;
import org.chromium.chrome.browser.feed.FeedOfflineIndicator; import org.chromium.chrome.browser.feed.FeedOfflineIndicator;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate; import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.mojom.WindowOpenDisposition; import org.chromium.ui.mojom.WindowOpenDisposition;
import java.util.Collections; import java.util.Collections;
...@@ -61,6 +69,12 @@ public class FeedActionHandlerTest { ...@@ -61,6 +69,12 @@ public class FeedActionHandlerTest {
private OfflinePageBridge mOfflinePageBridge; private OfflinePageBridge mOfflinePageBridge;
@Mock @Mock
private FeedLoggingBridge mLoggingBridge; private FeedLoggingBridge mLoggingBridge;
@Mock
private Tab mTab;
@Mock
private WebContents mWebContents;
@Mock
private NavigationController mNavigationController;
@Captor @Captor
private ArgumentCaptor<Integer> mDispositionCapture; private ArgumentCaptor<Integer> mDispositionCapture;
...@@ -69,8 +83,11 @@ public class FeedActionHandlerTest { ...@@ -69,8 +83,11 @@ public class FeedActionHandlerTest {
@Captor @Captor
private ArgumentCaptor<Long> mOfflineIdCapture; private ArgumentCaptor<Long> mOfflineIdCapture;
@Captor @Captor
private ArgumentCaptor<Callback<LoadUrlParams>> mLoadUrlParamsCallbackCapture; private ArgumentCaptor<Callback<LoadUrlParams>> mLoadUrlParamsCallbackCaptor;
@Captor
ArgumentCaptor<WebContentsObserver> mWebContentsObserverCaptor;
int mLastCommittedEntryIndex = 0;
private FeedActionHandler mActionHandler; private FeedActionHandler mActionHandler;
private void verifyOpenedOffline(int expectedDisposition) { private void verifyOpenedOffline(int expectedDisposition) {
...@@ -97,22 +114,43 @@ public class FeedActionHandlerTest { ...@@ -97,22 +114,43 @@ public class FeedActionHandlerTest {
mActionHandler = new FeedActionHandler(mDelegate, mSuggestionConsumedObserver, mActionHandler = new FeedActionHandler(mDelegate, mSuggestionConsumedObserver,
mOfflineIndicator, mOfflinePageBridge, mLoggingBridge); mOfflineIndicator, mOfflinePageBridge, mLoggingBridge);
doAnswer(invocation -> { // Setup mocks such that when NavigationRecorder#record is called, it immediately invokes
LoadUrlParams params = new LoadUrlParams(""); // the passed callback.
params.setExtraHeaders(Collections.singletonMap("", OFFLINE_ID.toString())); when(mDelegate.openUrl(anyInt(), any(LoadUrlParams.class))).thenReturn(mTab);
mLoadUrlParamsCallbackCapture.getValue().onResult(params); when(mTab.getWebContents()).thenReturn(mWebContents);
when(mWebContents.getNavigationController()).thenReturn(mNavigationController);
when(mNavigationController.getLastCommittedEntryIndex())
.thenReturn(mLastCommittedEntryIndex++);
doAnswer((InvocationOnMock invocation) -> {
mWebContentsObserverCaptor.getValue().navigationEntryCommitted();
return null; return null;
}) })
.when(mOfflinePageBridge) .when(mWebContents)
.getLoadUrlParamsByOfflineId(mOfflineIdCapture.capture(), anyInt(), .addObserver(mWebContentsObserverCaptor.capture());
mLoadUrlParamsCallbackCapture.capture());
doNothing().when(mLoggingBridge).onContentTargetVisited(anyLong());
Map<String, Boolean> featureMap = new ArrayMap<>(); Map<String, Boolean> featureMap = new ArrayMap<>();
featureMap.put(ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS, true); featureMap.put(ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS, true);
ChromeFeatureList.setTestFeatures(featureMap); ChromeFeatureList.setTestFeatures(featureMap);
} }
private void answerWithGoodParams() {
LoadUrlParams params = new LoadUrlParams("");
params.setExtraHeaders(Collections.singletonMap("", OFFLINE_ID.toString()));
answerWithGivenParams(params);
}
// Configures mOfflinePageBridge to run the passed callback when getLoadUrlParamsByOfflineId is
// called. If this isn't setup, the callback will never be invoked.
private void answerWithGivenParams(LoadUrlParams params) {
doAnswer(invocation -> {
mLoadUrlParamsCallbackCaptor.getValue().onResult(params);
return null;
})
.when(mOfflinePageBridge)
.getLoadUrlParamsByOfflineId(mOfflineIdCapture.capture(), anyInt(),
mLoadUrlParamsCallbackCaptor.capture());
}
@After @After
public void tearDown() { public void tearDown() {
ChromeFeatureList.setTestFeatures(null); ChromeFeatureList.setTestFeatures(null);
...@@ -120,18 +158,32 @@ public class FeedActionHandlerTest { ...@@ -120,18 +158,32 @@ public class FeedActionHandlerTest {
@Test @Test
@SmallTest @SmallTest
public void testOpenUrlOnline() { public void testOpenUrlOffline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID); when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
answerWithGoodParams();
mActionHandler.openUrl(TEST_URL); mActionHandler.openUrl(TEST_URL);
verifyOpenedOffline(WindowOpenDisposition.CURRENT_TAB); verifyOpenedOffline(WindowOpenDisposition.CURRENT_TAB);
verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(true));
} }
@Test @Test
@SmallTest @SmallTest
public void testOpenUrlOffline() { public void testOpenUrlOnline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null); when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null);
mActionHandler.openUrl(TEST_URL); mActionHandler.openUrl(TEST_URL);
verifyOpenedOnline(WindowOpenDisposition.CURRENT_TAB); verifyOpenedOnline(WindowOpenDisposition.CURRENT_TAB);
verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(false));
}
@Test
@SmallTest
public void testOpenUrlOfflineWithNullParams() {
// The indicator will give an offline id, but the model returns a null LoadUrlParams.
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
answerWithGivenParams(null);
mActionHandler.openUrl(TEST_URL);
verifyOpenedOnline(WindowOpenDisposition.CURRENT_TAB);
verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(false));
} }
@Test @Test
...@@ -147,34 +199,40 @@ public class FeedActionHandlerTest { ...@@ -147,34 +199,40 @@ public class FeedActionHandlerTest {
@Test @Test
@SmallTest @SmallTest
public void testOpenUrlInNewTabOnline() { public void testOpenUrlInNewTabOffline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID); when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
answerWithGoodParams();
mActionHandler.openUrlInNewTab(TEST_URL); mActionHandler.openUrlInNewTab(TEST_URL);
verifyOpenedOffline(WindowOpenDisposition.NEW_BACKGROUND_TAB); verifyOpenedOffline(WindowOpenDisposition.NEW_BACKGROUND_TAB);
verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(true));
} }
@Test @Test
@SmallTest @SmallTest
public void testOpenUrlInNewTabOffline() { public void testOpenUrlInNewTabOnline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null); when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null);
mActionHandler.openUrlInNewTab(TEST_URL); mActionHandler.openUrlInNewTab(TEST_URL);
verifyOpenedOnline(WindowOpenDisposition.NEW_BACKGROUND_TAB); verifyOpenedOnline(WindowOpenDisposition.NEW_BACKGROUND_TAB);
verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(false));
} }
@Test @Test
@SmallTest @SmallTest
public void testOpenUrlInNewWindowOnline() { public void testOpenUrlInNewWindowOffline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID); when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
answerWithGoodParams();
mActionHandler.openUrlInNewWindow(TEST_URL); mActionHandler.openUrlInNewWindow(TEST_URL);
verifyOpenedOffline(WindowOpenDisposition.NEW_WINDOW); verifyOpenedOffline(WindowOpenDisposition.NEW_WINDOW);
verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(true));
} }
@Test @Test
@SmallTest @SmallTest
public void testOpenUrlInNewWindowOffline() { public void testOpenUrlInNewWindowOnline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null); when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null);
mActionHandler.openUrlInNewWindow(TEST_URL); mActionHandler.openUrlInNewWindow(TEST_URL);
verifyOpenedOnline(WindowOpenDisposition.NEW_WINDOW); verifyOpenedOnline(WindowOpenDisposition.NEW_WINDOW);
verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(false));
} }
@Test @Test
...@@ -188,4 +246,17 @@ public class FeedActionHandlerTest { ...@@ -188,4 +246,17 @@ public class FeedActionHandlerTest {
// and as such the load params should not be updated with the offline id. // and as such the load params should not be updated with the offline id.
verifyOpenedOnline(WindowOpenDisposition.SAVE_TO_DISK); verifyOpenedOnline(WindowOpenDisposition.SAVE_TO_DISK);
} }
@Test
@SmallTest
public void testOpenUrlNullTab() {
// If openUrl returns null, then the {@link NavigationRecorder} logic should be skipped.
reset(mDelegate);
when(mDelegate.openUrl(anyInt(), any(LoadUrlParams.class))).thenReturn(null);
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null);
mActionHandler.openUrl(TEST_URL);
verifyOpenedOnline(WindowOpenDisposition.CURRENT_TAB);
verify(mLoggingBridge, times(0)).onContentTargetVisited(anyLong(), anyBoolean());
}
} }
...@@ -123,17 +123,15 @@ void FeedLoggingBridge::OnOpenedWithNoContent( ...@@ -123,17 +123,15 @@ void FeedLoggingBridge::OnOpenedWithNoContent(
void FeedLoggingBridge::OnContentTargetVisited( void FeedLoggingBridge::OnContentTargetVisited(
JNIEnv* j_env, JNIEnv* j_env,
const base::android::JavaRef<jobject>& j_this, const base::android::JavaRef<jobject>& j_this,
const jlong visit_time_ms) { const jlong visit_time_ms,
feed_logging_metrics_->OnSuggestionArticleVisited( const jboolean is_offline) {
base::TimeDelta::FromMilliseconds(visit_time_ms)); if (is_offline) {
}
void FeedLoggingBridge::OnOfflinePageVisited(
JNIEnv* j_env,
const base::android::JavaRef<jobject>& j_this,
const jlong visit_time_ms) {
feed_logging_metrics_->OnSuggestionOfflinePageVisited( feed_logging_metrics_->OnSuggestionOfflinePageVisited(
base::TimeDelta::FromMilliseconds(visit_time_ms)); base::TimeDelta::FromMilliseconds(visit_time_ms));
} else {
feed_logging_metrics_->OnSuggestionArticleVisited(
base::TimeDelta::FromMilliseconds(visit_time_ms));
}
} }
} // namespace feed } // namespace feed
...@@ -73,11 +73,8 @@ class FeedLoggingBridge { ...@@ -73,11 +73,8 @@ class FeedLoggingBridge {
void OnContentTargetVisited(JNIEnv* j_env, void OnContentTargetVisited(JNIEnv* j_env,
const base::android::JavaRef<jobject>& j_this, const base::android::JavaRef<jobject>& j_this,
const jlong visit_time_ms); const jlong visit_time_ms,
const jboolean is_offline);
void OnOfflinePageVisited(JNIEnv* j_env,
const base::android::JavaRef<jobject>& j_this,
const jlong visit_time_ms);
private: private:
FeedLoggingMetrics* feed_logging_metrics_; FeedLoggingMetrics* feed_logging_metrics_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment