Commit 77e3a8b3 authored by Tim Volodine's avatar Tim Volodine Committed by Commit Bot

[Android WebView] Add InterceptedRequest class and OnReceivedHttpError callback.

Add InterceptedRequest class, which represents an in-progress network request
and allows to receive callbacks at certain points in the process and control
the flow.

This patch also adds code for the OnReceivedHttpError callback, which fixes
the following tests:
- org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testAfterRedirect
- org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testForMainResource
- org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testForSubresource
- org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testNotCalledIfNoHttpError

This patch in more detail:
- add IntercepedRequest internal class
- add code necessary for AwContentsClientBridge lookup
- detect http errors and code for the onReceivedHttpError callback
- update tests filter

BUG=891722

Cq-Include-Trybots: luci.chromium.try:linux_mojo
Change-Id: I2123a30b0c9ea8c3f07c34adc0fedb6bc69ca8f3
Reviewed-on: https://chromium-review.googlesource.com/c/1259019
Commit-Queue: Tim Volodine <timvolodine@chromium.org>
Reviewed-by: default avatarJohn Abd-El-Malek <jam@chromium.org>
Reviewed-by: default avatarClark DuVall <cduvall@chromium.org>
Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599250}
parent 247a10a5
......@@ -64,7 +64,6 @@ include_rules = [
# AwContentBrowserClient::GetDefaultFavicon
"!ui/resources/grit/ui_resources.h",
# QuotaStatusCode required by AwQuotaManagerBridge.
"+third_party/blink/public/mojom/quota",
# Interface required for in-process input event handling.
......
......@@ -769,11 +769,12 @@ bool AwContentBrowserClient::WillCreateURLLoaderFactory(
auto proxied_request = std::move(*factory_request);
network::mojom::URLLoaderFactoryPtrInfo target_factory_info;
*factory_request = mojo::MakeRequest(&target_factory_info);
int process_id = is_navigation ? 0 : frame->GetProcess()->GetID();
// Android WebView has one non off-the-record browser context.
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&AwProxyingURLLoaderFactory::CreateProxy,
base::BindOnce(&AwProxyingURLLoaderFactory::CreateProxy, process_id,
std::move(proxied_request), std::move(target_factory_info),
nullptr /* AwInterceptedRequestHandler */));
return true;
......
......@@ -6,19 +6,284 @@
#include <utility>
#include "android_webview/browser/aw_contents_client_bridge.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_utils.h"
#include "net/http/http_util.h"
namespace android_webview {
namespace {
// Handles intercepted, in-progress requests/responses, so that they can be
// controlled and modified accordingly.
class InterceptedRequest : public network::mojom::URLLoader,
public network::mojom::URLLoaderClient {
public:
InterceptedRequest(
int process_id,
uint64_t request_id,
int32_t routing_id,
uint32_t options,
const network::ResourceRequest& request,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
network::mojom::URLLoaderRequest loader_request,
network::mojom::URLLoaderClientPtr client,
network::mojom::URLLoaderFactoryPtr target_factory);
~InterceptedRequest() override;
void Restart();
// network::mojom::URLLoaderClient
void OnReceiveResponse(const network::ResourceResponseHead& head) override;
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& head) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override;
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
// network::mojom::URLLoader
void FollowRedirect(const base::Optional<std::vector<std::string>>&
to_be_removed_request_headers,
const base::Optional<net::HttpRequestHeaders>&
modified_request_headers) override;
void ProceedWithResponse() override;
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override;
void PauseReadingBodyFromNet() override;
void ResumeReadingBodyFromNet() override;
private:
void OnRequestError(const network::URLLoaderCompletionStatus& status);
const int process_id_;
const uint64_t request_id_;
const int32_t routing_id_;
const uint32_t options_;
network::ResourceRequest request_;
const net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
mojo::Binding<network::mojom::URLLoader> proxied_loader_binding_;
network::mojom::URLLoaderClientPtr target_client_;
mojo::Binding<network::mojom::URLLoaderClient> proxied_client_binding_;
network::mojom::URLLoaderPtr target_loader_;
network::mojom::URLLoaderFactoryPtr target_factory_;
base::WeakPtrFactory<InterceptedRequest> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(InterceptedRequest);
};
InterceptedRequest::InterceptedRequest(
int process_id,
uint64_t request_id,
int32_t routing_id,
uint32_t options,
const network::ResourceRequest& request,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
network::mojom::URLLoaderRequest loader_request,
network::mojom::URLLoaderClientPtr client,
network::mojom::URLLoaderFactoryPtr target_factory)
: process_id_(process_id),
request_id_(request_id),
routing_id_(routing_id),
options_(options),
request_(request),
traffic_annotation_(traffic_annotation),
proxied_loader_binding_(this, std::move(loader_request)),
target_client_(std::move(client)),
proxied_client_binding_(this),
target_factory_(std::move(target_factory)),
weak_factory_(this) {
// If there is a client error, clean up the request.
target_client_.set_connection_error_handler(base::BindOnce(
&InterceptedRequest::OnRequestError, weak_factory_.GetWeakPtr(),
network::URLLoaderCompletionStatus(net::ERR_ABORTED)));
}
InterceptedRequest::~InterceptedRequest() {}
void InterceptedRequest::Restart() {
// TODO(timvolodine): add async check shouldOverrideUrlLoading and
// shouldInterceptRequest.
if (!target_loader_ && target_factory_) {
network::mojom::URLLoaderClientPtr proxied_client;
proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client));
target_factory_->CreateLoaderAndStart(
mojo::MakeRequest(&target_loader_), routing_id_, request_id_, options_,
request_, std::move(proxied_client), traffic_annotation_);
}
}
namespace {
void OnReceivedHttpErrorOnUiThread(
int process_id,
int render_frame_id,
const AwWebResourceRequest& request,
std::unique_ptr<AwContentsClientBridge::HttpErrorInfo> http_error_info) {
// TODO(timvolodine): consider factoring out this functionality as it will
// be needed for other callbacks as well.
content::WebContents* wc =
process_id
? content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(process_id, render_frame_id))
: content::WebContents::FromFrameTreeNodeId(render_frame_id);
auto* client = AwContentsClientBridge::FromWebContents(wc);
if (!client) {
DLOG(WARNING) << "client is null, onReceivedHttpError dropped for "
<< request.url;
return;
}
client->OnReceivedHttpError(request, std::move(http_error_info));
}
} // namespace
// URLLoaderClient methods.
void InterceptedRequest::OnReceiveResponse(
const network::ResourceResponseHead& head) {
// intercept response headers here
// pause/resume proxied_client_binding_ if necessary
if (head.headers->response_code() >= 400) {
// In Android WebView the WebViewClient.onReceivedHttpError callback
// is invoked for any resource (main page, iframe, image, etc.) with
// status code >= 400.
std::unique_ptr<AwContentsClientBridge::HttpErrorInfo> error_info =
AwContentsClientBridge::ExtractHttpErrorInfo(head.headers.get());
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(
&OnReceivedHttpErrorOnUiThread, process_id_,
request_.render_frame_id,
AwWebResourceRequest(
request_.url.spec(), request_.method,
request_.resource_type == content::RESOURCE_TYPE_MAIN_FRAME,
request_.has_user_gesture, request_.headers),
std::move(error_info)));
}
target_client_->OnReceiveResponse(head);
}
void InterceptedRequest::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& head) {
// TODO(timvolodine): handle redirect override.
// TODO(timvolodine): handle unsafe redirect case.
target_client_->OnReceiveRedirect(redirect_info, head);
request_.url = redirect_info.new_url;
request_.method = redirect_info.new_method;
request_.site_for_cookies = redirect_info.new_site_for_cookies;
request_.referrer = GURL(redirect_info.new_referrer);
request_.referrer_policy = redirect_info.new_referrer_policy;
}
void InterceptedRequest::OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
target_client_->OnUploadProgress(current_position, total_size,
std::move(callback));
}
void InterceptedRequest::OnReceiveCachedMetadata(
const std::vector<uint8_t>& data) {
target_client_->OnReceiveCachedMetadata(data);
}
void InterceptedRequest::OnTransferSizeUpdated(int32_t transfer_size_diff) {
target_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void InterceptedRequest::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
target_client_->OnStartLoadingResponseBody(std::move(body));
}
void InterceptedRequest::OnComplete(
const network::URLLoaderCompletionStatus& status) {
target_client_->OnComplete(status);
// Deletes |this|.
// TODO(timvolodine): consider doing this via the factory.
delete this;
}
// URLLoader methods.
void InterceptedRequest::FollowRedirect(
const base::Optional<std::vector<std::string>>&
to_be_removed_request_headers,
const base::Optional<net::HttpRequestHeaders>& modified_request_headers) {
if (target_loader_) {
target_loader_->FollowRedirect(to_be_removed_request_headers,
modified_request_headers);
}
Restart();
}
void InterceptedRequest::ProceedWithResponse() {
if (target_loader_)
target_loader_->ProceedWithResponse();
}
void InterceptedRequest::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {
if (target_loader_)
target_loader_->SetPriority(priority, intra_priority_value);
}
void InterceptedRequest::PauseReadingBodyFromNet() {
if (target_loader_)
target_loader_->PauseReadingBodyFromNet();
}
void InterceptedRequest::ResumeReadingBodyFromNet() {
if (target_loader_)
target_loader_->ResumeReadingBodyFromNet();
}
void InterceptedRequest::OnRequestError(
const network::URLLoaderCompletionStatus& status) {
target_client_->OnComplete(status);
// Deletes |this|.
// TODO(timvolodine): consider handling this through a data structure.
delete this;
}
} // namespace
//============================
// AwProxyingURLLoaderFactory
//============================
AwProxyingURLLoaderFactory::AwProxyingURLLoaderFactory(
int process_id,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
std::unique_ptr<AwInterceptedRequestHandler> request_handler)
: request_handler_(std::move(request_handler)), weak_factory_(this) {
: process_id_(process_id),
request_handler_(std::move(request_handler)),
weak_factory_(this) {
// actual creation of the factory
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
target_factory_.Bind(std::move(target_factory_info));
......@@ -35,13 +300,14 @@ AwProxyingURLLoaderFactory::~AwProxyingURLLoaderFactory() {}
// static
void AwProxyingURLLoaderFactory::CreateProxy(
int process_id,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
std::unique_ptr<AwInterceptedRequestHandler> request_handler) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// will manage its own lifetime
new AwProxyingURLLoaderFactory(std::move(loader_request),
new AwProxyingURLLoaderFactory(process_id, std::move(loader_request),
std::move(target_factory_info),
std::move(request_handler));
}
......@@ -54,8 +320,9 @@ void AwProxyingURLLoaderFactory::CreateLoaderAndStart(
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
if (!request_handler_) {
// no interceptor --> pass through
bool pass_through = false;
if (pass_through) {
// this is the so-called pass-through, no-op option.
target_factory_->CreateLoaderAndStart(
std::move(loader), routing_id, request_id, options, request,
std::move(client), traffic_annotation);
......@@ -64,6 +331,16 @@ void AwProxyingURLLoaderFactory::CreateLoaderAndStart(
// TODO(timvolodine): handle interception, modification (headers for
// webview), blocking, callbacks etc..
network::mojom::URLLoaderFactoryPtr target_factory_clone;
target_factory_->Clone(MakeRequest(&target_factory_clone));
// manages its own lifecycle
// TODO(timvolodine): consider keeping track of requests.
InterceptedRequest* req = new InterceptedRequest(
process_id_, request_id, routing_id, options, request, traffic_annotation,
std::move(loader), std::move(client), std::move(target_factory_clone));
req->Restart();
}
void AwProxyingURLLoaderFactory::OnTargetFactoryError() {
......
......@@ -31,6 +31,7 @@ class AwInterceptedRequestHandler {};
class AwProxyingURLLoaderFactory : public network::mojom::URLLoaderFactory {
public:
AwProxyingURLLoaderFactory(
int process_id,
network::mojom::URLLoaderFactoryRequest loader_request,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
std::unique_ptr<AwInterceptedRequestHandler> request_handler);
......@@ -39,6 +40,7 @@ class AwProxyingURLLoaderFactory : public network::mojom::URLLoaderFactory {
// static
static void CreateProxy(
int process_id,
network::mojom::URLLoaderFactoryRequest loader,
network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
std::unique_ptr<AwInterceptedRequestHandler> request_handler);
......@@ -58,6 +60,7 @@ class AwProxyingURLLoaderFactory : public network::mojom::URLLoaderFactory {
void OnTargetFactoryError();
void OnProxyBindingError();
const int process_id_;
mojo::BindingSet<network::mojom::URLLoaderFactory> proxy_bindings_;
network::mojom::URLLoaderFactoryPtr target_factory_;
......
......@@ -120,11 +120,7 @@
-org.chromium.android_webview.test.ClientOnReceivedErrorTest.testOnReceivedErrorOnInvalidUrl
# https://crbug.com/891722
-org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testAfterRedirect
-org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testForMainResource
-org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testForSubresource
-org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testForUserGesture
-org.chromium.android_webview.test.ClientOnReceivedHttpErrorTest.testNotCalledIfNoHttpError
# https://crbug.com/893575
-org.chromium.android_webview.test.CookieManagerStartupTest.testShouldInterceptRequestDeadlock
......
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