From a83c3cafa72c29051cbcf2b5a26a33d488559547 Mon Sep 17 00:00:00 2001 From: mnaganov Date: Tue, 17 Mar 2015 08:56:44 -0700 Subject: [Android WebView] Implement a better OnReceivedError callback The new callback (aptly named OnReceivedError2) is also called for subresource load failures and provides more information about the failed request. Note that calling OnReceivedError2 is not yet implemented for requests blocked due to security considerations by Blink. This will be implemented separately. BUG=456782 Review URL: https://codereview.chromium.org/1001003004 Cr-Commit-Position: refs/heads/master@{#320923} --- .../browser/aw_contents_io_thread_client.h | 5 +- android_webview/browser/aw_request_interceptor.cc | 5 +- android_webview/browser/aw_request_interceptor.h | 1 - .../aw_resource_dispatcher_host_delegate.cc | 18 + .../aw_resource_dispatcher_host_delegate.h | 2 + .../org/chromium/android_webview/AwContents.java | 26 ++ .../AwContentsClientCallbackHelper.java | 10 + .../android_webview/AwContentsIoThreadClient.java | 26 ++ .../android_webview/AwContentsStatics.java | 4 + .../android_webview/AwWebContentsObserver.java | 26 +- .../android_webview/ErrorCodeConversionHelper.java | 2 + .../test/AwWebContentsObserverTest.java | 47 --- .../test/ClientOnPageFinishedTest.java | 17 + .../test/ClientOnReceivedError2Test.java | 414 +++++++++++++++++++++ .../android_webview/test/TestAwContentsClient.java | 32 ++ .../native/aw_contents_io_thread_client_impl.cc | 142 +++---- .../native/aw_contents_io_thread_client_impl.h | 2 +- 17 files changed, 636 insertions(+), 143 deletions(-) create mode 100644 android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java (limited to 'android_webview') diff --git a/android_webview/browser/aw_contents_io_thread_client.h b/android_webview/browser/aw_contents_io_thread_client.h index 15fddc8..e240976 100644 --- a/android_webview/browser/aw_contents_io_thread_client.h +++ b/android_webview/browser/aw_contents_io_thread_client.h @@ -70,7 +70,6 @@ class AwContentsIoThreadClient { // This method is called on the IO thread only. virtual scoped_ptr ShouldInterceptRequest( - const GURL& location, const net::URLRequest* request) = 0; // Retrieve the AllowContentAccess setting value of this AwContents. @@ -104,6 +103,10 @@ class AwContentsIoThreadClient { const std::string& account, const std::string& args) = 0; + // Called when a resource loading error has occured (e.g. an I/O error, + // host name lookup failure etc.) + virtual void OnReceivedError(const net::URLRequest* request) = 0; + // Called when a response from the server is received with status code >= 400. virtual void OnReceivedHttpError( const net::URLRequest* request, diff --git a/android_webview/browser/aw_request_interceptor.cc b/android_webview/browser/aw_request_interceptor.cc index 381a8ab..45adfb6 100644 --- a/android_webview/browser/aw_request_interceptor.cc +++ b/android_webview/browser/aw_request_interceptor.cc @@ -36,7 +36,6 @@ AwRequestInterceptor::~AwRequestInterceptor() { scoped_ptr AwRequestInterceptor::QueryForAwWebResourceResponse( - const GURL& location, net::URLRequest* request) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); int render_process_id, render_frame_id; @@ -50,7 +49,7 @@ AwRequestInterceptor::QueryForAwWebResourceResponse( if (!io_thread_client.get()) return scoped_ptr(); - return io_thread_client->ShouldInterceptRequest(location, request).Pass(); + return io_thread_client->ShouldInterceptRequest(request).Pass(); } net::URLRequestJob* AwRequestInterceptor::MaybeInterceptRequest( @@ -71,7 +70,7 @@ net::URLRequestJob* AwRequestInterceptor::MaybeInterceptRequest( new base::SupportsUserData::Data()); scoped_ptr aw_web_resource_response = - QueryForAwWebResourceResponse(request->url(), request); + QueryForAwWebResourceResponse(request); if (!aw_web_resource_response) return NULL; diff --git a/android_webview/browser/aw_request_interceptor.h b/android_webview/browser/aw_request_interceptor.h index adbb18b..a3c54b3 100644 --- a/android_webview/browser/aw_request_interceptor.h +++ b/android_webview/browser/aw_request_interceptor.h @@ -36,7 +36,6 @@ class AwRequestInterceptor : public net::URLRequestInterceptor { private: scoped_ptr QueryForAwWebResourceResponse( - const GURL& location, net::URLRequest* request) const; DISALLOW_COPY_AND_ASSIGN(AwRequestInterceptor); 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 36f37d4..dfa4aa2 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 @@ -24,6 +24,7 @@ #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" +#include "net/url_request/url_request_status.h" #include "url/url_constants.h" using android_webview::AwContentsIoThreadClient; @@ -241,6 +242,23 @@ void AwResourceDispatcherHostDelegate::OnRequestRedirected( AddExtraHeadersIfNeeded(request, resource_context); } +void AwResourceDispatcherHostDelegate::RequestComplete( + net::URLRequest* request) { + if (request && !request->status().is_success()) { + const content::ResourceRequestInfo* request_info = + content::ResourceRequestInfo::ForRequest(request); + scoped_ptr io_client = + AwContentsIoThreadClient::FromID(request_info->GetChildID(), + request_info->GetRenderFrameID()); + if (io_client) { + io_client->OnReceivedError(request); + } else { + DLOG(WARNING) << "io_client is null, onReceivedError dropped for " << + request->url(); + } + } +} + void AwResourceDispatcherHostDelegate::DownloadStarting( net::URLRequest* request, diff --git a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h index dd08235..3add203 100644 --- a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h +++ b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h @@ -60,6 +60,8 @@ class AwResourceDispatcherHostDelegate content::ResourceContext* resource_context, content::ResourceResponse* response) override; + void RequestComplete(net::URLRequest* request) override; + void RemovePendingThrottleOnIoThread(IoThreadClientThrottle* throttle); static void OnIoThreadClientReady(int new_render_process_id, 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 8fa1b33..96d3481 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -62,6 +62,7 @@ import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.navigation_controller.LoadURLType; import org.chromium.content_public.browser.navigation_controller.UserAgentOverrideOption; import org.chromium.content_public.common.Referrer; +import org.chromium.net.NetError; import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.WindowAndroid; @@ -419,6 +420,31 @@ public class AwContents implements SmartClipProvider, } @Override + public void onReceivedError(AwContentsClient.AwWebResourceRequest request, + AwContentsClient.AwWebResourceError error) { + String unreachableWebDataUrl = AwContentsStatics.getUnreachableWebDataUrl(); + boolean isErrorUrl = + unreachableWebDataUrl != null && unreachableWebDataUrl.equals(request.url); + if (!isErrorUrl && error.errorCode != NetError.ERR_ABORTED) { + // NetError.ERR_ABORTED error code is generated for the following reasons: + // - WebView.stopLoading is called; + // - the navigation is intercepted by the embedder via shouldOverrideUrlLoading; + // - server returned 204 status (no content). + // + // Android WebView does not notify the embedder of these situations using + // this error code with the WebViewClient.onReceivedError callback. + error.errorCode = ErrorCodeConversionHelper.convertErrorCode(error.errorCode); + mContentsClient.getCallbackHelper().postOnReceivedError(request, error); + if (request.isMainFrame) { + // Need to call onPageFinished after onReceivedError for backwards compatibility + // with the classic webview. See also AwWebContentsObserver.didFailLoad which is + // used when we want to send onPageFinished alone. + mContentsClient.getCallbackHelper().postOnPageFinished(request.url); + } + } + } + + @Override public void onReceivedHttpError(AwContentsClient.AwWebResourceRequest request, AwWebResourceResponse response) { mContentsClient.getCallbackHelper().postOnReceivedHttpError(request, response); diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClientCallbackHelper.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClientCallbackHelper.java index 73dd0c8..500d22c 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContentsClientCallbackHelper.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClientCallbackHelper.java @@ -88,6 +88,7 @@ public class AwContentsClientCallbackHelper { private static final int MSG_ON_NEW_PICTURE = 6; private static final int MSG_ON_SCALE_CHANGED_SCALED = 7; private static final int MSG_ON_RECEIVED_HTTP_ERROR = 8; + private static final int MSG_ON_PAGE_FINISHED = 9; // Minimum period allowed between consecutive onNewPicture calls, to rate-limit the callbacks. private static final long ON_NEW_PICTURE_MIN_PERIOD_MILLIS = 500; @@ -157,6 +158,11 @@ public class AwContentsClientCallbackHelper { mContentsClient.onReceivedHttpError(info.mRequest, info.mResponse); break; } + case MSG_ON_PAGE_FINISHED: { + final String url = (String) msg.obj; + mContentsClient.onPageFinished(url); + break; + } default: throw new IllegalStateException( "AwContentsClientCallbackHelper: unhandled message " + msg.what); @@ -219,4 +225,8 @@ public class AwContentsClientCallbackHelper { new OnReceivedHttpErrorInfo(request, response); mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_HTTP_ERROR, info)); } + + public void postOnPageFinished(String url) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PAGE_FINISHED, url)); + } } diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsIoThreadClient.java b/android_webview/java/src/org/chromium/android_webview/AwContentsIoThreadClient.java index d298c25..07c0299 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContentsIoThreadClient.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContentsIoThreadClient.java @@ -43,6 +43,9 @@ public abstract class AwContentsIoThreadClient { public abstract AwWebResourceResponse shouldInterceptRequest( AwContentsClient.AwWebResourceRequest request); + public abstract void onReceivedError(AwContentsClient.AwWebResourceRequest request, + AwContentsClient.AwWebResourceError error); + public abstract void onReceivedHttpError(AwContentsClient.AwWebResourceRequest request, AwWebResourceResponse response); @@ -66,6 +69,29 @@ public abstract class AwContentsIoThreadClient { } @CalledByNative + protected void onReceivedError( + // WebResourceRequest + String url, boolean isMainFrame, boolean hasUserGesture, String method, + String[] requestHeaderNames, String[] requestHeaderValues, + // WebResourceError + int errorCode, String description) { + AwContentsClient.AwWebResourceRequest request = + new AwContentsClient.AwWebResourceRequest(); + request.url = url; + request.isMainFrame = isMainFrame; + request.hasUserGesture = hasUserGesture; + request.method = method; + request.requestHeaders = new HashMap(requestHeaderNames.length); + for (int i = 0; i < requestHeaderNames.length; ++i) { + request.requestHeaders.put(requestHeaderNames[i], requestHeaderValues[i]); + } + AwContentsClient.AwWebResourceError error = new AwContentsClient.AwWebResourceError(); + error.errorCode = errorCode; + error.description = description; + onReceivedError(request, error); + } + + @CalledByNative protected void onReceivedHttpError( // WebResourceRequest String url, boolean isMainFrame, boolean hasUserGesture, String method, diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsStatics.java b/android_webview/java/src/org/chromium/android_webview/AwContentsStatics.java index dab4274..f38c7342 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContentsStatics.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContentsStatics.java @@ -64,6 +64,10 @@ public class AwContentsStatics { } public static String getUnreachableWebDataUrl() { + // Note that this method may be called from both IO and UI threads, + // but as it only retrieves a value of a constant from native, even if + // two calls will be running at the same time, this should not cause + // any harm. if (sUnreachableWebDataUrl == null) { sUnreachableWebDataUrl = nativeGetUnreachableWebDataUrl(); } diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java index a248d39..f985369 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java +++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java @@ -51,29 +51,9 @@ public class AwWebContentsObserver extends WebContentsObserver { String unreachableWebDataUrl = AwContentsStatics.getUnreachableWebDataUrl(); boolean isErrorUrl = unreachableWebDataUrl != null && unreachableWebDataUrl.equals(failingUrl); - if (isErrorUrl) return; - if (errorCode != NetError.ERR_ABORTED) { - // This error code is generated for the following reasons: - // - WebView.stopLoading is called, - // - the navigation is intercepted by the embedder via shouldOverrideNavigation. - // - // The Android WebView does not notify the embedder of these situations using - // this error code with the WebViewClient.onReceivedError callback. - AwContentsClient.AwWebResourceRequest request = - new AwContentsClient.AwWebResourceRequest(); - request.url = failingUrl; - request.isMainFrame = isMainFrame; - // TODO(mnaganov): Fill in the rest of AwWebResourceRequest fields. Probably, - // we will have to actually invoke the error callback from the network delegate - // in order to catch load errors for all resources. - AwContentsClient.AwWebResourceError error = new AwContentsClient.AwWebResourceError(); - error.errorCode = ErrorCodeConversionHelper.convertErrorCode(errorCode); - error.description = description; - mAwContentsClient.onReceivedError(request, error); - } - if (isMainFrame) { - // Need to call onPageFinished after onReceivedError (if there is an error) for - // backwards compatibility with the classic webview. + if (isMainFrame && !isErrorUrl && errorCode == NetError.ERR_ABORTED) { + // Need to call onPageFinished for backwards compatibility with the classic webview. + // See also AwContents.IoThreadClientImpl.onReceivedError. mAwContentsClient.onPageFinished(failingUrl); } } diff --git a/android_webview/java/src/org/chromium/android_webview/ErrorCodeConversionHelper.java b/android_webview/java/src/org/chromium/android_webview/ErrorCodeConversionHelper.java index a4d3888..7656d8e 100644 --- a/android_webview/java/src/org/chromium/android_webview/ErrorCodeConversionHelper.java +++ b/android_webview/java/src/org/chromium/android_webview/ErrorCodeConversionHelper.java @@ -42,6 +42,8 @@ public abstract class ErrorCodeConversionHelper { public static final int ERROR_FILE_NOT_FOUND = -14; // Too many requests during this load public static final int ERROR_TOO_MANY_REQUESTS = -15; + // Request blocked by the browser + public static final int ERROR_BLOCKED = -16; static int convertErrorCode(int netError) { // Note: many NetError.Error constants don't have an obvious mapping. diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java index eb3d7f7..db0e1e3 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java @@ -11,7 +11,6 @@ import org.chromium.android_webview.AwContentsStatics; import org.chromium.android_webview.AwWebContentsObserver; import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.MinAndroidSdkLevel; -import org.chromium.net.NetError; /** * Tests for the AwWebContentsObserver class. @@ -65,26 +64,6 @@ public class AwWebContentsObserverTest extends AwTestBase { assertEquals("onPageFinished should be called for main frame navigations.", callCount + 1, mContentsClient.getOnPageFinishedHelper().getCallCount()); - boolean provisionalLoad = true; - - callCount = mContentsClient.getOnPageFinishedHelper().getCallCount(); - mWebContentsObserver.didFailLoad(!provisionalLoad, mainFrame, - NetError.ERR_ABORTED, ERROR_DESCRIPTION, EXAMPLE_URL); - assertEquals("onPageFinished should be called for main frame errors.", callCount + 1, - mContentsClient.getOnPageFinishedHelper().getCallCount()); - - callCount = mContentsClient.getOnPageFinishedHelper().getCallCount(); - mWebContentsObserver.didFailLoad(!provisionalLoad, subFrame, - NetError.ERR_ABORTED, ERROR_DESCRIPTION, EXAMPLE_URL); - assertEquals("onPageFinished should only be called for main frame errors.", callCount, - mContentsClient.getOnPageFinishedHelper().getCallCount()); - - callCount = mContentsClient.getOnPageFinishedHelper().getCallCount(); - mWebContentsObserver.didFailLoad(!provisionalLoad, mainFrame, - NetError.ERR_ABORTED, ERROR_DESCRIPTION, mUnreachableWebDataUrl); - assertEquals("onPageFinished should not be called on unrechable url errors.", callCount, - mContentsClient.getOnPageFinishedHelper().getCallCount()); - String baseUrl = null; boolean navigationToDifferentPage = true; boolean fragmentNavigation = true; @@ -104,32 +83,6 @@ public class AwWebContentsObserverTest extends AwTestBase { @SmallTest @Feature({"AndroidWebView"}) - public void testOnReceivedError() { - boolean provisionalLoad = true; - boolean mainFrame = true; - boolean subFrame = false; - - int callCount = mContentsClient.getOnReceivedErrorHelper().getCallCount(); - mWebContentsObserver.didFailLoad(!provisionalLoad, subFrame, - NetError.ERR_TIMED_OUT, ERROR_DESCRIPTION, EXAMPLE_URL); - assertEquals("onReceivedError should only be called for the main frame", callCount, - mContentsClient.getOnReceivedErrorHelper().getCallCount()); - - callCount = mContentsClient.getOnReceivedErrorHelper().getCallCount(); - mWebContentsObserver.didFailLoad(!provisionalLoad, mainFrame, - NetError.ERR_TIMED_OUT, ERROR_DESCRIPTION, EXAMPLE_URL); - assertEquals("onReceivedError should be called for the main frame", callCount + 1, - mContentsClient.getOnReceivedErrorHelper().getCallCount()); - - callCount = mContentsClient.getOnReceivedErrorHelper().getCallCount(); - mWebContentsObserver.didFailLoad(!provisionalLoad, mainFrame, - NetError.ERR_ABORTED, ERROR_DESCRIPTION, EXAMPLE_URL); - assertEquals("onReceivedError should not be called for aborted navigations", callCount, - mContentsClient.getOnReceivedErrorHelper().getCallCount()); - } - - @SmallTest - @Feature({"AndroidWebView"}) public void testDidNavigateMainFrame() { String nullUrl = null; String baseUrl = null; diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java index d363c5c..1084c0f 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java @@ -359,4 +359,21 @@ public class ClientOnPageFinishedTest extends AwTestBase { assertEquals(syncUrl, onPageFinishedHelper.getUrl()); assertEquals(onPageFinishedCallCount + 1, onPageFinishedHelper.getCallCount()); } + + @MediumTest + @Feature({"AndroidWebView"}) + public void testOnPageFinishedCalledAfter204Reply() throws Throwable { + TestWebServer webServer = TestWebServer.start(); + try { + final String url = webServer.setResponseWithNoContentStatus("/page.html"); + TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = + mContentsClient.getOnPageFinishedHelper(); + int currentCallCount = onPageFinishedHelper.getCallCount(); + loadUrlAsync(mAwContents, url); + onPageFinishedHelper.waitForCallback(currentCallCount); + assertEquals(url, onPageFinishedHelper.getUrl()); + } finally { + webServer.shutdown(); + } + } } diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java new file mode 100644 index 0000000..d9bdc8e6 --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java @@ -0,0 +1,414 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.android_webview.test; + +import android.os.Build; +import android.test.suitebuilder.annotation.SmallTest; +import android.webkit.WebSettings; + +import org.chromium.android_webview.AwContents; +import org.chromium.android_webview.AwContentsClient.AwWebResourceError; +import org.chromium.android_webview.AwContentsClient.AwWebResourceRequest; +import org.chromium.android_webview.ErrorCodeConversionHelper; +import org.chromium.android_webview.test.util.AwTestTouchUtils; +import org.chromium.android_webview.test.util.CommonResources; +import org.chromium.base.test.util.Feature; +import org.chromium.base.test.util.MinAndroidSdkLevel; +import org.chromium.content.browser.test.util.TestCallbackHelperContainer; +import org.chromium.net.test.util.TestWebServer; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for the ContentViewClient.onReceivedError2() method. + */ +@MinAndroidSdkLevel(Build.VERSION_CODES.KITKAT) +public class ClientOnReceivedError2Test extends AwTestBase { + + private VerifyOnReceivedError2CallClient mContentsClient; + private AwTestContainerView mTestContainerView; + private AwContents mAwContents; + private TestWebServer mWebServer; + + private static final String BAD_HTML_URL = + "http://man.id.be.really.surprised.if.this.address.existed/a.html"; + + @Override + public void setUp() throws Exception { + super.setUp(); + mContentsClient = new VerifyOnReceivedError2CallClient(); + mTestContainerView = createAwTestContainerViewOnMainSync(mContentsClient); + mAwContents = mTestContainerView.getAwContents(); + } + + @Override + protected void tearDown() throws Exception { + if (mWebServer != null) mWebServer.shutdown(); + super.tearDown(); + } + + private void startWebServer() throws Exception { + mWebServer = TestWebServer.start(); + } + + private void useDefaultTestAwContentsClient() throws Exception { + mContentsClient.enableBypass(); + } + + private static class VerifyOnReceivedError2CallClient extends TestAwContentsClient { + private boolean mBypass = false; + private boolean mIsOnPageFinishedCalled = false; + private boolean mIsOnReceivedError2Called = false; + + void enableBypass() { + mBypass = true; + } + + @Override + public void onPageFinished(String url) { + if (!mBypass) { + assertEquals( + "onPageFinished called twice for " + url, false, mIsOnPageFinishedCalled); + mIsOnPageFinishedCalled = true; + assertEquals("onReceivedError2 not called before onPageFinished for " + url, true, + mIsOnReceivedError2Called); + } + super.onPageFinished(url); + } + + @Override + public void onReceivedError2(AwWebResourceRequest request, + AwWebResourceError error) { + if (!mBypass) { + assertEquals("onReceivedError2 called twice for " + request.url, false, + mIsOnReceivedError2Called); + mIsOnReceivedError2Called = true; + } + super.onReceivedError2(request, error); + } + } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testMainFrame() throws Throwable { + loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), BAD_HTML_URL); + + TestAwContentsClient.OnReceivedError2Helper onReceivedError2Helper = + mContentsClient.getOnReceivedError2Helper(); + AwWebResourceRequest request = onReceivedError2Helper.getRequest(); + assertNotNull(request); + assertEquals(BAD_HTML_URL, request.url); + assertEquals("GET", request.method); + assertNotNull(request.requestHeaders); + // No actual request has been made, as the host name can't be resolved. + assertTrue(request.requestHeaders.isEmpty()); + assertTrue(request.isMainFrame); + assertFalse(request.hasUserGesture); + AwWebResourceError error = onReceivedError2Helper.getError(); + // The particular error code that is returned depends on the configuration of the device + // (such as existence of a proxy) so we don't test for it. + assertFalse(ErrorCodeConversionHelper.ERROR_UNKNOWN == error.errorCode); + assertNotNull(error.description); + } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testUserGesture() throws Throwable { + useDefaultTestAwContentsClient(); + final String pageHtml = CommonResources.makeHtmlPageWithSimpleLinkTo(BAD_HTML_URL); + loadDataAsync(mAwContents, pageHtml, "text/html", false); + waitForVisualStateCallback(mAwContents); + + TestAwContentsClient.OnReceivedError2Helper onReceivedError2Helper = + mContentsClient.getOnReceivedError2Helper(); + int onReceivedError2CallCount = onReceivedError2Helper.getCallCount(); + AwTestTouchUtils.simulateTouchCenterOfView(mTestContainerView); + onReceivedError2Helper.waitForCallback(onReceivedError2CallCount, + 1 /* numberOfCallsToWaitFor */, + WAIT_TIMEOUT_MS, + TimeUnit.MILLISECONDS); + AwWebResourceRequest request = onReceivedError2Helper.getRequest(); + assertNotNull(request); + assertEquals(BAD_HTML_URL, request.url); + assertEquals("GET", request.method); + assertNotNull(request.requestHeaders); + // No actual request has been made, as the host name can't be resolved. + assertTrue(request.requestHeaders.isEmpty()); + assertTrue(request.isMainFrame); + assertTrue(request.hasUserGesture); + AwWebResourceError error = onReceivedError2Helper.getError(); + // The particular error code that is returned depends on the configuration of the device + // (such as existence of a proxy) so we don't test for it. + assertFalse(ErrorCodeConversionHelper.ERROR_UNKNOWN == error.errorCode); + assertNotNull(error.description); + } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testIframeSubresource() throws Throwable { + final String pageHtml = CommonResources.makeHtmlPageFrom( + "", "