diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/external_intents/android | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/external_intents/android')
8 files changed, 364 insertions, 104 deletions
diff --git a/chromium/components/external_intents/android/BUILD.gn b/chromium/components/external_intents/android/BUILD.gn index 530cfa2c449..f0688b0e6ed 100644 --- a/chromium/components/external_intents/android/BUILD.gn +++ b/chromium/components/external_intents/android/BUILD.gn @@ -25,9 +25,11 @@ android_library("java") { "//components/embedder_support/android:util_java", "//components/navigation_interception/android:navigation_interception_java", "//content/public/android:content_java", + "//services/network/public/mojom:mojom_java", "//third_party/android_deps:androidx_annotation_annotation_java", "//ui/android:ui_java", "//url:gurl_java", + "//url:origin_java", ] } @@ -72,7 +74,10 @@ android_library("javatests") { "//base:base_java_test_support", "//content/public/test/android:content_java_test_support", "//third_party/android_deps:androidx_core_core_java", + "//third_party/android_sdk:android_test_mock_java", "//third_party/android_sdk/androidx_browser:androidx_browser_java", + "//third_party/android_support_test_runner:runner_java", + "//third_party/junit", "//ui/android:ui_java", ] } diff --git a/chromium/components/external_intents/android/external_intents_feature_list.cc b/chromium/components/external_intents/android/external_intents_feature_list.cc index 71d82a494be..91a4c0e7edc 100644 --- a/chromium/components/external_intents/android/external_intents_feature_list.cc +++ b/chromium/components/external_intents/android/external_intents_feature_list.cc @@ -9,6 +9,7 @@ #include <string> #include "base/android/jni_string.h" +#include "base/notreached.h" #include "components/external_intents/android/jni_headers/ExternalIntentsFeatureList_jni.h" namespace external_intents { diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java index 156128517d4..dd8a259c4f4 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java @@ -9,11 +9,13 @@ import android.content.Intent; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.WebContents; import org.chromium.ui.base.WindowAndroid; +import org.chromium.url.Origin; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -133,8 +135,12 @@ public interface ExternalNavigationDelegate { */ void maybeAdjustInstantAppExtras(Intent intent, boolean isIntentToInstantApp); - /** Invoked for intents with user gestures and records the user gesture if desired. */ - void maybeSetUserGesture(Intent intent); + /** + * Invoked for intents with request metadata such as user gesture, whether request is renderer + * initiated and the initiator origin. Records the information if desired. + */ + void maybeSetRequestMetadata(Intent intent, boolean hasUserGesture, boolean isRendererInitiated, + @Nullable Origin initiatorOrigin); /** * Records the pending incognito URL if desired. Called only if the diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java index b1b9bffa4b1..69f43cc8cfa 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java @@ -160,6 +160,17 @@ public class ExternalNavigationHandler { @VisibleForTesting static final String INTENT_ACTION_HISTOGRAM = "Android.Intent.OverrideUrlLoadingIntentAction"; + // Helper class to return a boolean by reference. + private static class MutableBoolean { + private Boolean mValue = null; + public void set(boolean value) { + mValue = value; + } + public Boolean get() { + return mValue; + } + } + private final ExternalNavigationDelegate mDelegate; /** @@ -217,9 +228,16 @@ public class ExternalNavigationHandler { && !UrlUtilities.isValidForIntentFallbackNavigation(browserFallbackUrl)) { browserFallbackUrl = null; } + + // TODO(https://crbug.com/1096099): Refactor shouldOverrideUrlLoadingInternal, splitting it + // up to separate out the notions wanting to fire an external intent vs being able to. + MutableBoolean canLaunchExternalFallbackResult = new MutableBoolean(); + long time = SystemClock.elapsedRealtime(); @OverrideUrlLoadingResult - int result = shouldOverrideUrlLoadingInternal(params, targetIntent, browserFallbackUrl); + int result = shouldOverrideUrlLoadingInternal( + params, targetIntent, browserFallbackUrl, canLaunchExternalFallbackResult); + assert canLaunchExternalFallbackResult.get() != null; RecordHistogram.recordTimesHistogram( "Android.StrictMode.OverrideUrlLoadingTime", SystemClock.elapsedRealtime() - time); @@ -236,31 +254,64 @@ public class ExternalNavigationHandler { && (params.getRedirectHandler() == null // For instance, if this is a chained fallback URL, we ignore it. || !params.getRedirectHandler().shouldNotOverrideUrlLoading())) { - result = handleFallbackUrl(params, targetIntent, browserFallbackUrl); + result = handleFallbackUrl(params, targetIntent, browserFallbackUrl, + canLaunchExternalFallbackResult.get()); } if (DEBUG) printDebugShouldOverrideUrlLoadingResult(result); return result; } - private @OverrideUrlLoadingResult int handleFallbackUrl( - ExternalNavigationParams params, Intent targetIntent, String browserFallbackUrl) { + private @OverrideUrlLoadingResult int handleFallbackUrl(ExternalNavigationParams params, + Intent targetIntent, String browserFallbackUrl, boolean canLaunchExternalFallback) { if (mDelegate.isIntentToInstantApp(targetIntent)) { RecordHistogram.recordEnumeratedHistogram("Android.InstantApps.DirectInstantAppsIntent", AiaIntent.FALLBACK_USED, AiaIntent.NUM_ENTRIES); } - // Launch WebAPK if it can handle the URL. - try { - Intent intent = Intent.parseUri(browserFallbackUrl, Intent.URI_INTENT_SCHEME); - sanitizeQueryIntentActivitiesIntent(intent); - List<ResolveInfo> resolvingInfos = queryIntentActivities(intent); - if (!isAlreadyInTargetWebApk(resolvingInfos, params) - && launchWebApkIfSoleIntentHandler(resolvingInfos, intent)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; + + if (canLaunchExternalFallback) { + if (shouldBlockAllExternalAppLaunches(params) || params.isIncognito()) { + throw new SecurityException("Context is not allowed to launch an external app."); } - } catch (Exception e) { - if (DEBUG) Log.i(TAG, "Could not parse fallback url as intent"); + // Launch WebAPK if it can handle the URL. + try { + Intent intent = Intent.parseUri(browserFallbackUrl, Intent.URI_INTENT_SCHEME); + sanitizeQueryIntentActivitiesIntent(intent); + List<ResolveInfo> resolvingInfos = queryIntentActivities(intent); + if (!isAlreadyInTargetWebApk(resolvingInfos, params) + && launchWebApkIfSoleIntentHandler(resolvingInfos, intent)) { + return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; + } + } catch (Exception e) { + if (DEBUG) Log.i(TAG, "Could not parse fallback url as intent"); + } + + // If the fallback URL is a link to Play Store, send the user to Play Store app + // instead: crbug.com/638672. + Pair<String, String> appInfo = maybeGetPlayStoreAppIdAndReferrer(browserFallbackUrl); + if (appInfo != null) { + String marketReferrer = TextUtils.isEmpty(appInfo.second) + ? ContextUtils.getApplicationContext().getPackageName() + : appInfo.second; + return sendIntentToMarket(appInfo.first, marketReferrer, params); + } + } + + // For subframes, we don't support fallback url for now. + // http://crbug.com/364522. + if (!params.isMainFrame()) { + if (DEBUG) Log.i(TAG, "Don't support fallback url in subframes"); + return OverrideUrlLoadingResult.NO_OVERRIDE; } - return clobberCurrentTabWithFallbackUrl(browserFallbackUrl, params); + + // NOTE: any further redirection from fall-back URL should not override URL loading. + // Otherwise, it can be used in chain for fingerprinting multiple app installation + // status in one shot. In order to prevent this scenario, we notify redirection + // handler that redirection from the current navigation should stay in this app. + if (params.getRedirectHandler() != null) { + params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain(); + } + if (DEBUG) Log.i(TAG, "clobberCurrentTab called"); + return clobberCurrentTab(browserFallbackUrl, params.getReferrerUrl()); } private void printDebugShouldOverrideUrlLoadingResult(int result) { @@ -300,6 +351,19 @@ public class ExternalNavigationHandler { } /** + * https://crbug.com/1094442: Don't allow any external navigation on AUTO_SUBFRAME navigation + * (eg. initial ad frame navigation). + */ + private boolean blockExternalNavFromAutoSubframe(ExternalNavigationParams params) { + int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK; + if (pageTransitionCore == PageTransition.AUTO_SUBFRAME) { + if (DEBUG) Log.i(TAG, "Auto navigation in subframe"); + return true; + } + return false; + } + + /** * http://crbug.com/441284 : Disallow firing external intent while the app is in the background. */ private boolean blockExternalNavWhileBackgrounded(ExternalNavigationParams params) { @@ -651,6 +715,30 @@ public class ExternalNavigationHandler { } /** + * Intent URIs leads to creating intents that chrome would use for firing external navigations + * via Android. Android throws an exception [1] when an application exposes a file:// Uri to + * another app. + * + * This method checks if the |targetIntent| contains the file:// scheme in its data. + * + * [1]: https://developer.android.com/reference/android/os/FileUriExposedException + */ + private boolean hasFileSchemeInIntentURI(Intent targetIntent, boolean hasIntentScheme) { + // We are only concerned with targetIntent that was generated due to intent:// schemes only. + if (!hasIntentScheme) return false; + + Uri data = targetIntent.getData(); + + if (data == null || data.getScheme() == null) return false; + + if (data.getScheme().equalsIgnoreCase(UrlConstants.FILE_SCHEME)) { + if (DEBUG) Log.i(TAG, "Intent navigation to file: URI"); + return true; + } + return false; + } + + /** * Special case - It makes no sense to use an external application for a YouTube * pairing code URL, since these match the current tab with a device (Chromecast * or similar) it is supposed to be controlling. Using a different application @@ -851,7 +939,8 @@ public class ExternalNavigationHandler { AiaIntent.SERP, AiaIntent.NUM_ENTRIES); } - if (params.hasUserGesture()) mDelegate.maybeSetUserGesture(targetIntent); + mDelegate.maybeSetRequestMetadata(targetIntent, params.hasUserGesture(), + params.isRendererInitiated(), params.getInitiatorOrigin()); } private @OverrideUrlLoadingResult int handleExternalIncognitoIntent(Intent targetIntent, @@ -940,15 +1029,20 @@ public class ExternalNavigationHandler { return false; } + // Check if we're navigating under conditions that should never launch an external app. + private boolean shouldBlockAllExternalAppLaunches(ExternalNavigationParams params) { + return blockExternalNavFromAutoSubframe(params) || blockExternalNavWhileBackgrounded(params) + || blockExternalNavFromBackgroundTab(params) || ignoreBackForwardNav(params); + } + private @OverrideUrlLoadingResult int shouldOverrideUrlLoadingInternal( ExternalNavigationParams params, Intent targetIntent, - @Nullable String browserFallbackUrl) { + @Nullable String browserFallbackUrl, MutableBoolean canLaunchExternalFallbackResult) { sanitizeQueryIntentActivitiesIntent(targetIntent); + // Don't allow external fallback URLs by default. + canLaunchExternalFallbackResult.set(false); - if (blockExternalNavWhileBackgrounded(params) || blockExternalNavFromBackgroundTab(params) - || ignoreBackForwardNav(params)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } + if (shouldBlockAllExternalAppLaunches(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; if (handleWithAutofillAssistant(params, targetIntent, browserFallbackUrl)) { return OverrideUrlLoadingResult.NO_OVERRIDE; @@ -1016,6 +1110,10 @@ public class ExternalNavigationHandler { return OverrideUrlLoadingResult.NO_OVERRIDE; } + if (hasFileSchemeInIntentURI(targetIntent, hasIntentScheme)) { + return OverrideUrlLoadingResult.NO_OVERRIDE; + } + if (isYoutubePairingCode(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; if (shouldStayInIncognito(params, isExternalProtocol)) { @@ -1026,6 +1124,10 @@ public class ExternalNavigationHandler { if (hasIntentScheme) recordIntentActionMetrics(targetIntent); + // From this point on, we have determined it is safe to launch an External App from a + // fallback URL, provided the user isn't in incognito. + if (!params.isIncognito()) canLaunchExternalFallbackResult.set(true); + Intent debugIntent = new Intent(targetIntent); List<ResolveInfo> resolvingInfos = queryIntentActivities(targetIntent); if (resolvingInfos.isEmpty()) { @@ -1154,44 +1256,6 @@ public class ExternalNavigationHandler { } /** - * Clobber the current tab with fallback URL. - * - * @param browserFallbackUrl The fallback URL. - * @param params The external navigation params. - * @return {@link OverrideUrlLoadingResult} if the tab was clobbered, or we launched an - * intent. - */ - private @OverrideUrlLoadingResult int clobberCurrentTabWithFallbackUrl( - String browserFallbackUrl, ExternalNavigationParams params) { - // If the fallback URL is a link to Play Store, send the user to Play Store app - // instead: crbug.com/638672. - Pair<String, String> appInfo = maybeGetPlayStoreAppIdAndReferrer(browserFallbackUrl); - if (appInfo != null) { - String marketReferrer = TextUtils.isEmpty(appInfo.second) - ? ContextUtils.getApplicationContext().getPackageName() - : appInfo.second; - return sendIntentToMarket(appInfo.first, marketReferrer, params); - } - - // For subframes, we don't support fallback url for now. - // http://crbug.com/364522. - if (!params.isMainFrame()) { - if (DEBUG) Log.i(TAG, "Don't support fallback url in subframes"); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - // NOTE: any further redirection from fall-back URL should not override URL loading. - // Otherwise, it can be used in chain for fingerprinting multiple app installation - // status in one shot. In order to prevent this scenario, we notify redirection - // handler that redirection from the current navigation should stay in this app. - if (params.getRedirectHandler() != null) { - params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain(); - } - if (DEBUG) Log.i(TAG, "clobberCurrentTab called"); - return clobberCurrentTab(browserFallbackUrl, params.getReferrerUrl()); - } - - /** * If the given URL is to Google Play, extracts the package name and referrer tracking code * from the {@param url} and returns as a Pair in that order. Otherwise returns null. */ diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java index f4d40512867..fe172dadec4 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java @@ -4,6 +4,10 @@ package org.chromium.components.external_intents; +import androidx.annotation.Nullable; + +import org.chromium.url.Origin; + /** * A container object for passing navigation parameters to {@link ExternalNavigationHandler}. */ @@ -53,12 +57,22 @@ public class ExternalNavigationParams { */ private final boolean mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; + /** + * Whether the navigation is initiated by the renderer. + */ + private boolean mIsRendererInitiated; + + /** + * The origin that initiates the navigation, could be null. + */ + private Origin mInitiatorOrigin; + private ExternalNavigationParams(String url, boolean isIncognito, String referrerUrl, int pageTransition, boolean isRedirect, boolean appMustBeInForeground, RedirectHandler redirectHandler, boolean openInNewTab, boolean isBackgroundTabNavigation, boolean isMainFrame, String nativeClientPackageName, - boolean hasUserGesture, - boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent) { + boolean hasUserGesture, boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent, + boolean isRendererInitiated, @Nullable Origin initiatorOrigin) { mUrl = url; mIsIncognito = isIncognito; mPageTransition = pageTransition; @@ -73,6 +87,8 @@ public class ExternalNavigationParams { mHasUserGesture = hasUserGesture; mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent = shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; + mIsRendererInitiated = isRendererInitiated; + mInitiatorOrigin = initiatorOrigin; } /** @return The URL to potentially open externally. */ @@ -149,6 +165,21 @@ public class ExternalNavigationParams { return mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; } + /** + * @return Whether the navigation is initiated by renderer. + */ + public boolean isRendererInitiated() { + return mIsRendererInitiated; + } + + /** + * @return The origin that initiates the navigation. + */ + @Nullable + public Origin getInitiatorOrigin() { + return mInitiatorOrigin; + } + /** The builder for {@link ExternalNavigationParams} objects. */ public static class Builder { /** The URL which we are navigating to. */ @@ -196,6 +227,16 @@ public class ExternalNavigationParams { */ private boolean mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; + /** + * Whether the navigation is initiated by the renderer. + */ + private boolean mIsRendererInitiated; + + /** + * The origin that initiates the navigation, could be null. + */ + private Origin mInitiatorOrigin; + public Builder(String url, boolean isIncognito) { mUrl = url; mIsIncognito = isIncognito; @@ -261,12 +302,29 @@ public class ExternalNavigationParams { return this; } + /** + * Sets whether the navigation is initiated by renderer. + */ + public Builder setIsRendererInitiated(boolean v) { + mIsRendererInitiated = v; + return this; + } + + /** + * Sets the origin that initiates the navigation. + */ + public Builder setInitiatorOrigin(@Nullable Origin v) { + mInitiatorOrigin = v; + return this; + } + /** @return A fully constructed {@link ExternalNavigationParams} object. */ public ExternalNavigationParams build() { return new ExternalNavigationParams(mUrl, mIsIncognito, mReferrerUrl, mPageTransition, mIsRedirect, mApplicationMustBeInForeground, mRedirectHandler, mOpenInNewTab, mIsBackgroundTabNavigation, mIsMainFrame, mNativeClientPackageName, - mHasUserGesture, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent); + mHasUserGesture, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent, + mIsRendererInitiated, mInitiatorOrigin); } } } diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java index dad8bbfe811..686721169fa 100644 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java +++ b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java @@ -180,7 +180,9 @@ public class InterceptNavigationDelegateImpl implements InterceptNavigationDeleg .setIsMainFrame(navigationParams.isMainFrame) .setHasUserGesture(navigationParams.hasUserGesture) .setShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent( - shouldCloseTab && navigationParams.isMainFrame); + shouldCloseTab && navigationParams.isMainFrame) + .setIsRendererInitiated(navigationParams.isRendererInitiated) + .setInitiatorOrigin(navigationParams.initiatorOrigin); } /** diff --git a/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java index 867a67a22b6..33090212178 100644 --- a/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java +++ b/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java @@ -1,4 +1,4 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. +// Copyright 2020 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. @@ -19,14 +19,13 @@ import android.os.Bundle; import android.os.SystemClock; import android.provider.Browser; import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; import android.test.mock.MockPackageManager; import androidx.browser.customtabs.CustomTabsIntent; +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; @@ -34,14 +33,15 @@ import org.chromium.base.ContextUtils; import org.chromium.base.IntentUtils; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.DisableIf; -import org.chromium.components.external_intents.ExternalNavigationDelegate.StartActivityIfNeededResult; import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.WebContents; -import org.chromium.content_public.browser.test.NativeLibraryTestRule; +import org.chromium.content_public.browser.test.NativeLibraryTestUtils; import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.WindowAndroid; +import org.chromium.url.Origin; import java.net.URISyntaxException; import java.util.ArrayList; @@ -56,11 +56,9 @@ import java.util.regex.Pattern; // clang-format off @DisableIf.Build(message = "Flaky on K - see https://crbug.com/851444", sdk_is_less_than = Build.VERSION_CODES.LOLLIPOP) +@Batch(Batch.UNIT_TESTS) public class ExternalNavigationHandlerTest { // clang-format on - @Rule - public final NativeLibraryTestRule mNativeLibraryTestRule = new NativeLibraryTestRule(); - // Expectations private static final int IGNORE = 0x0; private static final int START_INCOGNITO = 0x1; @@ -162,7 +160,7 @@ public class ExternalNavigationHandlerTest { mContext = new TestContext(InstrumentationRegistry.getTargetContext(), mDelegate); ContextUtils.initApplicationContextForTests(mContext); - mNativeLibraryTestRule.loadNativeLibraryNoBrowserProcess(); + NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess(); } @Test @@ -331,16 +329,13 @@ public class ExternalNavigationHandlerTest { @SmallTest public void testIgnore() { // Ensure the following URLs are not broadcast for external navigation. - String urlsToIgnore[] = new String[] { - "about:test", + String urlsToIgnore[] = new String[] {"about:test", "content:test", // Content URLs should not be exposed outside of Chrome. - "chrome://history", - "chrome-native://newtab", - "devtools://foo", + "chrome://history", "chrome-native://newtab", "devtools://foo", "intent:chrome-urls#Intent;package=com.android.chrome;scheme=about;end;", "intent:chrome-urls#Intent;package=com.android.chrome;scheme=chrome;end;", "intent://com.android.chrome.FileProvider/foo.html#Intent;scheme=content;end;", - }; + "intent:///x.mhtml#Intent;package=com.android.chrome;action=android.intent.action.VIEW;scheme=file;end;"}; for (String url : urlsToIgnore) { checkUrl(url).expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE); checkUrl(url).withIsIncognito(true).expecting( @@ -945,16 +940,52 @@ public class ExternalNavigationHandlerTest { @Test @SmallTest + public void testFallbackUrl_SubframeFallbackToMarketApp() { + mDelegate.setCanResolveActivityForExternalSchemes(false); + + RedirectHandler redirectHandler = RedirectHandler.create(); + redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0); + String intent = "intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;" + + "S." + ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL + "=" + + "https://play.google.com/store/apps/details?id=com.imdb.mobile" + + "&referrer=mypage;end"; + checkUrl(intent) + .withIsMainFrame(false) + .withHasUserGesture(true) + .withRedirectHandler(redirectHandler) + .withPageTransition(PageTransition.LINK) + .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, + START_OTHER_ACTIVITY); + Assert.assertEquals("market://details?id=com.imdb.mobile&referrer=mypage", + mDelegate.startActivityIntent.getDataString()); + + redirectHandler = RedirectHandler.create(); + redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0); + String intentBadUrl = "intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;" + + "S." + ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL + "=" + + "https://play.google.com/store/search?q=pub:imdb;end"; + checkUrl(intentBadUrl) + .withIsMainFrame(false) + .withHasUserGesture(true) + .withRedirectHandler(redirectHandler) + .withPageTransition(PageTransition.LINK) + .expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE); + } + + @Test + @SmallTest public void testFallbackUrl_RedirectToIntentToMarket() { + mDelegate.setCanResolveActivityForExternalSchemes(false); + RedirectHandler redirectHandler = RedirectHandler.create(); - redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0); + redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0); checkUrl("http://goo.gl/abcdefg") - .withPageTransition(PageTransition.TYPED) + .withPageTransition(PageTransition.LINK) .withRedirectHandler(redirectHandler) .expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE); - redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0); + redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, true, 0, 0); String realIntent = "intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;" + "S." + ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL + "=" + "https://play.google.com/store/apps/details?id=com.imdb.mobile" @@ -973,6 +1004,44 @@ public class ExternalNavigationHandlerTest { @Test @SmallTest + public void testFallbackUrl_DontFallbackForAutoSubframe() { + // IMDB app isn't installed. + mDelegate.setCanResolveActivityForExternalSchemes(false); + + mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME) + .withIsWebApk(true)); + + RedirectHandler redirectHandler = RedirectHandler.create(); + redirectHandler.updateNewUrlLoading(PageTransition.AUTO_SUBFRAME, true, true, 0, 0); + + checkUrl(INTENT_URL_WITH_FALLBACK_URL) + .withIsMainFrame(false) + .withHasUserGesture(true) + .withRedirectHandler(redirectHandler) + .withPageTransition(PageTransition.AUTO_SUBFRAME) + .withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS) + .expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE); + } + + @Test + @SmallTest + public void testFallbackUrl_NoExternalFallbackWithoutGesture() { + mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME) + .withIsWebApk(true)); + + RedirectHandler redirectHandler = RedirectHandler.create(); + redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0); + + checkUrl(INTENT_URL_WITH_FALLBACK_URL) + .withHasUserGesture(false) + .withRedirectHandler(redirectHandler) + .withPageTransition(PageTransition.LINK) + .withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS) + .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE); + } + + @Test + @SmallTest public void testFallbackUrl_IntentResolutionFailsWithoutPackageName() { // IMDB app isn't installed. mDelegate.setCanResolveActivityForExternalSchemes(false); @@ -1255,6 +1324,37 @@ public class ExternalNavigationHandlerTest { @Test @SmallTest + public void testIntentWithFileSchemeFiltered() { + checkUrl("intent://#Intent;package=com.test.package;scheme=file;end;") + .expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE); + } + + @Test + @SmallTest + public void testIntentWithNoSchemeLaunched() { + checkUrl("intent://#Intent;package=com.test.package;end;") + .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, + START_OTHER_ACTIVITY); + } + + @Test + @SmallTest + public void testIntentWithEmptySchemeLaunched() { + checkUrl("intent://#Intent;package=com.test.package;scheme=;end;") + .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, + START_OTHER_ACTIVITY); + } + + @Test + @SmallTest + public void testIntentWithWeirdSchemeLaunched() { + checkUrl("intent://#Intent;package=com.test.package;scheme=w3irD;end;") + .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, + START_OTHER_ACTIVITY); + } + + @Test + @SmallTest public void testIntentWithMissingReferrer() { mDelegate.add(new IntentActivity("http://refertest.com", "refertest")); mDelegate.add(new IntentActivity("https://refertest.com", "refertest")); @@ -1528,7 +1628,7 @@ public class ExternalNavigationHandlerTest { .withHasUserGesture(true) .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, START_OTHER_ACTIVITY); - Assert.assertTrue(mDelegate.maybeSetUserGestureCalled); + Assert.assertTrue(mDelegate.maybeSetRequestMetadataCalled); Assert.assertFalse(mDelegate.startIncognitoIntentCalled); } @@ -1544,12 +1644,26 @@ public class ExternalNavigationHandlerTest { .withIsIncognito(true) .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION, START_INCOGNITO | START_OTHER_ACTIVITY); - Assert.assertTrue(mDelegate.maybeSetUserGestureCalled); + Assert.assertTrue(mDelegate.maybeSetRequestMetadataCalled); Assert.assertTrue(mDelegate.startIncognitoIntentCalled); } @Test @SmallTest + public void testRendererInitiated() { + // IMDB app is installed. + mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME)); + + checkUrl(INTENT_URL_WITH_FALLBACK_URL) + .withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS) + .withIsRendererInitiated(true) + .expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, + START_OTHER_ACTIVITY); + Assert.assertTrue(mDelegate.maybeSetRequestMetadataCalled); + } + + @Test + @SmallTest public void testAutofillAssistantIntentWithFallback_InRegular() { mDelegate.setIsIntentToAutofillAssistant(true); checkUrl(AUTOFILL_ASSISTANT_INTENT_URL_WITH_FALLBACK) @@ -2046,8 +2160,9 @@ public class ExternalNavigationHandlerTest { } @Override - public void maybeSetUserGesture(Intent intent) { - maybeSetUserGestureCalled = true; + public void maybeSetRequestMetadata(Intent intent, boolean hasUserGesture, + boolean isRendererInitiated, Origin initiatorOrigin) { + maybeSetRequestMetadataCalled = true; } @Override @@ -2172,7 +2287,7 @@ public class ExternalNavigationHandlerTest { public Intent startActivityIntent; public boolean startIncognitoIntentCalled; public boolean handleIncognitoIntentTargetingSelfCalled; - public boolean maybeSetUserGestureCalled; + public boolean maybeSetRequestMetadataCalled; private String mReferrerWebappPackageName; @@ -2212,6 +2327,8 @@ public class ExternalNavigationHandlerTest { private boolean mIsBackgroundTabNavigation; private boolean mHasUserGesture; private RedirectHandler mRedirectHandler; + private boolean mIsRendererInitiated; + private boolean mIsMainFrame = true; private ExternalNavigationTestParams(String url) { mUrl = url; @@ -2259,6 +2376,16 @@ public class ExternalNavigationHandlerTest { return this; } + public ExternalNavigationTestParams withIsRendererInitiated(boolean isRendererInitiated) { + mIsRendererInitiated = isRendererInitiated; + return this; + } + + public ExternalNavigationTestParams withIsMainFrame(boolean isMainFrame) { + mIsMainFrame = isMainFrame; + return this; + } + public void expecting( @OverrideUrlLoadingResult int expectedOverrideResult, int otherExpectation) { boolean expectStartIncognito = (otherExpectation & START_INCOGNITO) != 0; @@ -2279,9 +2406,10 @@ public class ExternalNavigationHandlerTest { .setApplicationMustBeInForeground(mChromeAppInForegroundRequired) .setRedirectHandler(mRedirectHandler) .setIsBackgroundTabNavigation(mIsBackgroundTabNavigation) - .setIsMainFrame(true) + .setIsMainFrame(mIsMainFrame) .setNativeClientPackageName(mDelegate.getReferrerWebappPackageName()) .setHasUserGesture(mHasUserGesture) + .setIsRendererInitiated(mIsRendererInitiated) .build(); @OverrideUrlLoadingResult int result = mUrlHandler.shouldOverrideUrlLoading(params); diff --git a/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/RedirectHandlerTest.java b/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/RedirectHandlerTest.java index b1c1c111b98..a9d838b7dcf 100644 --- a/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/RedirectHandlerTest.java +++ b/chromium/components/external_intents/android/javatests/src/org/chromium/components/external_intents/RedirectHandlerTest.java @@ -10,9 +10,10 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.SystemClock; import android.provider.Browser; -import android.support.test.filters.SmallTest; import android.test.mock.MockPackageManager; +import androidx.test.filters.SmallTest; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -24,7 +25,6 @@ import org.chromium.base.PackageManagerUtils; import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.util.AdvancedMockContext; import org.chromium.base.test.util.Feature; -import org.chromium.base.test.util.RetryOnFailure; import org.chromium.ui.base.PageTransition; import java.net.URISyntaxException; @@ -353,7 +353,6 @@ public class RedirectHandlerTest { @Test @SmallTest @Feature({"IntentHandling"}) - @RetryOnFailure public void testNavigationFromLinkWithoutUserGesture() { RedirectHandler handler = RedirectHandler.create(); handler.updateIntent(sYtIntent, false, false, false); @@ -361,12 +360,11 @@ public class RedirectHandlerTest { Assert.assertFalse(handler.shouldStayInApp(false)); Assert.assertFalse(handler.shouldStayInApp(true)); - handler.updateNewUrlLoading( - PageTransition.LINK, false, false, SystemClock.elapsedRealtime(), 0); + long lastUserInteractionTime = SystemClock.elapsedRealtime(); + handler.updateNewUrlLoading(PageTransition.LINK, false, false, lastUserInteractionTime, 0); Assert.assertTrue(handler.shouldStayInApp(false)); Assert.assertTrue(handler.shouldStayInApp(true)); - handler.updateNewUrlLoading( - PageTransition.LINK, false, false, SystemClock.elapsedRealtime(), 1); + handler.updateNewUrlLoading(PageTransition.LINK, false, false, lastUserInteractionTime, 1); Assert.assertTrue(handler.shouldStayInApp(false)); Assert.assertTrue(handler.shouldStayInApp(true)); @@ -386,7 +384,6 @@ public class RedirectHandlerTest { @Test @SmallTest @Feature({"IntentHandling"}) - @RetryOnFailure public void testNavigationFromReload() { RedirectHandler handler = RedirectHandler.create(); handler.updateIntent(sYtIntent, false, false, false); @@ -394,12 +391,12 @@ public class RedirectHandlerTest { Assert.assertFalse(handler.shouldStayInApp(false)); Assert.assertFalse(handler.shouldStayInApp(true)); + long lastUserInteractionTime = SystemClock.elapsedRealtime(); handler.updateNewUrlLoading( - PageTransition.RELOAD, false, false, SystemClock.elapsedRealtime(), 0); + PageTransition.RELOAD, false, false, lastUserInteractionTime, 0); Assert.assertTrue(handler.shouldStayInApp(false)); Assert.assertTrue(handler.shouldStayInApp(true)); - handler.updateNewUrlLoading( - PageTransition.LINK, false, false, SystemClock.elapsedRealtime(), 1); + handler.updateNewUrlLoading(PageTransition.LINK, false, false, lastUserInteractionTime, 1); Assert.assertTrue(handler.shouldStayInApp(false)); Assert.assertTrue(handler.shouldStayInApp(true)); @@ -419,7 +416,6 @@ public class RedirectHandlerTest { @Test @SmallTest @Feature({"IntentHandling"}) - @RetryOnFailure public void testNavigationWithForwardBack() { RedirectHandler handler = RedirectHandler.create(); handler.updateIntent(sYtIntent, false, false, false); @@ -427,12 +423,12 @@ public class RedirectHandlerTest { Assert.assertFalse(handler.shouldStayInApp(false)); Assert.assertFalse(handler.shouldStayInApp(true)); + long lastUserInteractionTime = SystemClock.elapsedRealtime(); handler.updateNewUrlLoading(PageTransition.FORM_SUBMIT | PageTransition.FORWARD_BACK, false, - true, SystemClock.elapsedRealtime(), 0); + true, lastUserInteractionTime, 0); Assert.assertTrue(handler.shouldStayInApp(false)); Assert.assertTrue(handler.shouldStayInApp(true)); - handler.updateNewUrlLoading( - PageTransition.LINK, false, false, SystemClock.elapsedRealtime(), 1); + handler.updateNewUrlLoading(PageTransition.LINK, false, false, lastUserInteractionTime, 1); Assert.assertTrue(handler.shouldStayInApp(false)); Assert.assertTrue(handler.shouldStayInApp(true)); |