summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqinmin <qinmin@chromium.org>2015-08-07 11:32:39 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-07 18:33:14 +0000
commit976d4d9a2735bdc11e5a641c84b6382566d48f1d (patch)
tree84d3de6e08e47571eba894a48e0480f7e56b8870
parent5cf15225410b697e2b4e7b10ed1ad8923f8c2560 (diff)
downloadchromium_src-976d4d9a2735bdc11e5a641c84b6382566d48f1d.zip
chromium_src-976d4d9a2735bdc11e5a641c84b6382566d48f1d.tar.gz
chromium_src-976d4d9a2735bdc11e5a641c84b6382566d48f1d.tar.bz2
Pass user gesture bit when chrome handles an intent fired by itself
It is possible for user to choose chrome when url overriding results in an intent chooser. In this case, we should let everything work as if nothing has happened. However, intent handling will result in a new url request. And that causes chrome to lose the user gesture bit, and introduces some side effects. For example, when clicking a pdf link, the pdf will be auto opened by docs app. However, if Adobe pdf reader is installed, an intent chooser will be shown when clicking the pdf link. If user chooses chrome to handle the intent, the pdf will be downloaded, but not auto opened due to the missing user gesture BUG=512633 Review URL: https://codereview.chromium.org/1243253004 Cr-Commit-Position: refs/heads/master@{#342388}
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java2
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java3
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java9
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java2
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java16
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java5
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationParams.java26
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandler.java87
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/tab/ChromeTab.java1
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java5
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/util/IntentUtils.java13
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandlerTest.java48
-rw-r--r--chrome/browser/android/tab_android.cc4
-rw-r--r--chrome/browser/android/tab_android.h3
-rw-r--r--content/browser/frame_host/navigation_controller_impl.cc1
-rw-r--r--content/browser/frame_host/navigation_entry_impl.cc6
-rw-r--r--content/browser/frame_host/navigation_entry_impl.h11
-rw-r--r--content/common/frame_messages.h3
-rw-r--r--content/common/navigation_params.cc9
-rw-r--r--content/common/navigation_params.h7
-rw-r--r--content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java17
-rw-r--r--content/public/browser/navigation_controller.cc11
-rw-r--r--content/public/browser/navigation_controller.h18
-rw-r--r--content/renderer/render_frame_impl.cc3
24 files changed, 281 insertions, 29 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 1b6d991..c2c9df1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -896,7 +896,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
@Override
public void processUrlViewIntent(String url, String referer, String headers,
TabOpenType tabOpenType, String externalAppId, int tabIdToBringToFront,
- Intent intent) {
+ boolean hasUserGesture, Intent intent) {
}
};
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index bff30b8..b8ec53d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -619,7 +619,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ActionBarDel
@Override
public void processUrlViewIntent(String url, String referer, String headers,
TabOpenType tabOpenType, String externalAppId, int tabIdToBringToFront,
- Intent intent) {
+ boolean hasUserGesture, Intent intent) {
TabModel tabModel = getCurrentTabModel();
switch (tabOpenType) {
case REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB:
@@ -673,6 +673,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ActionBarDel
int transitionType = PageTransition.LINK | PageTransition.FROM_API;
LoadUrlParams loadUrlParams = new LoadUrlParams(url, transitionType);
loadUrlParams.setIntentReceivedTimestamp(mIntentHandlingTimeMs);
+ loadUrlParams.setHasUserGesture(hasUserGesture);
currentTab.loadUrl(loadUrlParams);
RecordUserAction.record("MobileTabClobbered");
} else {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index 10e7e65..0fa84e2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -26,6 +26,7 @@ import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
+import org.chromium.chrome.browser.externalnav.IntentWithGesturesHandler;
import org.chromium.chrome.browser.omnibox.AutocompleteController;
import org.chromium.chrome.browser.search_engines.TemplateUrlService;
import org.chromium.chrome.browser.tab.Tab;
@@ -217,7 +218,7 @@ public class IntentHandler {
*/
void processUrlViewIntent(String url, String referer, String headers,
TabOpenType tabOpenType, String externalAppId, int tabIdToBringToFront,
- Intent intent);
+ boolean hasUserGesture, Intent intent);
void processWebSearchIntent(String query);
}
@@ -289,7 +290,8 @@ public class IntentHandler {
public boolean onNewIntent(Context context, Intent intent) {
assert intentHasValidUrl(intent);
String url = getUrlFromIntent(intent);
-
+ boolean hasUserGesture =
+ IntentWithGesturesHandler.getInstance().getUserGestureAndClear(intent);
TabOpenType tabOpenType = getTabOpenType(intent);
int tabIdToBringToFront = IntentUtils.safeGetIntExtra(
intent, TabOpenType.BRING_TAB_TO_FRONT.name(), Tab.INVALID_TAB_ID);
@@ -304,7 +306,7 @@ public class IntentHandler {
// TODO(joth): Presumably this should check the action too.
mDelegate.processUrlViewIntent(url, referrerUrl, extraHeaders, tabOpenType,
IntentUtils.safeGetStringExtra(intent, Browser.EXTRA_APPLICATION_ID),
- tabIdToBringToFront, intent);
+ tabIdToBringToFront, hasUserGesture, intent);
recordExternalIntentSourceUMA(intent);
return true;
}
@@ -512,7 +514,6 @@ public class IntentHandler {
// and check it with isIntentChromeInternal.
intent.putExtra(TRUSTED_APPLICATION_CODE_EXTRA,
getAuthenticationToken(context.getApplicationContext()));
-
// It is crucial that we never leak the authentication token to other packages, because
// then the other package could be used to impersonate us/do things as us. Therefore,
// scope the real Intent to our package.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
index f28216f..1030fc9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
@@ -33,6 +33,7 @@ import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.compositor.resources.StaticResourcePreloads;
import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer;
import org.chromium.chrome.browser.device.DeviceClassManager;
+import org.chromium.chrome.browser.externalnav.IntentWithGesturesHandler;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.tabmodel.TabModelBase;
@@ -283,6 +284,7 @@ public class CompositorView
} else if (visibility == View.VISIBLE) {
mWindowAndroid.onVisibilityChanged(true);
}
+ IntentWithGesturesHandler.getInstance().clear();
}
@CalledByNative
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
index 090c232..fb35cb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
@@ -39,6 +39,7 @@ import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.WarmupManager;
import org.chromium.chrome.browser.WebappAuthenticator;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
+import org.chromium.chrome.browser.externalnav.IntentWithGesturesHandler;
import org.chromium.chrome.browser.firstrun.FirstRunFlowSequencer;
import org.chromium.chrome.browser.metrics.LaunchHistogram;
import org.chromium.chrome.browser.metrics.LaunchMetrics;
@@ -254,7 +255,7 @@ public class ChromeLauncherActivity extends Activity
@Override
public void processUrlViewIntent(String url, String referer, String headers,
IntentHandler.TabOpenType tabOpenType, String externalAppId,
- int tabIdToBringToFront, Intent intent) {
+ int tabIdToBringToFront, boolean hasUserGesture, Intent intent) {
assert false;
}
@@ -300,6 +301,9 @@ public class ChromeLauncherActivity extends Activity
maybePrefetchDnsInBackground();
+ boolean hasUserGesture =
+ IntentWithGesturesHandler.getInstance().getUserGestureAndClear(getIntent());
+
// Increment the Tab ID counter at this point since this Activity may not appear in
// getAppTasks() when DocumentTabModelSelector is initialized. This can potentially happen
// when Chrome is launched via the GSA/e200 search box and they relinquish their task.
@@ -316,7 +320,7 @@ public class ChromeLauncherActivity extends Activity
}
// Sometimes an Intent requests that the current Document get clobbered.
- if (clobberCurrentDocument(url)) return;
+ if (clobberCurrentDocument(url, hasUserGesture)) return;
// Try to retarget existing Documents before creating a new one.
boolean incognito = IntentUtils.safeGetBooleanExtra(getIntent(),
@@ -386,9 +390,10 @@ public class ChromeLauncherActivity extends Activity
/**
* If necessary, attempts to clobber the current DocumentActivity's tab with the given URL.
* @param url URL to display.
+ * @param hasUserGesture Whether the intent is launched from a previous user gesture.
* @return Whether or not the clobber was successful.
*/
- private boolean clobberCurrentDocument(String url) {
+ private boolean clobberCurrentDocument(String url, boolean hasUserGesture) {
boolean shouldOpenNewTab = IntentUtils.safeGetBooleanExtra(
getIntent(), Browser.EXTRA_CREATE_NEW_TAB, false);
String applicationId =
@@ -400,8 +405,11 @@ public class ChromeLauncherActivity extends Activity
if (tabId == Tab.INVALID_TAB_ID) return false;
// Try to clobber the page.
+ LoadUrlParams params = new LoadUrlParams(
+ url, PageTransition.LINK | PageTransition.FROM_API);
+ params.setHasUserGesture(hasUserGesture);
AsyncTabCreationParams data =
- new AsyncTabCreationParams(new LoadUrlParams(url), new Intent(getIntent()));
+ new AsyncTabCreationParams(params, new Intent(getIntent()));
AsyncTabCreationParamsManager.add(tabId, data);
if (!relaunchTask(tabId)) {
// Were not able to clobber, will fall through to handle in a new document.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java
index 778ae7d..d533ba8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java
@@ -360,6 +360,11 @@ public class ExternalNavigationHandler {
return OverrideUrlLoadingResult.NO_OVERRIDE;
}
}
+ // The intent can be used to launch Chrome itself, record the user
+ // gesture here so that it can be used later.
+ if (params.hasUserGesture()) {
+ IntentWithGesturesHandler.getInstance().onNewIntentWithGesture(intent);
+ }
if (mDelegate.startActivityIfNeeded(intent)) {
return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT;
} else {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationParams.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationParams.java
index dee735a..d056d36 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationParams.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationParams.java
@@ -43,6 +43,9 @@ public class ExternalNavigationParams {
/** Whether this navigation happens in main frame. */
private final boolean mIsMainFrame;
+ /** Whether this navigation is launched by user gesture. */
+ private final boolean mHasUserGesture;
+
/**
* Whether the current tab should be closed when an URL load was overridden and an
* intent launched.
@@ -51,8 +54,8 @@ public class ExternalNavigationParams {
private ExternalNavigationParams(String url, boolean isIncognito, String referrerUrl,
int pageTransition, boolean isRedirect, boolean appMustBeInForeground,
- TabRedirectHandler redirectHandler, Tab tab,
- boolean openInNewTab, boolean isBackgroundTabNavigation, boolean isMainFrame,
+ TabRedirectHandler redirectHandler, Tab tab, boolean openInNewTab,
+ boolean isBackgroundTabNavigation, boolean isMainFrame, boolean hasUserGesture,
boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent) {
mUrl = url;
mIsIncognito = isIncognito;
@@ -65,6 +68,7 @@ public class ExternalNavigationParams {
mOpenInNewTab = openInNewTab;
mIsBackgroundTabNavigation = isBackgroundTabNavigation;
mIsMainFrame = isMainFrame;
+ mHasUserGesture = hasUserGesture;
mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent =
shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent;
}
@@ -127,6 +131,11 @@ public class ExternalNavigationParams {
return mIsMainFrame;
}
+ /** @return Whether this navigation is launched by user gesture. */
+ public boolean hasUserGesture() {
+ return mHasUserGesture;
+ }
+
/**
* @return Whether the current tab should be closed when an URL load was overridden and an
* intent launched.
@@ -169,6 +178,9 @@ public class ExternalNavigationParams {
/** Whether this navigation happens in main frame. */
private boolean mIsMainFrame;
+ /** Whether this navigation is launched by user gesture. */
+ private boolean mHasUserGesture;
+
/**
* Whether the current tab should be closed when an URL load was overridden and an
* intent launched.
@@ -225,6 +237,12 @@ public class ExternalNavigationParams {
return this;
}
+ /** Sets whether this navigation happens in main frame. */
+ public Builder setHasUserGesture(boolean v) {
+ mHasUserGesture = v;
+ return this;
+ }
+
/** Sets whether the current tab should be closed when an URL load was overridden and an
* intent launched.
*/
@@ -237,8 +255,8 @@ public class ExternalNavigationParams {
public ExternalNavigationParams build() {
return new ExternalNavigationParams(mUrl, mIsIncognito, mReferrerUrl, mPageTransition,
mIsRedirect, mApplicationMustBeInForeground, mRedirectHandler,
- mTab, mOpenInNewTab, mIsBackgroundTabNavigation,
- mIsMainFrame, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent);
+ mTab, mOpenInNewTab, mIsBackgroundTabNavigation, mIsMainFrame,
+ mHasUserGesture, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent);
}
}
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandler.java
new file mode 100644
index 0000000..1fd2e78
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandler.java
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.externalnav;
+
+import android.content.Intent;
+
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.util.IntentUtils;
+
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+/**
+ * This class hold a token for the most recent launched external intent that has
+ * user gesture. If the external intent resolves to chrome itself, the user
+ * gesture will be applied to the new url request. Since there could be at most
+ * one intent chooser at a time, this class only stores the most recent intent
+ * that has user gesture. A previously launched intent with user gesture will
+ * have its token invalidated if a new one comes.
+ */
+public class IntentWithGesturesHandler {
+ /**
+ * Extra to record whether the intent is launched by user gesture.
+ */
+ public static final String EXTRA_USER_GESTURE_TOKEN =
+ "org.chromium.chrome.browser.user_gesture_token";
+
+ private static final Object INSTANCE_LOCK = new Object();
+ private static IntentWithGesturesHandler sIntentWithGesturesHandler;
+ private SecureRandom mSecureRandom;
+ private byte[] mIntentToken;
+ private String mUri;
+
+ /**
+ * Get the singleton instance of this object.
+ */
+ public static IntentWithGesturesHandler getInstance() {
+ synchronized (INSTANCE_LOCK) {
+ if (sIntentWithGesturesHandler == null) {
+ sIntentWithGesturesHandler = new IntentWithGesturesHandler();
+ }
+ }
+ return sIntentWithGesturesHandler;
+ }
+
+ private IntentWithGesturesHandler() {
+ mSecureRandom = new SecureRandom();
+ }
+
+ /**
+ * Generate a new token for the intent that has user gesture. This will
+ * invalidate the token on the previously launched intent with user gesture.
+ *
+ * @param intent Intent with user gesture.
+ */
+ public void onNewIntentWithGesture(Intent intent) {
+ mIntentToken = new byte[32];
+ mSecureRandom.nextBytes(mIntentToken);
+ intent.putExtra(EXTRA_USER_GESTURE_TOKEN, mIntentToken);
+ mUri = IntentHandler.getUrlFromIntent(intent);
+ }
+
+ /**
+ * Get the user gesture from the intent and clear the stored token.
+ *
+ * @param intent Intent that is used to launch chrome.
+ * @return true if the intent has user gesture, or false otherwise.
+ */
+ public boolean getUserGestureAndClear(Intent intent) {
+ if (mIntentToken == null || mUri == null) return false;
+ byte[] bytes = IntentUtils.safeGetByteArrayExtra(intent, EXTRA_USER_GESTURE_TOKEN);
+ boolean ret = (bytes != null) && Arrays.equals(bytes, mIntentToken)
+ && mUri.equals(IntentHandler.getUrlFromIntent(intent));
+ clear();
+ return ret;
+ }
+
+ /**
+ * Clear the gesture token.
+ */
+ public void clear() {
+ mIntentToken = null;
+ mUri = null;
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/ChromeTab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/ChromeTab.java
index ea22555..7e2900d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/ChromeTab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/ChromeTab.java
@@ -1204,6 +1204,7 @@ public class ChromeTab extends Tab {
.setOpenInNewTab(shouldCloseTab)
.setIsBackgroundTabNavigation(isHidden() && !isInitialTabLaunchInBackground)
.setIsMainFrame(navigationParams.isMainFrame)
+ .setHasUserGesture(navigationParams.hasUserGesture)
.setShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(shouldCloseTab
&& navigationParams.isMainFrame)
.build();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index a4a1fce..d5fd633 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -936,7 +936,8 @@ public class Tab implements ViewGroup.OnHierarchyChangeListener,
// TODO(ppi): Should we pass Referrer jobject and add JNI methods to read it
// from the native?
params.getReferrer() != null ? params.getReferrer().getPolicy() : 0,
- params.getIsRendererInitiated(), params.getIntentReceivedTimestamp());
+ params.getIsRendererInitiated(), params.getIntentReceivedTimestamp(),
+ params.getHasUserGesture());
for (TabObserver observer : mObservers) {
observer.onLoadUrl(this, params, loadType);
@@ -2822,7 +2823,7 @@ public class Tab implements ViewGroup.OnHierarchyChangeListener,
private native Profile nativeGetProfileAndroid(long nativeTabAndroid);
private native int nativeLoadUrl(long nativeTabAndroid, String url, String extraHeaders,
byte[] postData, int transition, String referrerUrl, int referrerPolicy,
- boolean isRendererInitiated, long intentReceivedTimestamp);
+ boolean isRendererInitiated, long intentReceivedTimestamp, boolean hasUserGesture);
private native void nativeSetActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url,
String title);
private native boolean nativePrint(long nativeTabAndroid);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/IntentUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/util/IntentUtils.java
index 401e269..a66722f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/IntentUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/IntentUtils.java
@@ -173,6 +173,19 @@ public class IntentUtils {
}
/**
+ * Just like {@link Intent#getByteArrayExtra(String)} but doesn't throw exceptions.
+ */
+ public static byte[] safeGetByteArrayExtra(Intent intent, String name) {
+ try {
+ return intent.getByteArrayExtra(name);
+ } catch (Throwable t) {
+ // Catches un-parceling exceptions.
+ Log.e(TAG, "getByteArrayExtra failed on intent " + intent);
+ return null;
+ }
+ }
+
+ /**
* @return a Binder from an Intent, or null.
*
* Creates a temporary copy of the extra Bundle, which is required as
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandlerTest.java
new file mode 100644
index 0000000..6651aa5
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandlerTest.java
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.externalnav;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Instrumentation tests for {@link IntentWithGesturesHandler}.
+ */
+public class IntentWithGesturesHandlerTest extends InstrumentationTestCase {
+
+ @Override
+ public void tearDown() throws Exception {
+ IntentWithGesturesHandler.getInstance().clear();
+ super.tearDown();
+ }
+
+ @SmallTest
+ public void testCanUseGestureTokenOnlyOnce() {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("content://abc"));
+ IntentWithGesturesHandler.getInstance().onNewIntentWithGesture(intent);
+ assertTrue(intent.hasExtra(IntentWithGesturesHandler.EXTRA_USER_GESTURE_TOKEN));
+ assertTrue(IntentWithGesturesHandler.getInstance().getUserGestureAndClear(intent));
+ assertFalse(IntentWithGesturesHandler.getInstance().getUserGestureAndClear(intent));
+ }
+
+ @SmallTest
+ public void testModifiedGestureToken() {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("content://abc"));
+ IntentWithGesturesHandler.getInstance().onNewIntentWithGesture(intent);
+ intent.setData(Uri.parse("content://xyz"));
+ assertFalse(IntentWithGesturesHandler.getInstance().getUserGestureAndClear(intent));
+ }
+
+ @SmallTest
+ public void testPreviousGestureToken() {
+ Intent intent1 = new Intent(Intent.ACTION_VIEW, Uri.parse("content://abc"));
+ IntentWithGesturesHandler.getInstance().onNewIntentWithGesture(intent1);
+ Intent intent2 = new Intent(Intent.ACTION_VIEW, Uri.parse("content://xyz"));
+ IntentWithGesturesHandler.getInstance().onNewIntentWithGesture(intent2);
+ assertFalse(IntentWithGesturesHandler.getInstance().getUserGestureAndClear(intent1));
+ }
+}
diff --git a/chrome/browser/android/tab_android.cc b/chrome/browser/android/tab_android.cc
index 84184b7..81b61a4 100644
--- a/chrome/browser/android/tab_android.cc
+++ b/chrome/browser/android/tab_android.cc
@@ -531,7 +531,8 @@ TabAndroid::TabLoadStatus TabAndroid::LoadUrl(JNIEnv* env,
jstring j_referrer_url,
jint referrer_policy,
jboolean is_renderer_initiated,
- jlong intent_received_timestamp) {
+ jlong intent_received_timestamp,
+ jboolean has_user_gesture) {
if (!web_contents())
return PAGE_LOAD_FAILED;
@@ -614,6 +615,7 @@ TabAndroid::TabLoadStatus TabAndroid::LoadUrl(JNIEnv* env,
}
load_params.is_renderer_initiated = is_renderer_initiated;
load_params.intent_received_timestamp = intent_received_timestamp;
+ load_params.has_user_gesture = has_user_gesture;
web_contents()->GetController().LoadURLWithParams(load_params);
}
return DEFAULT_PAGE_LOAD;
diff --git a/chrome/browser/android/tab_android.h b/chrome/browser/android/tab_android.h
index b5a8b4a..2c74e40 100644
--- a/chrome/browser/android/tab_android.h
+++ b/chrome/browser/android/tab_android.h
@@ -163,7 +163,8 @@ class TabAndroid : public CoreTabHelperDelegate,
jstring j_referrer_url,
jint referrer_policy,
jboolean is_renderer_initiated,
- jlong intent_received_timestamp);
+ jlong intent_received_timestamp,
+ jboolean has_user_gesture);
void SetActiveNavigationEntryTitleForUrl(JNIEnv* env,
jobject obj,
jstring jurl,
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 41e5b92..e447c0e 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -775,6 +775,7 @@ void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
base::TimeTicks() +
base::TimeDelta::FromMilliseconds(params.intent_received_timestamp));
}
+ entry->set_has_user_gesture(params.has_user_gesture);
#endif
switch (params.load_type) {
diff --git a/content/browser/frame_host/navigation_entry_impl.cc b/content/browser/frame_host/navigation_entry_impl.cc
index b51b7be0..70e01b6 100644
--- a/content/browser/frame_host/navigation_entry_impl.cc
+++ b/content/browser/frame_host/navigation_entry_impl.cc
@@ -118,6 +118,9 @@ NavigationEntryImpl::NavigationEntryImpl(SiteInstanceImpl* instance,
should_clear_history_list_(false),
can_load_local_resources_(false),
frame_tree_node_id_(-1) {
+#if defined(OS_ANDROID)
+ has_user_gesture_ = false;
+#endif
}
NavigationEntryImpl::~NavigationEntryImpl() {
@@ -466,6 +469,9 @@ StartNavigationParams NavigationEntryImpl::ConstructStartNavigationParams()
return StartNavigationParams(GetHasPostData(), extra_headers(),
browser_initiated_post_data,
+#if defined(OS_ANDROID)
+ has_user_gesture(),
+#endif
transferred_global_request_id().child_id,
transferred_global_request_id().request_id);
}
diff --git a/content/browser/frame_host/navigation_entry_impl.h b/content/browser/frame_host/navigation_entry_impl.h
index 4254e6a..8aec815 100644
--- a/content/browser/frame_host/navigation_entry_impl.h
+++ b/content/browser/frame_host/navigation_entry_impl.h
@@ -333,6 +333,14 @@ class CONTENT_EXPORT NavigationEntryImpl
const base::TimeTicks intent_received_timestamp) {
intent_received_timestamp_ = intent_received_timestamp;
}
+
+ bool has_user_gesture() const {
+ return has_user_gesture_;
+ }
+
+ void set_has_user_gesture (bool has_user_gesture) {
+ has_user_gesture_ = has_user_gesture;
+ }
#endif
private:
@@ -460,6 +468,9 @@ class CONTENT_EXPORT NavigationEntryImpl
// The time at which Chrome received the Android Intent that triggered this
// URL load operation. Reset at commit and not persisted.
base::TimeTicks intent_received_timestamp_;
+
+ // Whether the URL load carries a user gesture.
+ bool has_user_gesture_;
#endif
// Used to store extra data to support browser features. This member is not
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 52de800..1a52689 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -280,6 +280,9 @@ IPC_STRUCT_TRAITS_BEGIN(content::StartNavigationParams)
IPC_STRUCT_TRAITS_MEMBER(is_post)
IPC_STRUCT_TRAITS_MEMBER(extra_headers)
IPC_STRUCT_TRAITS_MEMBER(browser_initiated_post_data)
+#if defined(OS_ANDROID)
+ IPC_STRUCT_TRAITS_MEMBER(has_user_gesture)
+#endif
IPC_STRUCT_TRAITS_MEMBER(transferred_request_child_id)
IPC_STRUCT_TRAITS_MEMBER(transferred_request_request_id)
IPC_STRUCT_TRAITS_END()
diff --git a/content/common/navigation_params.cc b/content/common/navigation_params.cc
index 93a0378..5c5dfc7 100644
--- a/content/common/navigation_params.cc
+++ b/content/common/navigation_params.cc
@@ -73,6 +73,9 @@ BeginNavigationParams::BeginNavigationParams(std::string method,
StartNavigationParams::StartNavigationParams()
: is_post(false),
+#if defined(OS_ANDROID)
+ has_user_gesture(false),
+#endif
transferred_request_child_id(-1),
transferred_request_request_id(-1) {
}
@@ -81,11 +84,17 @@ StartNavigationParams::StartNavigationParams(
bool is_post,
const std::string& extra_headers,
const std::vector<unsigned char>& browser_initiated_post_data,
+#if defined(OS_ANDROID)
+ bool has_user_gesture,
+#endif
int transferred_request_child_id,
int transferred_request_request_id)
: is_post(is_post),
extra_headers(extra_headers),
browser_initiated_post_data(browser_initiated_post_data),
+#if defined(OS_ANDROID)
+ has_user_gesture(has_user_gesture),
+#endif
transferred_request_child_id(transferred_request_child_id),
transferred_request_request_id(transferred_request_request_id) {
}
diff --git a/content/common/navigation_params.h b/content/common/navigation_params.h
index 1862377..37b1af0 100644
--- a/content/common/navigation_params.h
+++ b/content/common/navigation_params.h
@@ -142,6 +142,9 @@ struct CONTENT_EXPORT StartNavigationParams {
bool is_post,
const std::string& extra_headers,
const std::vector<unsigned char>& browser_initiated_post_data,
+#if defined(OS_ANDROID)
+ bool has_user_gesture,
+#endif
int transferred_request_child_id,
int transferred_request_request_id);
~StartNavigationParams();
@@ -156,6 +159,10 @@ struct CONTENT_EXPORT StartNavigationParams {
// otherwise.
std::vector<unsigned char> browser_initiated_post_data;
+#if defined(OS_ANDROID)
+ bool has_user_gesture;
+#endif
+
// The following two members identify a previous request that has been
// created before this navigation is being transferred to a new render view.
// This serves the purpose of recycling the old request.
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java b/content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java
index 770c6ae..0f2cd2d 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java
@@ -39,6 +39,7 @@ public class LoadUrlParams {
boolean mCanLoadLocalResources;
boolean mIsRendererInitiated;
long mIntentReceivedTimestamp;
+ boolean mHasUserGesture;
/**
* Creates an instance with default page transition type.
@@ -407,6 +408,22 @@ public class LoadUrlParams {
return mIntentReceivedTimestamp;
}
+ /**
+ * Set whether the load is initiated by a user gesture.
+ *
+ * @param hasUserGesture True if load is initiated by user gesture, or false otherwise.
+ */
+ public void setHasUserGesture(boolean hasUserGesture) {
+ mHasUserGesture = hasUserGesture;
+ }
+
+ /**
+ * @return Whether or not this load was initiated with a user gesture.
+ */
+ public boolean getHasUserGesture() {
+ return mHasUserGesture;
+ }
+
public boolean isBaseUrlDataScheme() {
// If there's no base url set, but this is a data load then
// treat the scheme as data:.
diff --git a/content/public/browser/navigation_controller.cc b/content/public/browser/navigation_controller.cc
index 23b32d9..6287fe5 100644
--- a/content/public/browser/navigation_controller.cc
+++ b/content/public/browser/navigation_controller.cc
@@ -18,10 +18,11 @@ NavigationController::LoadURLParams::LoadURLParams(const GURL& url)
browser_initiated_post_data(nullptr),
can_load_local_resources(false),
should_replace_current_entry(false),
- should_clear_history_list(false) {
#if defined(OS_ANDROID)
- intent_received_timestamp = 0;
+ intent_received_timestamp(0),
+ has_user_gesture(false),
#endif
+ should_clear_history_list(false) {
}
NavigationController::LoadURLParams::~LoadURLParams() {
@@ -42,10 +43,11 @@ NavigationController::LoadURLParams::LoadURLParams(
virtual_url_for_data_url(other.virtual_url_for_data_url),
browser_initiated_post_data(other.browser_initiated_post_data),
should_replace_current_entry(false),
- should_clear_history_list(false) {
#if defined(OS_ANDROID)
- intent_received_timestamp = other.intent_received_timestamp;
+ intent_received_timestamp(other.intent_received_timestamp),
+ has_user_gesture(other.has_user_gesture),
#endif
+ should_clear_history_list(false) {
}
NavigationController::LoadURLParams&
@@ -68,6 +70,7 @@ NavigationController::LoadURLParams::operator=(
should_clear_history_list = other.should_clear_history_list;
#if defined(OS_ANDROID)
intent_received_timestamp = other.intent_received_timestamp;
+ has_user_gesture = other.has_user_gesture;
#endif
return *this;
diff --git a/content/public/browser/navigation_controller.h b/content/public/browser/navigation_controller.h
index 9faceb4..c0c1899 100644
--- a/content/public/browser/navigation_controller.h
+++ b/content/public/browser/navigation_controller.h
@@ -177,6 +177,17 @@ class NavigationController {
// navigated. This is currently only used in tests.
std::string frame_name;
+#if defined(OS_ANDROID)
+ // On Android, for a load triggered by an intent, the time Chrome received
+ // the original intent that prompted the load (in milliseconds active time
+ // since boot).
+ int64 intent_received_timestamp;
+
+ // When Chrome launches the intent chooser, user can select Chrome itself to
+ // open the intent. In this case, we should carry over the user gesture.
+ bool has_user_gesture;
+#endif
+
// Indicates that during this navigation, the session history should be
// cleared such that the resulting page is the first and only entry of the
// session history.
@@ -185,13 +196,6 @@ class NavigationController {
// commits.
bool should_clear_history_list;
-#if defined(OS_ANDROID)
- // On Android, for a load triggered by an intent, the time Chrome received
- // the original intent that prompted the load (in milliseconds active time
- // since boot).
- int64 intent_received_timestamp;
-#endif
-
explicit LoadURLParams(const GURL& url);
~LoadURLParams();
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index d1672fc..05e6ad6 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4464,6 +4464,9 @@ void RenderFrameImpl::NavigateInternal(
WebHistoryItem item_for_history_navigation;
WebURLRequest request = CreateURLRequestForNavigation(
common_params, stream_params.Pass(), frame_->isViewSourceModeEnabled());
+#if defined(OS_ANDROID)
+ request.setHasUserGesture(start_params.has_user_gesture);
+#endif
// PlzNavigate: Make sure that Blink's loader will not try to use browser side
// navigation for this request (since it already went to the browser).