Skip to content

Commit

Permalink
[WebLayer] Add API for disabling intent processing on Navigation
Browse files Browse the repository at this point in the history
This CL adds Navigation#disableIntentProcessing(), which can be called
from the onNavigationStarted() callback to disable intent processing
for the lifetime of a given Navigation. The API is similar in semantics
and implementation to Navigation#disableNetworkErrorAutoReload().

Note that this API differs from NavigateParams#disableIntentProcessing()
in two ways:

(1) The new API can be invoked for *any* Navigation, not just
embedder-initiated Navigations.
(2) The existing API disallows intent launches on direct navigations
but allows them after server redirects.

The two APIs serve different use cases; crbug.com/1264328 tracks
renaming and documentation of the existing API to clarify its semantics
and intended use cases.

Bug: 1233527
Change-Id: I9c7c63481c750d577ca30cd29c139e6ac3169d50
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3253348
Commit-Queue: Colin Blundell <blundell@chromium.org>
Reviewed-by: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/main@{#937790}
  • Loading branch information
colinblundell authored and Chromium LUCI CQ committed Nov 3, 2021
1 parent a5ad632 commit 6a7e7bf
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,113 @@ public void testExternalIntentWithNoRedirectLaunched() throws Throwable {
Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
}

/**
* Tests that a direct navigation to an external intent is blocked if the client calls
* Navigation#disableIntentProcessing() from the onNavigationStarted() callback.
*/
@Test
@SmallTest
public void
testExternalIntentWithNoRedirectBlockedIfIntentProcessingDisabledOnNavigationStarted()
throws Throwable {
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
IntentInterceptor intentInterceptor = new IntentInterceptor();
activity.setIntentInterceptor(intentInterceptor);

CallbackHelper onNavigationToIntentFailedCallbackHelper = new CallbackHelper();
NavigationCallback navigationCallback = new NavigationCallback() {
@Override
public void onNavigationStarted(Navigation navigation) {
if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
navigation.disableIntentProcessing();
}
}
@Override
public void onNavigationFailed(Navigation navigation) {
if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
onNavigationToIntentFailedCallbackHelper.notifyCalled();
}
}
};

Tab tab = mActivityTestRule.getActivity().getTab();
TestThreadUtils.runOnUiThreadBlocking(() -> {
tab.getNavigationController().registerNavigationCallback(navigationCallback);
tab.getNavigationController().navigate(Uri.parse(INTENT_TO_SELF_URL));
});

// The navigation should fail...
onNavigationToIntentFailedCallbackHelper.waitForFirst();

// ...the intent should not have been launched.
Assert.assertNull(intentInterceptor.mLastIntent);

// As there was no fallback Url, there should be only the initial navigation in the tab.
Browser browser = mActivityTestRule.getActivity().getBrowser();
int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
return browser.getActiveTab().getNavigationController().getNavigationListSize();
});
Assert.assertEquals(1, numNavigationsInTab);

TestThreadUtils.runOnUiThreadBlocking(() -> {
tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
});
}

/**
* Tests that a navigation to an external intent after a server redirect is blocked if the
* client calls Navigation#disableIntentProcessing() from the onNavigationStarted()
* callback.
*/
@Test
@SmallTest
public void
testExternalIntentAfterRedirectBlockedIfIntentProcessingDisabledOnNavigationStarted()
throws Throwable {
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
IntentInterceptor intentInterceptor = new IntentInterceptor();
activity.setIntentInterceptor(intentInterceptor);

CallbackHelper onNavigationToIntentFailedCallbackHelper = new CallbackHelper();
NavigationCallback navigationCallback = new NavigationCallback() {
@Override
public void onNavigationStarted(Navigation navigation) {
if (navigation.getUri().toString().equals(mRedirectToIntentToSelfURL)) {
navigation.disableIntentProcessing();
}
}
@Override
public void onNavigationFailed(Navigation navigation) {
if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
onNavigationToIntentFailedCallbackHelper.notifyCalled();
}
}
};

Tab tab = mActivityTestRule.getActivity().getTab();
TestThreadUtils.runOnUiThreadBlocking(() -> {
tab.getNavigationController().registerNavigationCallback(navigationCallback);
tab.getNavigationController().navigate(Uri.parse(mRedirectToIntentToSelfURL));
});

// The navigation should fail...
onNavigationToIntentFailedCallbackHelper.waitForFirst();

// ...the intent should not have been launched.
Assert.assertNull(intentInterceptor.mLastIntent);

// As there was no fallback Url, there should be only the initial navigation in the tab.
Browser browser = mActivityTestRule.getActivity().getBrowser();
int numNavigationsInTab = TestThreadUtils.runOnUiThreadBlocking(() -> {
return browser.getActiveTab().getNavigationController().getNavigationListSize();
});
Assert.assertEquals(1, numNavigationsInTab);

TestThreadUtils.runOnUiThreadBlocking(() -> {
tab.getNavigationController().unregisterNavigationCallback(navigationCallback);
});
}

/**
* Tests that external intent-related navigation params are not set on browser navigations.
*/
Expand Down
27 changes: 15 additions & 12 deletions weblayer/browser/content_browser_client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -774,16 +774,17 @@ ContentBrowserClientImpl::CreateThrottlesForNavigation(
navigation_controller =
static_cast<NavigationControllerImpl*>(tab->GetNavigationController());
}

NavigationImpl* navigation_impl = nullptr;
if (navigation_controller) {
navigation_impl =
navigation_controller->GetNavigationImplFromHandle(handle);
}

if (handle->IsInMainFrame()) {
NavigationUIDataImpl* navigation_ui_data =
static_cast<NavigationUIDataImpl*>(handle->GetNavigationUIData());

NavigationImpl* navigation_impl = nullptr;
if (navigation_controller) {
navigation_impl =
navigation_controller->GetNavigationImplFromHandle(handle);
}

if ((!navigation_ui_data ||
!navigation_ui_data->disable_network_error_auto_reload()) &&
(!navigation_impl ||
Expand Down Expand Up @@ -851,12 +852,14 @@ ContentBrowserClientImpl::CreateThrottlesForNavigation(
throttles.push_back(std::move(safe_browsing_throttle));
}

std::unique_ptr<content::NavigationThrottle> intercept_navigation_throttle =
navigation_interception::InterceptNavigationDelegate::
MaybeCreateThrottleFor(
handle, navigation_interception::SynchronyMode::kAsync);
if (intercept_navigation_throttle)
throttles.push_back(std::move(intercept_navigation_throttle));
if (!navigation_impl || !navigation_impl->disable_intent_processing()) {
std::unique_ptr<content::NavigationThrottle> intercept_navigation_throttle =
navigation_interception::InterceptNavigationDelegate::
MaybeCreateThrottleFor(
handle, navigation_interception::SynchronyMode::kAsync);
if (intercept_navigation_throttle)
throttles.push_back(std::move(intercept_navigation_throttle));
}
#endif
return throttles;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@ public void disableNetworkErrorAutoReload() {
}
}

@Override
public void disableIntentProcessing() {
if (!NavigationImplJni.get().disableIntentProcessing(mNativeNavigationImpl)) {
throw new IllegalStateException();
}
}

@Override
public boolean isFormSubmission() {
StrictModeWorkaround.apply();
Expand Down Expand Up @@ -320,6 +327,7 @@ interface Natives {
boolean isReload(long nativeNavigationImpl);
boolean isServedFromBackForwardCache(long nativeNavigationImpl);
boolean disableNetworkErrorAutoReload(long nativeNavigationImpl);
boolean disableIntentProcessing(long nativeNavigationImpl);
boolean areIntentLaunchesAllowedInBackground(long nativeNavigationImpl);
boolean isFormSubmission(long nativeNavigationImpl);
String getReferrer(long nativeNavigationImpl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ interface INavigation {

// @since 92
int getNavigationEntryOffset() = 22;

// @since 97
void disableIntentProcessing() = 23;

}
2 changes: 2 additions & 0 deletions weblayer/browser/navigation_controller_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ void NavigationControllerImpl::DidStartNavigation(
navigation);
navigation->set_safe_to_set_request_headers(true);
navigation->set_safe_to_disable_network_error_auto_reload(true);
navigation->set_safe_to_disable_intent_processing(true);

#if defined(OS_ANDROID)
// Desktop mode and per-navigation UA use the same mechanism and so don't
Expand Down Expand Up @@ -447,6 +448,7 @@ void NavigationControllerImpl::DidStartNavigation(
navigation->set_safe_to_set_user_agent(false);
navigation->set_safe_to_set_request_headers(false);
navigation->set_safe_to_disable_network_error_auto_reload(false);
navigation->set_safe_to_disable_intent_processing(false);
}

void NavigationControllerImpl::DidRedirectNavigation(
Expand Down
7 changes: 7 additions & 0 deletions weblayer/browser/navigation_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ jboolean NavigationImpl::DisableNetworkErrorAutoReload(JNIEnv* env) {
return true;
}

jboolean NavigationImpl::DisableIntentProcessing(JNIEnv* env) {
if (!safe_to_disable_intent_processing_)
return false;
disable_intent_processing_ = true;
return true;
}

jboolean NavigationImpl::AreIntentLaunchesAllowedInBackground(JNIEnv* env) {
NavigationUIDataImpl* navigation_ui_data = static_cast<NavigationUIDataImpl*>(
navigation_handle_->GetNavigationUIData());
Expand Down
12 changes: 12 additions & 0 deletions weblayer/browser/navigation_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class NavigationImpl : public Navigation {
safe_to_disable_network_error_auto_reload_ = value;
}

void set_safe_to_disable_intent_processing(bool value) {
safe_to_disable_intent_processing_ = value;
}

void set_safe_to_get_page() { safe_to_get_page_ = true; }

void set_was_stopped() { was_stopped_ = true; }
Expand All @@ -62,6 +66,8 @@ class NavigationImpl : public Navigation {
return disable_network_error_auto_reload_;
}

bool disable_intent_processing() { return disable_intent_processing_; }

void set_finished() { finished_ = true; }

#if defined(OS_ANDROID)
Expand Down Expand Up @@ -89,6 +95,7 @@ class NavigationImpl : public Navigation {
return IsServedFromBackForwardCache();
}
jboolean DisableNetworkErrorAutoReload(JNIEnv* env);
jboolean DisableIntentProcessing(JNIEnv* env);
jboolean AreIntentLaunchesAllowedInBackground(JNIEnv* env);
jboolean IsFormSubmission(JNIEnv* env) { return IsFormSubmission(); }
base::android::ScopedJavaLocalRef<jstring> GetReferrer(JNIEnv* env);
Expand Down Expand Up @@ -156,11 +163,16 @@ class NavigationImpl : public Navigation {
// Whether DisableNetworkErrorAutoReload is allowed at this time.
bool safe_to_disable_network_error_auto_reload_ = false;

// Whether DisableIntentProcessing is allowed at this time.
bool safe_to_disable_intent_processing_ = false;

// Whether GetPage is allowed at this time.
bool safe_to_get_page_ = false;

bool disable_network_error_auto_reload_ = false;

bool disable_intent_processing_ = false;

// Whether this navigation has finished.
bool finished_ = false;

Expand Down
22 changes: 22 additions & 0 deletions weblayer/public/java/org/chromium/weblayer/Navigation.java
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,28 @@ public void disableNetworkErrorAutoReload() {
}
}

/**
* Disables intent processing for the lifetime of this navigation (including following
* redirects). This method may only be called from
* {@link NavigationCallback.onNavigationStarted}.
*
* @throws IllegalStateException If not called during start.
*
* @since 97
*/
public void disableIntentProcessing() {
ThreadCheck.ensureOnUiThread();
if (WebLayer.shouldPerformVersionChecks()
&& WebLayer.getSupportedMajorVersionInternal() < 97) {
throw new UnsupportedOperationException();
}
try {
mNavigationImpl.disableIntentProcessing();
} catch (RemoteException e) {
throw new APICallException(e);
}
}

/**
* Sets the user-agent string that applies to the current navigation. This user-agent is not
* sticky, it applies to this navigation only (and any redirects or resources that are loaded).
Expand Down

0 comments on commit 6a7e7bf

Please sign in to comment.