[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: Filip Gorski <fgorski@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606886}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
index 76d304f..97cbe7c 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
@@ -109,27 +109,14 @@
* Reports how long a user spends on 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) {
- // We cannot assume that the |mNativeFeedLoggingBridge| is always available like other
+ public void onContentTargetVisited(long visitTimeMs, boolean isOffline) {
+ // 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) {
- nativeOnContentTargetVisited(mNativeFeedLoggingBridge, visitTimeMs);
- }
- }
-
- /**
- * 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);
+ nativeOnContentTargetVisited(mNativeFeedLoggingBridge, visitTimeMs, isOffline);
}
}
@@ -171,6 +158,5 @@
private native void nativeOnOpenedWithNoImmediateContent(long nativeFeedLoggingBridge);
private native void nativeOnOpenedWithNoContent(long nativeFeedLoggingBridge);
private native void nativeOnContentTargetVisited(
- long nativeFeedLoggingBridge, long visitTimeMs);
- private native void nativeOnOfflinePageVisited(long nativeFeedLoggingBridge, long visitTimeMs);
+ long nativeFeedLoggingBridge, long visitTimeMs, boolean isOffline);
}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/action/FeedActionHandler.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/action/FeedActionHandler.java
index 1776ee8..9621112 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/action/FeedActionHandler.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/action/FeedActionHandler.java
@@ -140,26 +140,38 @@
private void openOfflineIfPossible(int disposition, String url) {
Long maybeOfflineId = mOfflineIndicator.getOfflineIdIfPageIsOfflined(url);
if (maybeOfflineId == null) {
- Tab loadingTab = mDelegate.openUrl(disposition, createLoadUrlParams(url));
- if (loadingTab != null) {
- // Records how long the user spending on the suggested page.
- NavigationRecorder.record(loadingTab, visitData -> {
- mLoggingBridge.onContentTargetVisited(visitData.duration);
- });
- }
+ openAndRecord(disposition, createLoadUrlParams(url), /*isOffline*/ false);
} else {
mOfflinePageBridge.getLoadUrlParamsByOfflineId(
maybeOfflineId, LaunchLocation.SUGGESTION, (loadUrlParams) -> {
- loadUrlParams.setVerbatimHeaders(loadUrlParams.getExtraHeadersString());
- Tab loadingTab = mDelegate.openUrl(disposition, loadUrlParams);
- if (loadingTab != null) {
- // Records how long the user spending on the offline page.
- NavigationRecorder.record(loadingTab, visitData -> {
- mLoggingBridge.onOfflinePageVisited(visitData.duration);
- });
+ 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());
+ openAndRecord(disposition, loadUrlParams, /*isOffline*/ true);
}
});
}
+ }
+ /**
+ * 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();
}
}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/action/FeedActionHandlerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/action/FeedActionHandlerTest.java
index d28b9c0..d5823a20 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/action/FeedActionHandlerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/action/FeedActionHandlerTest.java
@@ -6,10 +6,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
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.doNothing;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,6 +30,7 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
@@ -36,7 +40,11 @@
import org.chromium.chrome.browser.feed.FeedOfflineIndicator;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
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.NavigationController;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.mojom.WindowOpenDisposition;
import java.util.Collections;
@@ -61,6 +69,12 @@
private OfflinePageBridge mOfflinePageBridge;
@Mock
private FeedLoggingBridge mLoggingBridge;
+ @Mock
+ private Tab mTab;
+ @Mock
+ private WebContents mWebContents;
+ @Mock
+ private NavigationController mNavigationController;
@Captor
private ArgumentCaptor<Integer> mDispositionCapture;
@@ -69,8 +83,11 @@
@Captor
private ArgumentCaptor<Long> mOfflineIdCapture;
@Captor
- private ArgumentCaptor<Callback<LoadUrlParams>> mLoadUrlParamsCallbackCapture;
+ private ArgumentCaptor<Callback<LoadUrlParams>> mLoadUrlParamsCallbackCaptor;
+ @Captor
+ ArgumentCaptor<WebContentsObserver> mWebContentsObserverCaptor;
+ int mLastCommittedEntryIndex = 0;
private FeedActionHandler mActionHandler;
private void verifyOpenedOffline(int expectedDisposition) {
@@ -97,22 +114,43 @@
mActionHandler = new FeedActionHandler(mDelegate, mSuggestionConsumedObserver,
mOfflineIndicator, mOfflinePageBridge, mLoggingBridge);
- doAnswer(invocation -> {
- LoadUrlParams params = new LoadUrlParams("");
- params.setExtraHeaders(Collections.singletonMap("", OFFLINE_ID.toString()));
- mLoadUrlParamsCallbackCapture.getValue().onResult(params);
+ // Setup mocks such that when NavigationRecorder#record is called, it immediately invokes
+ // the passed callback.
+ when(mDelegate.openUrl(anyInt(), any(LoadUrlParams.class))).thenReturn(mTab);
+ when(mTab.getWebContents()).thenReturn(mWebContents);
+ when(mWebContents.getNavigationController()).thenReturn(mNavigationController);
+ when(mNavigationController.getLastCommittedEntryIndex())
+ .thenReturn(mLastCommittedEntryIndex++);
+ doAnswer((InvocationOnMock invocation) -> {
+ mWebContentsObserverCaptor.getValue().navigationEntryCommitted();
return null;
})
- .when(mOfflinePageBridge)
- .getLoadUrlParamsByOfflineId(mOfflineIdCapture.capture(), anyInt(),
- mLoadUrlParamsCallbackCapture.capture());
- doNothing().when(mLoggingBridge).onContentTargetVisited(anyLong());
+ .when(mWebContents)
+ .addObserver(mWebContentsObserverCaptor.capture());
Map<String, Boolean> featureMap = new ArrayMap<>();
featureMap.put(ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS, true);
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
public void tearDown() {
ChromeFeatureList.setTestFeatures(null);
@@ -120,18 +158,32 @@
@Test
@SmallTest
- public void testOpenUrlOnline() {
+ public void testOpenUrlOffline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
+ answerWithGoodParams();
mActionHandler.openUrl(TEST_URL);
verifyOpenedOffline(WindowOpenDisposition.CURRENT_TAB);
+ verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(true));
}
@Test
@SmallTest
- public void testOpenUrlOffline() {
+ public void testOpenUrlOnline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null);
mActionHandler.openUrl(TEST_URL);
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
@@ -147,34 +199,40 @@
@Test
@SmallTest
- public void testOpenUrlInNewTabOnline() {
+ public void testOpenUrlInNewTabOffline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
+ answerWithGoodParams();
mActionHandler.openUrlInNewTab(TEST_URL);
verifyOpenedOffline(WindowOpenDisposition.NEW_BACKGROUND_TAB);
+ verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(true));
}
@Test
@SmallTest
- public void testOpenUrlInNewTabOffline() {
+ public void testOpenUrlInNewTabOnline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null);
mActionHandler.openUrlInNewTab(TEST_URL);
verifyOpenedOnline(WindowOpenDisposition.NEW_BACKGROUND_TAB);
- }
-
- @Test
- @SmallTest
- public void testOpenUrlInNewWindowOnline() {
- when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
- mActionHandler.openUrlInNewWindow(TEST_URL);
- verifyOpenedOffline(WindowOpenDisposition.NEW_WINDOW);
+ verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(false));
}
@Test
@SmallTest
public void testOpenUrlInNewWindowOffline() {
+ when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(OFFLINE_ID);
+ answerWithGoodParams();
+ mActionHandler.openUrlInNewWindow(TEST_URL);
+ verifyOpenedOffline(WindowOpenDisposition.NEW_WINDOW);
+ verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(true));
+ }
+
+ @Test
+ @SmallTest
+ public void testOpenUrlInNewWindowOnline() {
when(mOfflineIndicator.getOfflineIdIfPageIsOfflined(TEST_URL)).thenReturn(null);
mActionHandler.openUrlInNewWindow(TEST_URL);
verifyOpenedOnline(WindowOpenDisposition.NEW_WINDOW);
+ verify(mLoggingBridge, times(1)).onContentTargetVisited(anyLong(), /*isOffline*/ eq(false));
}
@Test
@@ -188,4 +246,17 @@
// and as such the load params should not be updated with the offline id.
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());
+ }
}
diff --git a/chrome/browser/android/feed/feed_logging_bridge.cc b/chrome/browser/android/feed/feed_logging_bridge.cc
index 22f8430..33934e6 100644
--- a/chrome/browser/android/feed/feed_logging_bridge.cc
+++ b/chrome/browser/android/feed/feed_logging_bridge.cc
@@ -123,17 +123,15 @@
void FeedLoggingBridge::OnContentTargetVisited(
JNIEnv* j_env,
const base::android::JavaRef<jobject>& j_this,
- const jlong visit_time_ms) {
- feed_logging_metrics_->OnSuggestionArticleVisited(
- base::TimeDelta::FromMilliseconds(visit_time_ms));
-}
-
-void FeedLoggingBridge::OnOfflinePageVisited(
- JNIEnv* j_env,
- const base::android::JavaRef<jobject>& j_this,
- const jlong visit_time_ms) {
- feed_logging_metrics_->OnSuggestionOfflinePageVisited(
- base::TimeDelta::FromMilliseconds(visit_time_ms));
+ const jlong visit_time_ms,
+ const jboolean is_offline) {
+ if (is_offline) {
+ feed_logging_metrics_->OnSuggestionOfflinePageVisited(
+ base::TimeDelta::FromMilliseconds(visit_time_ms));
+ } else {
+ feed_logging_metrics_->OnSuggestionArticleVisited(
+ base::TimeDelta::FromMilliseconds(visit_time_ms));
+ }
}
} // namespace feed
diff --git a/chrome/browser/android/feed/feed_logging_bridge.h b/chrome/browser/android/feed/feed_logging_bridge.h
index f0ee72f4..a67e794e 100644
--- a/chrome/browser/android/feed/feed_logging_bridge.h
+++ b/chrome/browser/android/feed/feed_logging_bridge.h
@@ -73,11 +73,8 @@
void OnContentTargetVisited(JNIEnv* j_env,
const base::android::JavaRef<jobject>& j_this,
- const jlong visit_time_ms);
-
- void OnOfflinePageVisited(JNIEnv* j_env,
- const base::android::JavaRef<jobject>& j_this,
- const jlong visit_time_ms);
+ const jlong visit_time_ms,
+ const jboolean is_offline);
private:
FeedLoggingMetrics* feed_logging_metrics_;