diff options
author | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-09 22:10:46 +0000 |
---|---|---|
committer | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-09 22:10:46 +0000 |
commit | 4360ae79dc73cb3c29a590dafa4d19817b5a47bf (patch) | |
tree | d2fca3225b319701d30946db524b8b849dda5084 | |
parent | e87d91382514612bf857f9eee1c373f568bba926 (diff) | |
download | chromium_src-4360ae79dc73cb3c29a590dafa4d19817b5a47bf.zip chromium_src-4360ae79dc73cb3c29a590dafa4d19817b5a47bf.tar.gz chromium_src-4360ae79dc73cb3c29a590dafa4d19817b5a47bf.tar.bz2 |
Componentize IgnoreNavigationResourceThrottle and add chrome and webview specific implementations.
This change moves the IgnoreNavigationResourceThrottle to a separate component
as it will be shared between chrome/android and android_webview.
This change also adds the chrome/ and android_webview/ specializations of the
throttle (or more precisely the callback that is used to find the right method
on the right delegate).
BUG=130006
TBR=ben@chromium.org
Review URL: https://chromiumcodereview.appspot.com/10946008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@160945 0039d316-1c4b-4281-b951-d872f2087c98
26 files changed, 1159 insertions, 72 deletions
diff --git a/android_webview/android_webview.gyp b/android_webview/android_webview.gyp index d74f5d6..c83cfe3 100644 --- a/android_webview/android_webview.gyp +++ b/android_webview/android_webview.gyp @@ -14,6 +14,7 @@ '../chrome/chrome.gyp:renderer', '../content/content.gyp:content', '../android_webview/native/webview_native.gyp:webview_native', + '../chrome/browser/component/components.gyp:navigation_interception', '../chrome/browser/component/components.gyp:web_contents_delegate_android', '../chrome/browser/component/components.gyp:browser_component_jni_headers', ], @@ -95,6 +96,7 @@ 'type': 'none', 'dependencies': [ '../content/content.gyp:content_java', + '../chrome/browser/component/components.gyp:navigation_interception_java', '../chrome/browser/component/components.gyp:web_contents_delegate_android_java', '../ui/ui.gyp:ui_java', ], @@ -109,6 +111,7 @@ 'type': 'none', 'dependencies': [ '../base/base.gyp:base_java', + '../chrome/browser/component/components.gyp:navigation_interception_java', '../chrome/browser/component/components.gyp:web_contents_delegate_android_java', '../chrome/chrome_resources.gyp:packed_extra_resources', '../chrome/chrome_resources.gyp:packed_resources', @@ -166,6 +169,7 @@ 'dependencies': [ '../base/base.gyp:base_java', '../base/base.gyp:base_java_test_support', + '../chrome/browser/component/components.gyp:navigation_interception_java', '../chrome/browser/component/components.gyp:web_contents_delegate_android_java', '../chrome/chrome_resources.gyp:packed_extra_resources', '../chrome/chrome_resources.gyp:packed_resources', diff --git a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc index cb5d870..d0fa953 100644 --- a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc +++ b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc @@ -8,6 +8,7 @@ #include "android_webview/browser/aw_contents_io_thread_client.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" +#include "chrome/browser/component/navigation_interception/intercept_navigation_delegate.h" #include "content/public/browser/resource_controller.h" #include "content/public/browser/resource_dispatcher_host.h" #include "content/public/browser/resource_dispatcher_host_login_delegate.h" @@ -16,6 +17,8 @@ #include "net/base/load_flags.h" #include "net/url_request/url_request.h" +using navigation_interception::InterceptNavigationDelegate; + namespace { base::LazyInstance<android_webview::AwResourceDispatcherHostDelegate> @@ -77,6 +80,11 @@ void AwResourceDispatcherHostDelegate::RequestBeginning( request->set_load_flags(load_flags); } } + + if (resource_type == ResourceType::MAIN_FRAME) { + throttles->push_back(InterceptNavigationDelegate::CreateThrottleFor( + request)); + } } bool AwResourceDispatcherHostDelegate::AcceptAuthRequest( diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java index 67848b05..377e568 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -16,6 +16,7 @@ import android.webkit.ValueCallback; import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; import org.chromium.base.ThreadUtils; +import org.chromium.chrome.browser.component.navigation_interception.InterceptNavigationDelegate; import org.chromium.content.browser.ContentViewCore; import org.chromium.content.browser.NavigationHistory; import org.chromium.content.common.CleanupReference; @@ -48,6 +49,7 @@ public class AwContents { private ContentViewCore mContentViewCore; private AwContentsClient mContentsClient; private AwContentsIoThreadClient mIoThreadClient; + private InterceptNavigationDelegate mInterceptNavigationDelegate; // This can be accessed on any thread after construction. See AwContentsIoThreadClient. private final AwSettings mSettings; @@ -78,6 +80,13 @@ public class AwContents { } } + private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate { + @Override + public boolean shouldIgnoreNavigation(String url, boolean isUserGestrue) { + return AwContents.this.mContentsClient.shouldIgnoreNavigation(url); + } + } + /** * @param containerView the view-hierarchy item this object will be bound to. * @param internalAccessAdapter to access private methods on containerView. @@ -105,6 +114,7 @@ public class AwContents { mSettings = new AwSettings(mContentViewCore.getContext()); setIoThreadClient(new IoThreadClientImpl()); + setInterceptNavigationDelegate(new InterceptNavigationDelegateImpl()); } public ContentViewCore getContentViewCore() { @@ -121,6 +131,11 @@ public class AwContents { nativeSetIoThreadClient(mNativeAwContents, mIoThreadClient); } + public void setInterceptNavigationDelegate(InterceptNavigationDelegate delegate) { + mInterceptNavigationDelegate = delegate; + nativeSetInterceptNavigationDelegate(mNativeAwContents, delegate); + } + public void destroy() { if (mContentViewCore != null) { mContentViewCore.destroy(); @@ -363,6 +378,8 @@ public class AwContents { private native void nativeSetIoThreadClient(int nativeAwContents, AwContentsIoThreadClient ioThreadClient); + private native void nativeSetInterceptNavigationDelegate(int nativeAwContents, + InterceptNavigationDelegate navigationInterceptionDelegate); private native int nativeFindAllSync(int nativeAwContents, String searchString); private native void nativeFindAllAsync(int nativeAwContents, String searchString); diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java index 15be569..f646040 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java @@ -43,11 +43,6 @@ public abstract class AwContentsClient extends ContentViewClient { } @Override - public boolean shouldOverrideUrlLoading(String url) { - return AwContentsClient.this.shouldOverrideUrlLoading(url); - } - - @Override public void handleKeyboardEvent(KeyEvent event) { AwContentsClient.this.onUnhandledKeyEvent(event); } @@ -147,7 +142,7 @@ public abstract class AwContentsClient extends ContentViewClient { public abstract InterceptedRequestData shouldInterceptRequest(String url); - public abstract boolean shouldOverrideUrlLoading(String url); + public abstract boolean shouldIgnoreNavigation(String url); public abstract void onUnhandledKeyEvent(KeyEvent event); diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwShouldIgnoreNavigationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwShouldIgnoreNavigationTest.java new file mode 100644 index 0000000..053fe75 --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwShouldIgnoreNavigationTest.java @@ -0,0 +1,678 @@ +// Copyright (c) 2012 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.android_webview.test; + +import android.os.Bundle; +import android.os.SystemClock; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; +import android.view.MotionEvent; +import android.util.Log; + +import org.chromium.android_webview.test.util.TestWebServer; +import org.chromium.base.test.util.DisabledTest; +import org.chromium.base.test.util.Feature; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.content.browser.test.util.CallbackHelper; +import org.chromium.content.browser.test.util.Criteria; +import org.chromium.content.browser.test.util.CriteriaHelper; +import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper; +import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnPageStartedHelper; +import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnReceivedErrorHelper; + +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +/** + * Tests for the WebViewClient.shouldOverrideUrlLoading() method. + */ +public class AwShouldIgnoreNavigationTest extends AndroidWebViewTestBase { + private final static String ABOUT_BLANK_URL = "about:blank"; + private final static String DATA_URL = "data:text/html,<div/>"; + + private static final long TEST_TIMEOUT = 20000L; + private static final int CHECK_INTERVAL = 100; + + private static class TestAwContentsClient + extends org.chromium.android_webview.test.TestAwContentsClient { + + public class ShouldIgnoreNavigationHelper extends CallbackHelper { + private String mShouldIgnoreNavigationUrl; + private String mPreviousShouldIgnoreNavigationUrl; + private boolean mShouldIgnoreNavigationReturnValue = false; + void setShouldIgnoreNavigationUrl(String url) { + mShouldIgnoreNavigationUrl = url; + } + void setPreviousShouldIgnoreNavigationUrl(String url) { + mPreviousShouldIgnoreNavigationUrl = url; + } + void setShouldIgnoreNavigationReturnValue(boolean value) { + mShouldIgnoreNavigationReturnValue = value; + } + public String getShouldIgnoreNavigationUrl() { + assert getCallCount() > 0; + return mShouldIgnoreNavigationUrl; + } + public String getPreviousShouldIgnoreNavigationUrl() { + assert getCallCount() > 1; + return mPreviousShouldIgnoreNavigationUrl; + } + public boolean getShouldIgnoreNavigationReturnValue() { + return mShouldIgnoreNavigationReturnValue; + } + public void notifyCalled(String url) { + mPreviousShouldIgnoreNavigationUrl = mShouldIgnoreNavigationUrl; + mShouldIgnoreNavigationUrl = url; + notifyCalled(); + } + } + + @Override + public boolean shouldIgnoreNavigation(String url) { + super.shouldIgnoreNavigation(url); + boolean returnValue = + mShouldIgnoreNavigationHelper.getShouldIgnoreNavigationReturnValue(); + mShouldIgnoreNavigationHelper.notifyCalled(url); + return returnValue; + } + + private ShouldIgnoreNavigationHelper mShouldIgnoreNavigationHelper; + + public TestAwContentsClient() { + super(); + mShouldIgnoreNavigationHelper = new ShouldIgnoreNavigationHelper(); + } + + public ShouldIgnoreNavigationHelper getShouldIgnoreNavigationHelper() { + return mShouldIgnoreNavigationHelper; + } + } + + private void clickOnLinkUsingJs(final ContentViewCore contentViewCore, + final TestAwContentsClient contentsClient) throws Throwable { + enableJavaScriptOnUiThread(contentViewCore); + + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + try { + String linkIsNotNull = executeJavaScriptAndWaitForResult(contentViewCore, + contentsClient, "document.getElementById('link') != null"); + return linkIsNotNull.equals("true"); + } catch (Throwable t) { + t.printStackTrace(); + fail("Failed to check if DOM is loaded: " + t.toString()); + return false; + } + } + }, WAIT_TIMEOUT_SECONDS * 1000, CHECK_INTERVAL)); + + runTestOnUiThread(new Runnable() { + @Override + public void run() { + contentViewCore.evaluateJavaScript( + "var evObj = document.createEvent('Events'); " + + "evObj.initEvent('click', true, false); " + + "document.getElementById('link').dispatchEvent(evObj);" + + "console.log('element with id link clicked');"); + } + }); + } + + // Since this value is read on the UI thread, it's simpler to set it there too. + void setShouldIgnoreNavigationReturnValueOnUiThread( + final TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper, + final boolean value) throws Throwable { + runTestOnUiThread(new Runnable() { + @Override + public void run() { + shouldIgnoreNavigationHelper.setShouldIgnoreNavigationReturnValue(value); + } + }); + } + + private String makeHtmlPageFrom(String headers, String body) { + return "<html>" + + "<head>" + + "<title>Title</title>" + + "<style type=\"text/css\">" + + // Make the image take up all of the page so that we don't have to do + // any fancy hit target calculations when synthesizing the touch event + // to click it. + "img.big { width:100%; height:100%; background-color:blue; }" + + "</style>" + + headers + + "</head>" + + "<body>" + + body + + "</body>" + + "</html>"; + } + + private String getHtmlForPageWithSimpleLinkTo(String destination) { + return makeHtmlPageFrom("", + "<a href=\"" + destination + "\" id=\"link\">" + + "<img class=\"big\" />" + + "</a>"); + } + + private String getHtmlForPageWithJsAssignLinkTo(String url) { + return makeHtmlPageFrom("", + "<img onclick=\"location.href='" + url + "'\" class=\"big\" id=\"link\" />"); + } + + private String getHtmlForPageWithJsReplaceLinkTo(String url) { + return makeHtmlPageFrom("", + "<img onclick=\"location.replace('" + url + "');\" class=\"big\" id=\"link\" />"); + } + + private String getHtmlForPageWithMetaRefreshRedirectTo(String url) { + return makeHtmlPageFrom("<meta http-equiv=\"refresh\" content=\"0;url=" + url + "\" />", + "<div>Meta refresh redirect</div>"); + } + + private String getHtmlForPageWithJsRedirectTo(String url, String method, int timeout) { + return makeHtmlPageFrom( + "<script>" + + "function doRedirectAssign() {" + + "location.href = '" + url + "';" + + "} " + + "function doRedirectReplace() {" + + "location.replace('" + url + "');" + + "} "+ + "</script>", + String.format("<iframe onLoad=\"setTimeout('doRedirect%s()', %d);\" />", + method, timeout)); + } + + private String addPageToTestServer(TestWebServer webServer, String httpPath, String html) { + List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>(); + headers.add(Pair.create("Content-Type", "text/html")); + headers.add(Pair.create("Cache-Control", "no-store")); + return webServer.setResponse(httpPath, html, headers); + } + + private String createRedirectTargetPage(TestWebServer webServer) { + return addPageToTestServer(webServer, "/redirect_target.html", + getHtmlForPageWithSimpleLinkTo(ABOUT_BLANK_URL)); + } + + /** + * @SmallTest + * @Feature({"Android-WebView", "Navigation"}) + * BUG=154558 + */ + @DisabledTest + public void testShouldIgnoreNavigationNotCalledOnLoadUrl() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(DATA_URL), "text/html", false); + + assertEquals(0, shouldIgnoreNavigationHelper.getCallCount()); + } + + /** + * @SmallTest + * @Feature({"Android-WebView", "Navigation"}) + * BUG=154558 + */ + @DisabledTest + public void testShouldIgnoreNavigationCantBlockLoads() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + setShouldIgnoreNavigationReturnValueOnUiThread(shouldIgnoreNavigationHelper, true); + + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(DATA_URL), "text/html", false); + + assertEquals("Title", getTitleOnUiThread(contentViewCore)); + } + + /** + * @SmallTest + * @Feature({"Android-WebView", "Navigation"}) + * BUG=154292 + */ + @DisabledTest + public void testShouldIgnoreNavigationCalledBeforeOnPageStarted() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + OnPageStartedHelper onPageStartedHelper = contentsClient.getOnPageStartedHelper(); + + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(DATA_URL), "text/html", false); + + final int shouldIgnoreNavigationCallCount = shouldIgnoreNavigationHelper.getCallCount(); + final int onPageStartedCallCount = onPageStartedHelper.getCallCount(); + setShouldIgnoreNavigationReturnValueOnUiThread(shouldIgnoreNavigationHelper, true); + clickOnLinkUsingJs(contentViewCore, contentsClient); + + shouldIgnoreNavigationHelper.waitForCallback(shouldIgnoreNavigationCallCount); + assertEquals(onPageStartedCallCount, onPageStartedHelper.getCallCount()); + } + + + /** + * @SmallTest + * @Feature({"Android-WebView", "Navigation"}) + * BUG=154292 + */ + @DisabledTest + public void testShouldIgnoreNavigationDoesNotCauseOnReceivedError() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + final TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + OnReceivedErrorHelper onReceivedErrorHelper = contentsClient.getOnReceivedErrorHelper(); + final int onReceivedErrorCallCount = onReceivedErrorHelper.getCallCount(); + + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(DATA_URL), "text/html", false); + + final int shouldIgnoreNavigationCallCount = shouldIgnoreNavigationHelper.getCallCount(); + + setShouldIgnoreNavigationReturnValueOnUiThread(shouldIgnoreNavigationHelper, true); + + clickOnLinkUsingJs(contentViewCore, contentsClient); + + shouldIgnoreNavigationHelper.waitForCallback(shouldIgnoreNavigationCallCount); + + setShouldIgnoreNavigationReturnValueOnUiThread(shouldIgnoreNavigationHelper, false); + + // After we load this URL we're certain that any in-flight callbacks for the previous + // navigation have been delivered. + loadUrlSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), ABOUT_BLANK_URL); + + assertEquals(onReceivedErrorCallCount, onReceivedErrorHelper.getCallCount()); + } + + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledWhenLinkClicked() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + // We can't go to about:blank from here because we'd get a cross-origin error. + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(DATA_URL), "text/html", false); + + int callCount = shouldIgnoreNavigationHelper.getCallCount(); + + clickOnLinkUsingJs(contentViewCore, contentsClient); + + shouldIgnoreNavigationHelper.waitForCallback(callCount); + } + + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledWhenSelfLinkClicked() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String httpPath = "/page_with_link_to_self.html"; + final String httpPathOnServer = webServer.getResponseUrl(httpPath); + addPageToTestServer(webServer, httpPath, + getHtmlForPageWithSimpleLinkTo(httpPathOnServer)); + + loadUrlSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + httpPathOnServer); + + int callCount = shouldIgnoreNavigationHelper.getCallCount(); + + clickOnLinkUsingJs(contentViewCore, contentsClient); + + shouldIgnoreNavigationHelper.waitForCallback(callCount); + assertEquals(httpPathOnServer, + shouldIgnoreNavigationHelper.getShouldIgnoreNavigationUrl()); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledWhenNavigatingFromJavaScriptUsingAssign() + throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + enableJavaScriptOnUiThread(contentViewCore); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithJsAssignLinkTo(redirectTargetUrl), "text/html", false); + + int callCount = shouldIgnoreNavigationHelper.getCallCount(); + + clickOnLinkUsingJs(contentViewCore, contentsClient); + + shouldIgnoreNavigationHelper.waitForCallback(callCount); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledWhenNavigatingFromJavaScriptUsingReplace() + throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + enableJavaScriptOnUiThread(contentViewCore); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithJsReplaceLinkTo(redirectTargetUrl), "text/html", false); + + int callCount = shouldIgnoreNavigationHelper.getCallCount(); + clickOnLinkUsingJs(contentViewCore, contentsClient); + shouldIgnoreNavigationHelper.waitForCallback(callCount); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationPassesCorrectUrl() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(redirectTargetUrl), "text/html", false); + + int callCount = shouldIgnoreNavigationHelper.getCallCount(); + clickOnLinkUsingJs(contentViewCore, contentsClient); + shouldIgnoreNavigationHelper.waitForCallback(callCount); + assertEquals(redirectTargetUrl, + shouldIgnoreNavigationHelper.getShouldIgnoreNavigationUrl()); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCanOverrideLoading() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + final TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(redirectTargetUrl), "text/html", false); + + setShouldIgnoreNavigationReturnValueOnUiThread(shouldIgnoreNavigationHelper, true); + + OnPageFinishedHelper onPageFinishedHelper = contentsClient.getOnPageFinishedHelper(); + int onPageFinishedCountBeforeClickingOnLink = onPageFinishedHelper.getCallCount(); + int callCount = shouldIgnoreNavigationHelper.getCallCount(); + clickOnLinkUsingJs(contentViewCore, contentsClient); + // Some time around here true should be returned from the shouldIgnoreNavigation + // callback causing the navigation caused by calling clickOnLinkUsingJs to be ignored. + // We validate this by indirectly checking that an onPageFinished callback was not + // issued after this point. + shouldIgnoreNavigationHelper.waitForCallback(callCount); + + setShouldIgnoreNavigationReturnValueOnUiThread(shouldIgnoreNavigationHelper, false); + + final String synchronizationUrl = ABOUT_BLANK_URL; + loadUrlSync(contentViewCore, onPageFinishedHelper, synchronizationUrl); + + assertEquals(synchronizationUrl, onPageFinishedHelper.getUrl()); + assertEquals(onPageFinishedHelper.getCallCount(), + onPageFinishedCountBeforeClickingOnLink + 1); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledForDataUrl() throws Throwable { + final String dataUrl = + "data:text/html;base64," + + "PGh0bWw+PGhlYWQ+PHRpdGxlPmRhdGFVcmxUZXN0QmFzZTY0PC90aXRsZT48" + + "L2hlYWQ+PC9odG1sPg=="; + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + loadDataSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + getHtmlForPageWithSimpleLinkTo(dataUrl), "text/html", false); + + int callCount = shouldIgnoreNavigationHelper.getCallCount(); + clickOnLinkUsingJs(contentViewCore, contentsClient); + + shouldIgnoreNavigationHelper.waitForCallback(callCount); + assertTrue("Expected URL that starts with 'data:' but got: <" + + shouldIgnoreNavigationHelper.getShouldIgnoreNavigationUrl() + "> instead.", + shouldIgnoreNavigationHelper.getShouldIgnoreNavigationUrl().startsWith( + "data:")); + } + + // The +1 comes from the fact that we call shouldIgnoreNavigation for URLs that were passed + // to the .loadUrl method. + // See BUG=154558. + private int adjustForBug154558(int callCount) { + return callCount + 1; + } + + /** + * Worker method for the various redirect tests. + * + * Calling this will first load the redirect URL built from redirectFilePath, query and + * locationFilePath and assert that we get a override callback for the destination. + * The second part of the test loads a page that contains a link which points at the redirect + * URL. We expect two callbacks - one for the redirect link and another for the destination. + */ + private void doTestShouldIgnoreNavigationCalledOnRedirect(TestWebServer webServer, + String redirectUrl, String redirectTarget) throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ContentViewCore contentViewCore = + createAwTestContainerViewOnMainSync(contentsClient).getContentViewCore(); + final String pageWithLinkToRedirectUrl = addPageToTestServer(webServer, + "/page_with_link_to_redirect.html", + getHtmlForPageWithSimpleLinkTo(redirectUrl)); + enableJavaScriptOnUiThread(contentViewCore); + + TestAwContentsClient.ShouldIgnoreNavigationHelper shouldIgnoreNavigationHelper = + contentsClient.getShouldIgnoreNavigationHelper(); + int directLoadCallCount = shouldIgnoreNavigationHelper.getCallCount(); + loadUrlSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), redirectUrl); + + shouldIgnoreNavigationHelper.waitForCallback(directLoadCallCount, adjustForBug154558(1)); + assertEquals(redirectTarget, + shouldIgnoreNavigationHelper.getShouldIgnoreNavigationUrl()); + + // There is a slight difference between navigations caused by calling load and navigations + // caused by clicking on a link: + // * when using load the navigation is treated as if it came from the URL bar (has the + // navigation type TYPED and doesn't have the has_user_gesture flag) + // * when clicking on a link the navigation has the LINK type and has_user_gesture is + // true. + // Both of these should yield the same result which is what we're verifying here. + int indirectLoadCallCount = shouldIgnoreNavigationHelper.getCallCount(); + loadUrlSync(contentViewCore, contentsClient.getOnPageFinishedHelper(), + pageWithLinkToRedirectUrl); + + // We waitForCallback here because we call shouldIgnoreNavigation for URLs that were passed + // to the .loadUrl method. See BUG=154558. + // Do this when the bug is fixed: + // assertEquals(indirectLoadCallCount, shouldIgnoreNavigationHelper.getCallCount()); + shouldIgnoreNavigationHelper.waitForCallback(indirectLoadCallCount, 1); + + clickOnLinkUsingJs(contentViewCore, contentsClient); + + shouldIgnoreNavigationHelper.waitForCallback(indirectLoadCallCount, adjustForBug154558(2)); + + assertEquals(redirectTarget, + shouldIgnoreNavigationHelper.getShouldIgnoreNavigationUrl()); + assertEquals(redirectUrl, + shouldIgnoreNavigationHelper.getPreviousShouldIgnoreNavigationUrl()); + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledOn302Redirect() throws Throwable { + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + final String redirectUrl = webServer.setRedirect("/302.html", redirectTargetUrl); + + doTestShouldIgnoreNavigationCalledOnRedirect(webServer, redirectUrl, + redirectTargetUrl); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledOnMetaRefreshRedirect() throws Throwable { + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + final String redirectUrl = addPageToTestServer(webServer, "/meta_refresh.html", + getHtmlForPageWithMetaRefreshRedirectTo(redirectTargetUrl)); + doTestShouldIgnoreNavigationCalledOnRedirect(webServer, redirectUrl, + redirectTargetUrl); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledOnJavaScriptLocationImmediateAssignRedirect() + throws Throwable { + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + final String redirectUrl = addPageToTestServer(webServer, "/js_immediate_assign.html", + getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Assign", 0)); + doTestShouldIgnoreNavigationCalledOnRedirect(webServer, redirectUrl, + redirectTargetUrl); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledOnJavaScriptLocationImmediateReplaceRedirect() + throws Throwable { + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + final String redirectUrl = addPageToTestServer(webServer, "/js_immediate_replace.html", + getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Replace", 0)); + doTestShouldIgnoreNavigationCalledOnRedirect(webServer, redirectUrl, + redirectTargetUrl); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledOnJavaScriptLocationDelayedAssignRedirect() + throws Throwable { + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + final String redirectUrl = addPageToTestServer(webServer, "/js_delayed_assign.html", + getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Assign", 100)); + doTestShouldIgnoreNavigationCalledOnRedirect(webServer, redirectUrl, + redirectTargetUrl); + } finally { + if (webServer != null) webServer.shutdown(); + } + } + + @SmallTest + @Feature({"Android-WebView", "Navigation"}) + public void testShouldIgnoreNavigationCalledOnJavaScriptLocationDelayedReplaceRedirect() + throws Throwable { + TestWebServer webServer = null; + try { + // Set up the HTML page. + webServer = new TestWebServer(false); + final String redirectTargetUrl = createRedirectTargetPage(webServer); + final String redirectUrl = addPageToTestServer(webServer, "/js_delayed_replace.html", + getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Replace", 100)); + doTestShouldIgnoreNavigationCalledOnRedirect(webServer, redirectUrl, + redirectTargetUrl); + } finally { + if (webServer != null) webServer.shutdown(); + } + } +} diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/NullContentsClient.java b/android_webview/javatests/src/org/chromium/android_webview/test/NullContentsClient.java index 5605715..9c4bd57 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/NullContentsClient.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/NullContentsClient.java @@ -20,7 +20,7 @@ import org.chromium.android_webview.JsResultReceiver; */ class NullContentsClient extends AwContentsClient { @Override - public boolean shouldOverrideUrlLoading(String url) { + public boolean shouldIgnoreNavigation(String url) { return false; } diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java index 569c0a4..656fbe5 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java @@ -75,7 +75,7 @@ class TestAwContentsClient extends NullContentsClient { mAddMessageToConsoleHelper.setLineNumber(consoleMessage.lineNumber()); mAddMessageToConsoleHelper.setSourceId(consoleMessage.sourceId()); mAddMessageToConsoleHelper.notifyCalled(); - return true; + return false; } public class AddMessageToConsoleHelper extends CallbackHelper { diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/util/TestWebServer.java b/android_webview/javatests/src/org/chromium/android_webview/test/util/TestWebServer.java index a02d3f9..1b37ed33 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/util/TestWebServer.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/util/TestWebServer.java @@ -73,8 +73,11 @@ public class TestWebServer { private static class Response { final byte[] mResponseData; final List<Pair<String, String>> mResponseHeaders; + final boolean mIsRedirect; - Response(byte[] resposneData, List<Pair<String, String>> responseHeaders) { + Response(byte[] resposneData, List<Pair<String, String>> responseHeaders, + boolean isRedirect) { + mIsRedirect = isRedirect; mResponseData = resposneData; mResponseHeaders = responseHeaders == null ? new ArrayList<Pair<String, String>>() : responseHeaders; @@ -142,12 +145,29 @@ public class TestWebServer { sInstance = null; } + private final static int RESPONSE_STATUS_NORMAL = 0; + private final static int RESPONSE_STATUS_MOVED_TEMPORARILY = 1; + private String setResponseInternal( String requestPath, byte[] responseData, - List<Pair<String, String>> responseHeaders) { - mResponseMap.put(requestPath, new Response(responseData, responseHeaders)); + List<Pair<String, String>> responseHeaders, + int status) { + final boolean isRedirect = (status == RESPONSE_STATUS_MOVED_TEMPORARILY); + mResponseMap.put(requestPath, new Response(responseData, responseHeaders, isRedirect)); mResponseCountMap.put(requestPath, new Integer(0)); mLastRequestMap.put(requestPath, null); + return getResponseUrl(requestPath); + } + + /** + * Gets the URL on the server under which a particular request path will be accessible. + * + * This only gets the URL, you still need to set the response if you intend to access it. + * + * @param requestPath The path to respond to. + * @return The full URL including the requestPath. + */ + public String getResponseUrl(String requestPath) { return mServerUri + requestPath; } @@ -165,7 +185,25 @@ public class TestWebServer { public String setResponse( String requestPath, String responseString, List<Pair<String, String>> responseHeaders) { - return setResponseInternal(requestPath, responseString.getBytes(), responseHeaders); + return setResponseInternal(requestPath, responseString.getBytes(), responseHeaders, + RESPONSE_STATUS_NORMAL); + } + + /** + * Sets a redirect. + * + * @param requestPath The path to respond to. + * @param targetPath The path to redirect to. + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setRedirect( + String requestPath, String targetPath) { + List<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>(); + responseHeaders.add(Pair.create("Location", targetPath)); + + return setResponseInternal(requestPath, targetPath.getBytes(), responseHeaders, + RESPONSE_STATUS_MOVED_TEMPORARILY); } /** @@ -185,7 +223,8 @@ public class TestWebServer { List<Pair<String, String>> responseHeaders) { return setResponseInternal(requestPath, Base64.decode(base64EncodedResponse, Base64.DEFAULT), - responseHeaders); + responseHeaders, + RESPONSE_STATUS_NORMAL); } /** @@ -281,6 +320,11 @@ public class TestWebServer { Response response = mResponseMap.get(path); if (path.equals(SHUTDOWN_PREFIX)) { httpResponse = createResponse(HttpStatus.SC_OK); + } else if (response.mIsRedirect) { + httpResponse = createResponse(HttpStatus.SC_MOVED_TEMPORARILY); + for (Pair<String, String> header : response.mResponseHeaders) { + httpResponse.addHeader(header.first, header.second); + } } else if (response == null) { httpResponse = createResponse(HttpStatus.SC_NOT_FOUND); } else { diff --git a/android_webview/native/android_webview_jni_registrar.cc b/android_webview/native/android_webview_jni_registrar.cc index f041015..bec2cb6 100644 --- a/android_webview/native/android_webview_jni_registrar.cc +++ b/android_webview/native/android_webview_jni_registrar.cc @@ -14,11 +14,17 @@ #include "android_webview/native/js_result_handler.h" #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" +#include "chrome/browser/component/navigation_interception/component_jni_registrar.h" #include "chrome/browser/component/web_contents_delegate_android/component_jni_registrar.h" namespace android_webview { static base::android::RegistrationMethod kWebViewRegisteredMethods[] = { + // Register JNI for components we depend on. + { "navigation_interception", navigation_interception::RegisterJni }, + { "web_contents_delegate_android", + web_contents_delegate_android::RegisterJni }, + // Register JNI for android_webview classes. { "AndroidProtocolHandler", RegisterAndroidProtocolHandler }, { "AndroidStreamReaderUrlRequestJob", RegisterAndroidStreamReaderUrlRequestJob }, @@ -31,9 +37,6 @@ static base::android::RegistrationMethod kWebViewRegisteredMethods[] = { }; bool RegisterJni(JNIEnv* env) { - if (!web_contents_delegate_android::RegisterJni(env)) - return false; - return RegisterNativeMethods(env, kWebViewRegisteredMethods, arraysize(kWebViewRegisteredMethods)); } diff --git a/android_webview/native/aw_contents.cc b/android_webview/native/aw_contents.cc index 257ea51..cc69024 100644 --- a/android_webview/native/aw_contents.cc +++ b/android_webview/native/aw_contents.cc @@ -16,6 +16,7 @@ #include "base/bind.h" #include "base/callback.h" #include "base/supports_user_data.h" +#include "chrome/browser/component/navigation_interception/intercept_navigation_delegate.h" #include "content/public/browser/android/content_view_core.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/cert_store.h" @@ -36,6 +37,7 @@ using base::android::ScopedJavaLocalRef; using content::BrowserThread; using content::ContentViewCore; using content::WebContents; +using navigation_interception::InterceptNavigationDelegate; namespace android_webview { @@ -219,6 +221,16 @@ void AwContents::SetIoThreadClient(JNIEnv* env, jobject obj, jobject client) { web_contents, ScopedJavaLocalRef<jobject>(env, client)); } +void AwContents::SetInterceptNavigationDelegate(JNIEnv* env, + jobject obj, + jobject delegate) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + content::WebContents* web_contents = contents_container_->GetWebContents(); + InterceptNavigationDelegate::Associate( + web_contents, + make_scoped_ptr(new InterceptNavigationDelegate(env, delegate))); +} + static jint Init(JNIEnv* env, jobject obj, jobject web_contents_delegate, diff --git a/android_webview/native/aw_contents.h b/android_webview/native/aw_contents.h index 6228cef..1e856ef 100644 --- a/android_webview/native/aw_contents.h +++ b/android_webview/native/aw_contents.h @@ -65,6 +65,8 @@ class AwContents : public FindHelper::Listener { void DocumentHasImages(JNIEnv* env, jobject obj, jobject message); void GenerateMHTML(JNIEnv* env, jobject obj, jstring jpath, jobject callback); void SetIoThreadClient(JNIEnv* env, jobject obj, jobject client); + void SetInterceptNavigationDelegate(JNIEnv* env, jobject obj, + jobject delegate); base::android::ScopedJavaLocalRef<jbyteArray> GetCertificate( JNIEnv* env, jobject obj); diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py index 3ac64cf..f8df00a 100755 --- a/base/android/jni_generator/jni_generator.py +++ b/base/android/jni_generator/jni_generator.py @@ -180,7 +180,10 @@ def JavaParamToJni(param): 'Lorg/chromium/chrome/browser/FindNotificationDetails', 'Lorg/chromium/chrome/browser/JavascriptAppModalDialog', 'Lorg/chromium/chrome/browser/ProcessUtils', - 'Lorg/chromium/chrome/browser/component/web_contents_delegate_android/WebContentsDelegateAndroid', + ('Lorg/chromium/chrome/browser/component/navigation_interception/' + 'InterceptNavigationDelegate'), + ('Lorg/chromium/chrome/browser/component/web_contents_delegate_android/' + 'WebContentsDelegateAndroid'), 'Lorg/chromium/chrome/browser/database/SQLiteCursor', 'Lorg/chromium/content/app/SandboxedProcessService', 'Lorg/chromium/content/browser/ContentVideoView', diff --git a/chrome/browser/component/components.gyp b/chrome/browser/component/components.gyp index 2145902..8fab5f9 100644 --- a/chrome/browser/component/components.gyp +++ b/chrome/browser/component/components.gyp @@ -5,7 +5,8 @@ { 'includes': [ - 'web_contents_delegate_android/web_contents_delegate_android.gypi' + 'navigation_interception/navigation_interception.gypi', + 'web_contents_delegate_android/web_contents_delegate_android.gypi', ], 'conditions': [ @@ -16,6 +17,7 @@ 'type': 'none', 'sources': [ 'web_contents_delegate_android/java/src/org/chromium/chrome/browser/component/web_contents_delegate_android/WebContentsDelegateAndroid.java', + 'navigation_interception/java/src/org/chromium/chrome/browser/component/navigation_interception/InterceptNavigationDelegate.java', ], 'variables': { 'jni_gen_dir': 'chrome/browser_component', @@ -26,4 +28,3 @@ }], ], } - diff --git a/chrome/browser/component/navigation_interception/DEPS b/chrome/browser/component/navigation_interception/DEPS new file mode 100644 index 0000000..4d85167 --- /dev/null +++ b/chrome/browser/component/navigation_interception/DEPS @@ -0,0 +1,12 @@ +include_rules = [ + "+content/public", + "+net/url_request", +] + +specific_include_rules = { + '.*_(a-z)+test\.cc': [ + # Temporary until test case moves into a separate test runner. + "!chrome/test/base", + "+testing", + ], +} diff --git a/chrome/browser/component/navigation_interception/component_jni_registrar.cc b/chrome/browser/component/navigation_interception/component_jni_registrar.cc new file mode 100644 index 0000000..21dd29a --- /dev/null +++ b/chrome/browser/component/navigation_interception/component_jni_registrar.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2012 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. + +#include "chrome/browser/component/navigation_interception/component_jni_registrar.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "chrome/browser/component/navigation_interception/intercept_navigation_delegate.h" + +namespace navigation_interception { + +static base::android::RegistrationMethod kComponentRegisteredMethods[] = { + { "InterceptNavigationDelegate", RegisterInterceptNavigationDelegate }, +}; + +bool RegisterJni(JNIEnv* env) { + return RegisterNativeMethods(env, + kComponentRegisteredMethods, arraysize(kComponentRegisteredMethods)); +} + +} // namespace navigation_interception diff --git a/chrome/browser/component/navigation_interception/component_jni_registrar.h b/chrome/browser/component/navigation_interception/component_jni_registrar.h new file mode 100644 index 0000000..adbe66a --- /dev/null +++ b/chrome/browser/component/navigation_interception/component_jni_registrar.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ +#define CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ + +#include <jni.h> + +namespace navigation_interception { + +// Register all JNI bindings necessary for the navigation_interception +// component. +bool RegisterJni(JNIEnv* env); + +} // namespace navigation_interception + +#endif // CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ diff --git a/chrome/browser/component/navigation_interception/intercept_navigation_delegate.cc b/chrome/browser/component/navigation_interception/intercept_navigation_delegate.cc new file mode 100644 index 0000000..3b934fa --- /dev/null +++ b/chrome/browser/component/navigation_interception/intercept_navigation_delegate.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2012 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. + +#include "chrome/browser/component/navigation_interception/intercept_navigation_delegate.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/callback.h" +#include "chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "googleurl/src/gurl.h" +#include "jni/InterceptNavigationDelegate_jni.h" +#include "net/url_request/url_request.h" + +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; +using content::BrowserThread; +using content::RenderViewHost; +using content::WebContents; + +namespace navigation_interception { + +namespace { + +const void* kInterceptNavigationDelegateUserDataKey = + &kInterceptNavigationDelegateUserDataKey; + +bool CheckIfShouldIgnoreNavigationOnUIThread(RenderViewHost* source, + const GURL& url, + const content::Referrer& referrer, + bool has_user_gesture) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(source); + + WebContents* web_contents = WebContents::FromRenderViewHost(source); + if (!web_contents) + return false; + InterceptNavigationDelegate* intercept_navigation_delegate = + InterceptNavigationDelegate::Get(web_contents); + if (!intercept_navigation_delegate) + return false; + + return intercept_navigation_delegate->ShouldIgnoreNavigation( + url, has_user_gesture); +} + +} // namespace + +// static +void InterceptNavigationDelegate::Associate( + WebContents* web_contents, + scoped_ptr<InterceptNavigationDelegate> delegate) { + web_contents->SetUserData(kInterceptNavigationDelegateUserDataKey, + delegate.release()); +} + +// static +InterceptNavigationDelegate* InterceptNavigationDelegate::Get( + WebContents* web_contents) { + return reinterpret_cast<InterceptNavigationDelegate*>( + web_contents->GetUserData(kInterceptNavigationDelegateUserDataKey)); +} + +// static +content::ResourceThrottle* InterceptNavigationDelegate::CreateThrottleFor( + net::URLRequest* request) { + return new InterceptNavigationResourceThrottle( + request, base::Bind(&CheckIfShouldIgnoreNavigationOnUIThread)); +} + +InterceptNavigationDelegate::InterceptNavigationDelegate( + JNIEnv* env, jobject jdelegate) + : weak_jdelegate_(env, jdelegate) { +} + +InterceptNavigationDelegate::~InterceptNavigationDelegate() { +} + +bool InterceptNavigationDelegate::ShouldIgnoreNavigation( + const GURL& url, + bool has_user_gesture) { + if (!url.is_valid()) + return false; + + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env); + + if (jdelegate.is_null()) + return false; + + ScopedJavaLocalRef<jstring> jstring_url = + ConvertUTF8ToJavaString(env, url.spec()); + return Java_InterceptNavigationDelegate_shouldIgnoreNavigation( + env, + jdelegate.obj(), + jstring_url.obj(), + has_user_gesture); +} + +// Register native methods. + +bool RegisterInterceptNavigationDelegate(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace navigation_interception diff --git a/chrome/browser/component/navigation_interception/intercept_navigation_delegate.h b/chrome/browser/component/navigation_interception/intercept_navigation_delegate.h new file mode 100644 index 0000000..cc1f0d3 --- /dev/null +++ b/chrome/browser/component/navigation_interception/intercept_navigation_delegate.h @@ -0,0 +1,66 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_DELEGATE_H_ +#define CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_DELEGATE_H_ + +#include "base/android/jni_helper.h" +#include "base/memory/scoped_ptr.h" +#include "base/supports_user_data.h" + +class GURL; + +namespace content { +class ResourceThrottle; +class WebContents; +} + +namespace net { +class URLRequest; +} + +namespace navigation_interception { + +// Native side of the InterceptNavigationDelegate Java interface. +// This is used to create a InterceptNavigationResourceThrottle that calls the +// Java interface method to determine whether a navigation should be ignored or +// not. +// To us this class: +// 1) the Java-side interface implementation must be associated (via the +// Associate method) with a WebContents for which URLRequests are to be +// intercepted, +// 2) the ResourceThrottle obtained via CreateThrottleFor must be associated +// with the URLRequests in the ResourceDispatcherHostDelegate +// implementation. +class InterceptNavigationDelegate : public base::SupportsUserData::Data { + public: + InterceptNavigationDelegate(JNIEnv* env, jobject jdelegate); + virtual ~InterceptNavigationDelegate(); + + // Associates the InterceptNavigationDelegate with a WebContents using the + // SupportsUserData mechanism. + // As implied by the use of scoped_ptr, the WebContents will assume ownership + // of |delegate|. + static void Associate(content::WebContents* web_contents, + scoped_ptr<InterceptNavigationDelegate> delegate); + // Gets the InterceptNavigationDelegate associated with the WebContents, + // can be null. + static InterceptNavigationDelegate* Get(content::WebContents* web_contents); + + // Creates a InterceptNavigationResourceThrottle that will direct all + // callbacks to the InterceptNavigationDelegate. + static content::ResourceThrottle* CreateThrottleFor( + net::URLRequest* request); + + virtual bool ShouldIgnoreNavigation(const GURL& url, + bool has_user_gesture); + private: + JavaObjectWeakGlobalRef weak_jdelegate_; +}; + +bool RegisterInterceptNavigationDelegate(JNIEnv* env); + +} // namespace navigation_interception + +#endif // CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_DELEGATE_H_ diff --git a/chrome/browser/renderer_host/intercept_navigation_resource_throttle.cc b/chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle.cc index 3852627..1180ab6 100644 --- a/chrome/browser/renderer_host/intercept_navigation_resource_throttle.cc +++ b/chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/renderer_host/intercept_navigation_resource_throttle.h" +#include "chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" @@ -19,6 +19,8 @@ using content::Referrer; using content::RenderViewHost; using content::ResourceRequestInfo; +namespace navigation_interception { + namespace { void CheckIfShouldIgnoreNavigationOnUIThread( @@ -26,7 +28,7 @@ void CheckIfShouldIgnoreNavigationOnUIThread( int render_view_id, const GURL& url, const Referrer& referrer, - bool is_content_initiated, + bool has_user_gesture, InterceptNavigationResourceThrottle::CheckOnUIThreadCallback should_ignore_callback, base::Callback<void(bool)> callback) { @@ -42,7 +44,7 @@ void CheckIfShouldIgnoreNavigationOnUIThread( bool should_ignore_navigation = false; should_ignore_navigation = should_ignore_callback.Run( - rvh, validated_url, referrer, is_content_initiated); + rvh, validated_url, referrer, has_user_gesture); BrowserThread::PostTask( BrowserThread::IO, @@ -117,3 +119,5 @@ void InterceptNavigationResourceThrottle::OnResultObtained( controller()->Resume(); } } + +} // namespace navigation_interception diff --git a/chrome/browser/renderer_host/intercept_navigation_resource_throttle.h b/chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle.h index 9425e93..b807663 100644 --- a/chrome/browser/renderer_host/intercept_navigation_resource_throttle.h +++ b/chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_RENDERER_HOST_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ -#define CHROME_BROWSER_RENDERER_HOST_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ +#ifndef CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ +#define CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ #include <string> @@ -22,6 +22,8 @@ namespace net { class URLRequest; } +namespace navigation_interception { + // This class allows the provider of the Callback to selectively ignore top // level navigations. class InterceptNavigationResourceThrottle : public content::ResourceThrottle { @@ -29,7 +31,7 @@ class InterceptNavigationResourceThrottle : public content::ResourceThrottle { typedef base::Callback<bool(content::RenderViewHost* /* source */, const GURL& /*url*/, const content::Referrer& /*referrer*/, - bool /*is_content_initiated*/)> + bool /*has_user_gesture*/)> CheckOnUIThreadCallback; InterceptNavigationResourceThrottle( @@ -52,4 +54,6 @@ class InterceptNavigationResourceThrottle : public content::ResourceThrottle { DISALLOW_COPY_AND_ASSIGN(InterceptNavigationResourceThrottle); }; -#endif // CHROME_BROWSER_RENDERER_HOST_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ +} // namespace navigation_interception + +#endif // CHROME_BROWSER_COMPONENT_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ diff --git a/chrome/browser/renderer_host/intercept_navigation_resource_throttle_unittest.cc b/chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle_unittest.cc index c2e7693..9835f0d 100644 --- a/chrome/browser/renderer_host/intercept_navigation_resource_throttle_unittest.cc +++ b/chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle_unittest.cc @@ -6,15 +6,15 @@ #include "base/bind_helpers.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" -#include "chrome/browser/renderer_host/intercept_navigation_resource_throttle.h" +#include "chrome/browser/component/navigation_interception/intercept_navigation_resource_throttle.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/resource_context.h" +#include "content/public/browser/resource_controller.h" #include "content/public/browser/resource_dispatcher_host.h" #include "content/public/browser/resource_dispatcher_host_delegate.h" #include "content/public/browser/resource_request_info.h" #include "content/public/browser/resource_throttle.h" -#include "content/public/browser/resource_controller.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/test/mock_resource_context.h" @@ -24,6 +24,7 @@ #include "testing/gtest/include/gtest/gtest.h" using namespace content; +using namespace navigation_interception; using namespace ::testing; namespace { @@ -47,7 +48,7 @@ class MockInterceptCallbackReceiver { MOCK_METHOD4(ShouldIgnoreNavigation, bool(RenderViewHost* source, const GURL& url, const content::Referrer& referrer, - bool is_content_initiated)); + bool has_user_gesture)); }; // MockResourceController ----------------------------------------------------- diff --git a/chrome/browser/component/navigation_interception/java/src/org/chromium/chrome/browser/component/navigation_interception/InterceptNavigationDelegate.java b/chrome/browser/component/navigation_interception/java/src/org/chromium/chrome/browser/component/navigation_interception/InterceptNavigationDelegate.java new file mode 100644 index 0000000..91a28f3 --- /dev/null +++ b/chrome/browser/component/navigation_interception/java/src/org/chromium/chrome/browser/component/navigation_interception/InterceptNavigationDelegate.java @@ -0,0 +1,21 @@ +// Copyright (c) 2012 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.component.navigation_interception; + +import org.chromium.base.CalledByNative; + +public interface InterceptNavigationDelegate { + /** + * This method is called for every top-level navigation within the associated WebContents. + * The method allows the embedder to ignore navigations. This is used on Android to 'convert' + * certain navigations to Intents to 3rd party applications. + * + * @param url the target url of the navigation. + * @param isUserGestrue true if the navigation was initiated by the user. + * @return true if the navigation should be ignored. + */ + @CalledByNative + boolean shouldIgnoreNavigation(String url, boolean isUserGestrue); +} diff --git a/chrome/browser/component/navigation_interception/navigation_interception.gypi b/chrome/browser/component/navigation_interception/navigation_interception.gypi new file mode 100644 index 0000000..af9de6e --- /dev/null +++ b/chrome/browser/component/navigation_interception/navigation_interception.gypi @@ -0,0 +1,60 @@ +# Copyright (c) 2012 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. + +{ + 'targets': [ + { + 'target_name': 'navigation_interception', + 'type': 'static_library', + 'dependencies': [ + '../../../base/base.gyp:base', + '../../../content/content.gyp:content_browser', + '../../../content/content.gyp:content_common', + '../../../net/net.gyp:net', + ], + 'include_dirs': [ + '../../../..', + '../../../../skia/config', + '<(SHARED_INTERMEDIATE_DIR)/chrome/browser_component', + + ], + 'sources': [ + 'intercept_navigation_resource_throttle.cc', + 'intercept_navigation_resource_throttle.h', + ], + 'conditions': [ + ['OS=="android"', { + 'dependencies': [ + 'browser_component_jni_headers', + ], + 'sources': [ + 'component_jni_registrar.cc', + 'component_jni_registrar.h', + 'intercept_navigation_delegate.cc', + 'intercept_navigation_delegate.h', + ], + }], + ], + }, + ], + 'conditions': [ + ['OS=="android"', { + 'targets': [ + { + 'target_name': 'navigation_interception_java', + 'type': 'none', + 'dependencies': [ + '../../../base/base.gyp:base', + ], + 'variables': { + 'package_name': 'navigation_interception', + 'java_in_dir': 'java', + }, + 'includes': [ '../../../../build/java.gypi' ], + }, + ], + }], + ], +} diff --git a/chrome/browser/component/web_contents_delegate_android/web_contents_delegate_android.gypi b/chrome/browser/component/web_contents_delegate_android/web_contents_delegate_android.gypi index ad15c3f..922e802 100644 --- a/chrome/browser/component/web_contents_delegate_android/web_contents_delegate_android.gypi +++ b/chrome/browser/component/web_contents_delegate_android/web_contents_delegate_android.gypi @@ -4,43 +4,47 @@ # found in the LICENSE file. { - 'targets': [ - { - 'target_name': 'web_contents_delegate_android', - 'type': 'static_library', - 'dependencies': [ - '../../../base/base.gyp:base', - '../../../content/content.gyp:content_browser', - '../../../content/content.gyp:content_common', - '../../../net/net.gyp:net', - '../../../skia/skia.gyp:skia', - '../../../ui/ui.gyp:ui', - '../../../webkit/support/webkit_support.gyp:glue', - 'browser_component_jni_headers', + 'conditions': [ + ['OS=="android"', { + 'targets': [ + { + 'target_name': 'web_contents_delegate_android', + 'type': 'static_library', + 'dependencies': [ + '../../../base/base.gyp:base', + '../../../content/content.gyp:content_browser', + '../../../content/content.gyp:content_common', + '../../../net/net.gyp:net', + '../../../skia/skia.gyp:skia', + '../../../ui/ui.gyp:ui', + '../../../webkit/support/webkit_support.gyp:glue', + 'browser_component_jni_headers', + ], + 'include_dirs': [ + '../../..', + '../../../skia/config', + '<(SHARED_INTERMEDIATE_DIR)/chrome/browser_component', + ], + 'sources': [ + 'component_jni_registrar.cc', + 'component_jni_registrar.h', + 'web_contents_delegate_android.cc', + 'web_contents_delegate_android.h', + ], + }, + { + 'target_name': 'web_contents_delegate_android_java', + 'type': 'none', + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + ], + 'variables': { + 'package_name': 'web_contents_delegate_android', + 'java_in_dir': 'java', + }, + 'includes': [ '../../../../build/java.gypi' ], + }, ], - 'include_dirs': [ - '../../..', - '../../../skia/config', - '<(SHARED_INTERMEDIATE_DIR)/chrome/browser_component', - ], - 'sources': [ - 'component_jni_registrar.cc', - 'component_jni_registrar.h', - 'web_contents_delegate_android.cc', - 'web_contents_delegate_android.h', - ], - }, - { - 'target_name': 'web_contents_delegate_android_java', - 'type': 'none', - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - ], - 'variables': { - 'package_name': 'web_contents_delegate_android', - 'java_in_dir': 'java', - }, - 'includes': [ '../../../../build/java.gypi' ], - }, + }], ], } diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 5a607ab..c06e251 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -12,9 +12,10 @@ 'dependencies': [ 'app/policy/cloud_policy_codegen.gyp:policy', 'autofill_regexes', + 'browser/component/components.gyp:navigation_interception', + 'browser/performance_monitor/performance_monitor.gyp:performance_monitor', 'browser_extensions', 'browser_ui', - 'browser/performance_monitor/performance_monitor.gyp:performance_monitor', 'cert_logger_proto', 'chrome_resources.gyp:chrome_extra_resources', 'chrome_resources.gyp:chrome_resources', @@ -1595,8 +1596,6 @@ 'browser/renderer_host/chrome_resource_dispatcher_host_delegate.h', 'browser/renderer_host/chrome_url_request_user_data.cc', 'browser/renderer_host/chrome_url_request_user_data.h', - 'browser/renderer_host/intercept_navigation_resource_throttle.cc', - 'browser/renderer_host/intercept_navigation_resource_throttle.h', 'browser/renderer_host/offline_resource_throttle.cc', 'browser/renderer_host/offline_resource_throttle.h', 'browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 5899e3b..5a70d3a2 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1197,10 +1197,11 @@ 'browser/chromeos/version_loader_unittest.cc', 'browser/chromeos/web_socket_proxy_helper_unittest.cc', 'browser/command_updater_unittest.cc', - 'browser/component_updater/test/component_installers_unittest.cc', - 'browser/component_updater/test/component_updater_service_unittest.cc', + 'browser/component/navigation_interception/intercept_navigation_resource_throttle_unittest.cc', 'browser/component_updater/component_updater_interceptor.cc', 'browser/component_updater/component_updater_interceptor.h', + 'browser/component_updater/test/component_installers_unittest.cc', + 'browser/component_updater/test/component_updater_service_unittest.cc', 'browser/content_settings/content_settings_default_provider_unittest.cc', 'browser/content_settings/content_settings_mock_observer.cc', 'browser/content_settings/content_settings_mock_observer.h', @@ -1555,7 +1556,6 @@ 'browser/protector/prefs_backup_invalid_change_unittest.cc', 'browser/protector/protected_prefs_watcher_unittest.cc', 'browser/protector/session_startup_change_unittest.cc', - 'browser/renderer_host/intercept_navigation_resource_throttle_unittest.cc', 'browser/renderer_host/plugin_info_message_filter_unittest.cc', 'browser/renderer_host/web_cache_manager_unittest.cc', 'browser/resources/print_preview/data/measurement_system.js', |