Commit 8a4a31d0 authored by mnaganov's avatar mnaganov Committed by Commit bot

[Android WebView] Implement OnReceivedHttpError callback

Implement OnReceivedHttpError via NetworkDelegate::OnHeadersReceived
for status codes >= 400.

BUG=456782

Review URL: https://codereview.chromium.org/976393002

Cr-Commit-Position: refs/heads/master@{#319856}
parent b6a70087
......@@ -12,6 +12,7 @@
class GURL;
namespace net {
class HttpResponseHeaders;
class URLRequest;
}
......@@ -102,6 +103,11 @@ class AwContentsIoThreadClient {
virtual void NewLoginRequest(const std::string& realm,
const std::string& account,
const std::string& args) = 0;
// Called when a response from the server is received with status code >= 400.
virtual void OnReceivedHttpError(
const net::URLRequest* request,
const net::HttpResponseHeaders* response_headers) = 0;
};
} // namespace android_webview
......
......@@ -4,14 +4,20 @@
#include "android_webview/browser/net/aw_network_delegate.h"
#include "android_webview/browser/aw_contents_io_thread_client.h"
#include "android_webview/browser/aw_cookie_access_policy.h"
#include "base/android/build_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h"
#include "net/base/net_errors.h"
#include "net/base/completion_callback.h"
#include "net/http/http_response_headers.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_server.h"
#include "net/url_request/url_request.h"
using content::BrowserThread;
namespace android_webview {
AwNetworkDelegate::AwNetworkDelegate() {
......@@ -49,6 +55,17 @@ int AwNetworkDelegate::OnHeadersReceived(
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
GURL* allowed_unsafe_redirect_url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
int render_process_id, render_frame_id;
if (original_response_headers->response_code() >= 400 &&
content::ResourceRequestInfo::GetRenderFrameForRequest(
request, &render_process_id, &render_frame_id)) {
scoped_ptr<AwContentsIoThreadClient> io_thread_client =
AwContentsIoThreadClient::FromID(render_process_id, render_frame_id);
if (io_thread_client.get()) {
io_thread_client->OnReceivedHttpError(request, original_response_headers);
}
}
return net::OK;
}
......
......@@ -574,6 +574,17 @@ public class WebViewContentsClientAdapter extends AwContentsClient {
}
}
@Override
public void onReceivedHttpError(AwWebResourceRequest request, AwWebResourceResponse response) {
try {
TraceEvent.begin("WebViewContentsClientAdapter.onReceivedHttpError");
if (TRACE) Log.d(TAG, "onReceivedHttpError=" + request.url);
// TODO(mnaganov): Call mWebViewClient.onReceivedHttpError(mWebView, request, response);
} finally {
TraceEvent.end("WebViewContentsClientAdapter.onReceivedHttpError");
}
}
/**
* @see ContentViewClient#onReceivedTitle(String)
*/
......
......@@ -416,6 +416,12 @@ public class AwContents implements SmartClipProvider,
public void newLoginRequest(String realm, String account, String args) {
mContentsClient.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
}
@Override
public void onReceivedHttpError(AwContentsClient.AwWebResourceRequest request,
AwWebResourceResponse response) {
mContentsClient.getCallbackHelper().postOnReceivedHttpError(request, response);
}
}
//--------------------------------------------------------------------------------------------
......
......@@ -297,6 +297,9 @@ public abstract class AwContentsClient {
protected abstract void onReceivedError2(
AwWebResourceRequest request, AwWebResourceError error);
public abstract void onReceivedHttpError(AwWebResourceRequest request,
AwWebResourceResponse response);
// TODO (michaelbai): Remove this method once the same method remove from
// WebViewContentsClientAdapter.
public void onShowCustomView(View view, int requestedOrientation,
......
......@@ -69,6 +69,17 @@ public class AwContentsClientCallbackHelper {
}
}
private static class OnReceivedHttpErrorInfo {
final AwContentsClient.AwWebResourceRequest mRequest;
final AwWebResourceResponse mResponse;
OnReceivedHttpErrorInfo(
AwContentsClient.AwWebResourceRequest request, AwWebResourceResponse response) {
mRequest = request;
mResponse = response;
}
}
private static final int MSG_ON_LOAD_RESOURCE = 1;
private static final int MSG_ON_PAGE_STARTED = 2;
private static final int MSG_ON_DOWNLOAD_START = 3;
......@@ -76,6 +87,7 @@ public class AwContentsClientCallbackHelper {
private static final int MSG_ON_RECEIVED_ERROR = 5;
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;
// Minimum period allowed between consecutive onNewPicture calls, to rate-limit the callbacks.
private static final long ON_NEW_PICTURE_MIN_PERIOD_MILLIS = 500;
......@@ -140,6 +152,11 @@ public class AwContentsClientCallbackHelper {
mContentsClient.onScaleChangedScaled(oldScale, newScale);
break;
}
case MSG_ON_RECEIVED_HTTP_ERROR: {
OnReceivedHttpErrorInfo info = (OnReceivedHttpErrorInfo) msg.obj;
mContentsClient.onReceivedHttpError(info.mRequest, info.mResponse);
break;
}
default:
throw new IllegalStateException(
"AwContentsClientCallbackHelper: unhandled message " + msg.what);
......@@ -195,4 +212,11 @@ public class AwContentsClientCallbackHelper {
mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_SCALE_CHANGED_SCALED,
Float.floatToIntBits(oldScale), Float.floatToIntBits(newScale)));
}
public void postOnReceivedHttpError(AwContentsClient.AwWebResourceRequest request,
AwWebResourceResponse response) {
OnReceivedHttpErrorInfo info =
new OnReceivedHttpErrorInfo(request, response);
mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_HTTP_ERROR, info));
}
}
......@@ -8,6 +8,7 @@ import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import java.util.HashMap;
import java.util.Map;
/**
* Delegate for handling callbacks. All methods are called on the IO thread.
......@@ -42,6 +43,9 @@ public abstract class AwContentsIoThreadClient {
public abstract AwWebResourceResponse shouldInterceptRequest(
AwContentsClient.AwWebResourceRequest request);
public abstract void onReceivedHttpError(AwContentsClient.AwWebResourceRequest request,
AwWebResourceResponse response);
// Protected methods ---------------------------------------------------------------------------
@CalledByNative
......@@ -60,4 +64,42 @@ public abstract class AwContentsIoThreadClient {
}
return shouldInterceptRequest(request);
}
@CalledByNative
protected void onReceivedHttpError(
// WebResourceRequest
String url, boolean isMainFrame, boolean hasUserGesture, String method,
String[] requestHeaderNames, String[] requestHeaderValues,
// WebResourceResponse
String mimeType, String encoding, int statusCode, String reasonPhrase,
String[] responseHeaderNames, String[] responseHeaderValues) {
AwContentsClient.AwWebResourceRequest request =
new AwContentsClient.AwWebResourceRequest();
request.url = url;
request.isMainFrame = isMainFrame;
request.hasUserGesture = hasUserGesture;
request.method = method;
request.requestHeaders = new HashMap<String, String>(requestHeaderNames.length);
for (int i = 0; i < requestHeaderNames.length; ++i) {
request.requestHeaders.put(requestHeaderNames[i], requestHeaderValues[i]);
}
Map<String, String> responseHeaders =
new HashMap<String, String>(responseHeaderNames.length);
// Note that we receive un-coalesced response header lines, thus we need to combine
// values for the same header.
for (int i = 0; i < responseHeaderNames.length; ++i) {
if (!responseHeaders.containsKey(responseHeaderNames[i])) {
responseHeaders.put(responseHeaderNames[i], responseHeaderValues[i]);
} else if (!responseHeaderValues[i].isEmpty()) {
String currentValue = responseHeaders.get(responseHeaderNames[i]);
if (!currentValue.isEmpty()) {
currentValue += ", ";
}
responseHeaders.put(responseHeaderNames[i], currentValue + responseHeaderValues[i]);
}
}
AwWebResourceResponse response = new AwWebResourceResponse(
mimeType, encoding, null, statusCode, reasonPhrase, responseHeaders);
onReceivedHttpError(request, response);
}
}
......@@ -6,7 +6,6 @@ package org.chromium.android_webview;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.base.VisibleForTesting;
import java.io.InputStream;
import java.util.Map;
......@@ -21,6 +20,7 @@ public class AwWebResourceResponse {
private InputStream mData;
private int mStatusCode;
private String mReasonPhrase;
private Map<String, String> mResponseHeaders;
private String[] mResponseHeaderNames;
private String[] mResponseHeaderValues;
......@@ -36,53 +36,59 @@ public class AwWebResourceResponse {
mStatusCode = statusCode;
mReasonPhrase = reasonPhrase;
mResponseHeaders = responseHeaders;
}
if (responseHeaders != null) {
mResponseHeaderNames = new String[responseHeaders.size()];
mResponseHeaderValues = new String[responseHeaders.size()];
private void fillInResponseHeaderNamesAndValuesIfNeeded() {
if (mResponseHeaders == null || mResponseHeaderNames != null) return;
mResponseHeaderNames = new String[mResponseHeaders.size()];
mResponseHeaderValues = new String[mResponseHeaders.size()];
int i = 0;
for (Map.Entry<String, String> entry : responseHeaders.entrySet()) {
for (Map.Entry<String, String> entry : mResponseHeaders.entrySet()) {
mResponseHeaderNames[i] = entry.getKey();
mResponseHeaderValues[i] = entry.getValue();
i++;
}
}
}
@VisibleForTesting
@CalledByNative
public String getMimeType() {
return mMimeType;
}
@CalledByNative
private String getCharset() {
public String getCharset() {
return mCharset;
}
@VisibleForTesting
@CalledByNative
public InputStream getData() {
return mData;
}
@CalledByNative
private int getStatusCode() {
public int getStatusCode() {
return mStatusCode;
}
@CalledByNative
private String getReasonPhrase() {
public String getReasonPhrase() {
return mReasonPhrase;
}
public Map<String, String> getResponseHeaders() {
return mResponseHeaders;
}
@CalledByNative
private String[] getResponseHeaderNames() {
fillInResponseHeaderNamesAndValuesIfNeeded();
return mResponseHeaderNames;
}
@CalledByNative
private String[] getResponseHeaderValues() {
fillInResponseHeaderNamesAndValuesIfNeeded();
return mResponseHeaderValues;
}
}
......@@ -273,6 +273,26 @@ public class AwTestBase
TimeUnit.MILLISECONDS);
}
public void waitForVisualStateCallback(final AwContents awContents) throws Exception {
final CallbackHelper ch = new CallbackHelper();
final int chCount = ch.getCallCount();
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
final long requestId = 666;
awContents.insertVisualStateCallback(requestId,
new AwContents.VisualStateCallback() {
@Override
public void onComplete(long id) {
assertEquals(requestId, id);
ch.notifyCalled();
}
});
}
});
ch.waitForCallback(chCount);
}
/**
* Checks the current test has |clazz| annotation. Note this swallows NoSuchMethodException
* and returns false in that case.
......
......@@ -9,6 +9,8 @@ import android.net.http.SslError;
import android.webkit.ConsoleMessage;
import android.webkit.ValueCallback;
import org.chromium.android_webview.AwContentsClient.AwWebResourceRequest;
import org.chromium.android_webview.AwWebResourceResponse;
import org.chromium.base.ThreadUtils;
import org.chromium.content.browser.test.util.CallbackHelper;
import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
......@@ -25,6 +27,7 @@ public class TestAwContentsClient extends NullContentsClient {
private final OnPageStartedHelper mOnPageStartedHelper;
private final OnPageFinishedHelper mOnPageFinishedHelper;
private final OnReceivedErrorHelper mOnReceivedErrorHelper;
private final OnReceivedHttpErrorHelper mOnReceivedHttpErrorHelper;
private final CallbackHelper mOnReceivedSslErrorHelper;
private final OnDownloadStartHelper mOnDownloadStartHelper;
private final OnReceivedLoginRequestHelper mOnReceivedLoginRequestHelper;
......@@ -41,6 +44,7 @@ public class TestAwContentsClient extends NullContentsClient {
mOnPageStartedHelper = new OnPageStartedHelper();
mOnPageFinishedHelper = new OnPageFinishedHelper();
mOnReceivedErrorHelper = new OnReceivedErrorHelper();
mOnReceivedHttpErrorHelper = new OnReceivedHttpErrorHelper();
mOnReceivedSslErrorHelper = new CallbackHelper();
mOnDownloadStartHelper = new OnDownloadStartHelper();
mOnReceivedLoginRequestHelper = new OnReceivedLoginRequestHelper();
......@@ -66,6 +70,10 @@ public class TestAwContentsClient extends NullContentsClient {
return mOnReceivedErrorHelper;
}
public OnReceivedHttpErrorHelper getOnReceivedHttpErrorHelper() {
return mOnReceivedHttpErrorHelper;
}
public CallbackHelper getOnReceivedSslErrorHelper() {
return mOnReceivedSslErrorHelper;
}
......@@ -439,4 +447,32 @@ public class TestAwContentsClient extends NullContentsClient {
public void doUpdateVisitedHistory(String url, boolean isReload) {
getDoUpdateVisitedHistoryHelper().notifyCalled(url, isReload);
}
/**
* CallbackHelper for OnReceivedHttpError.
*/
public static class OnReceivedHttpErrorHelper extends CallbackHelper {
private AwWebResourceRequest mRequest;
private AwWebResourceResponse mResponse;
public void notifyCalled(AwWebResourceRequest request, AwWebResourceResponse response) {
mRequest = request;
mResponse = response;
notifyCalled();
}
public AwWebResourceRequest getRequest() {
assert getCallCount() > 0;
return mRequest;
}
public AwWebResourceResponse getResponse() {
assert getCallCount() > 0;
return mResponse;
}
}
@Override
public void onReceivedHttpError(AwWebResourceRequest request, AwWebResourceResponse response) {
super.onReceivedHttpError(request, response);
mOnReceivedHttpErrorHelper.notifyCalled(request, response);
}
}
......@@ -25,6 +25,7 @@
#include "content/public/browser/web_contents_observer.h"
#include "jni/AwContentsIoThreadClient_jni.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "url/gurl.h"
......@@ -375,6 +376,85 @@ void AwContentsIoThreadClientImpl::NewLoginRequest(const string& realm,
env, java_object_.obj(), jrealm.obj(), jaccount.obj(), jargs.obj());
}
void AwContentsIoThreadClientImpl::OnReceivedHttpError(
const net::URLRequest* request,
const net::HttpResponseHeaders* response_headers) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (java_object_.is_null())
return;
vector<string> request_header_names;
vector<string> request_header_values;
{
net::HttpRequestHeaders headers;
if (!request->GetFullRequestHeaders(&headers))
headers = request->extra_request_headers();
net::HttpRequestHeaders::Iterator headers_iterator(headers);
while (headers_iterator.GetNext()) {
request_header_names.push_back(headers_iterator.name());
request_header_values.push_back(headers_iterator.value());
}
}
vector<string> response_header_names;
vector<string> response_header_values;
{
void* headers_iterator = NULL;
string header_name, header_value;
while (response_headers->EnumerateHeaderLines(
&headers_iterator, &header_name, &header_value)) {
response_header_names.push_back(header_name);
response_header_values.push_back(header_value);
}
}
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> jstring_url =
ConvertUTF8ToJavaString(env, request->url().spec());
ScopedJavaLocalRef<jstring> jstring_method =
ConvertUTF8ToJavaString(env, request->method());
ScopedJavaLocalRef<jobjectArray> jstringArray_request_header_names =
ToJavaArrayOfStrings(env, request_header_names);
ScopedJavaLocalRef<jobjectArray> jstringArray_request_header_values =
ToJavaArrayOfStrings(env, request_header_values);
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request);
bool is_main_frame = info &&
info->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME;
bool has_user_gesture = info && info->HasUserGesture();
string mime_type, encoding;
response_headers->GetMimeTypeAndCharset(&mime_type, &encoding);
ScopedJavaLocalRef<jstring> jstring_mime_type =
ConvertUTF8ToJavaString(env, mime_type);
ScopedJavaLocalRef<jstring> jstring_encoding =
ConvertUTF8ToJavaString(env, encoding);
int status_code = response_headers->response_code();
ScopedJavaLocalRef<jstring> jstring_reason =
ConvertUTF8ToJavaString(env, response_headers->GetStatusText());
ScopedJavaLocalRef<jobjectArray> jstringArray_response_header_names =
ToJavaArrayOfStrings(env, response_header_names);
ScopedJavaLocalRef<jobjectArray> jstringArray_response_header_values =
ToJavaArrayOfStrings(env, response_header_values);
Java_AwContentsIoThreadClient_onReceivedHttpError(
env,
java_object_.obj(),
jstring_url.obj(),
is_main_frame,
has_user_gesture,
jstring_method.obj(),
jstringArray_request_header_names.obj(),
jstringArray_request_header_values.obj(),
jstring_mime_type.obj(),
jstring_encoding.obj(),
status_code,
jstring_reason.obj(),
jstringArray_response_header_names.obj(),
jstringArray_response_header_values.obj());
}
bool RegisterAwContentsIoThreadClientImpl(JNIEnv* env) {
return RegisterNativesImpl(env);
}
......
......@@ -62,6 +62,9 @@ class AwContentsIoThreadClientImpl : public AwContentsIoThreadClient {
void NewLoginRequest(const std::string& realm,
const std::string& account,
const std::string& args) override;
void OnReceivedHttpError(
const net::URLRequest* request,
const net::HttpResponseHeaders* response_headers) override;
private:
bool pending_association_;
......
......@@ -171,6 +171,10 @@ public class NullContentsClient extends AwContentsClient {
public void onReceivedError2(AwWebResourceRequest request, AwWebResourceError error) {
}
@Override
public void onReceivedHttpError(AwWebResourceRequest request, AwWebResourceResponse response) {
}
@Override
public void onFormResubmission(Message dontResend, Message resend) {
dontResend.sendToTarget();
......
......@@ -252,8 +252,22 @@ public class TestWebServer {
*/
public String setResponseWithNotFoundStatus(
String requestPath) {
return setResponseInternal(requestPath, "".getBytes(), null, null,
RESPONSE_STATUS_NOT_FOUND);
return setResponseWithNotFoundStatus(requestPath, null);
}
/**
* Sets a 404 (not found) response to be returned when a particular request path is passed in.
*
* @param requestPath The path to respond to.
* @param responseHeaders Any additional headers that should be returned along with the
* response (null is acceptable).
* @return The full URL including the path that should be requested to get the expected
* response.
*/
public String setResponseWithNotFoundStatus(
String requestPath, List<Pair<String, String>> responseHeaders) {
return setResponseInternal(
requestPath, "".getBytes(), responseHeaders, null, RESPONSE_STATUS_NOT_FOUND);
}
/**
......@@ -468,6 +482,9 @@ public class TestWebServer {
httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
} else if (response.mIsNotFound) {
httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
for (Pair<String, String> header : response.mResponseHeaders) {
httpResponse.addHeader(header.first, header.second);
}
servedResponseFor(path, request);
} else if (response.mIsNoContent) {
httpResponse = createResponse(HttpStatus.SC_NO_CONTENT);
......@@ -531,6 +548,7 @@ public class TestWebServer {
ByteArrayEntity entity = createEntity(buf.toString().getBytes());
response.setEntity(entity);
response.setHeader("Content-Length", "" + entity.getContentLength());
response.setReasonPhrase(reason);
}
return response;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment