// Copyright (c) 2011 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. // An implementation of WebURLLoader in terms of ResourceLoaderBridge. #include "webkit/glue/weburlloader_impl.h" #include "base/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/process_util.h" #include "base/string_util.h" #include "base/time.h" #include "net/base/data_url.h" #include "net/base/load_flags.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/http/http_response_headers.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebHTTPHeaderVisitor.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebHTTPLoadInfo.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoadTiming.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderClient.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" #include "webkit/glue/ftp_directory_listing_response_delegate.h" #include "webkit/glue/multipart_response_delegate.h" #include "webkit/glue/resource_loader_bridge.h" #include "webkit/glue/site_isolation_metrics.h" #include "webkit/glue/webkit_glue.h" using base::Time; using base::TimeDelta; using WebKit::WebData; using WebKit::WebHTTPBody; using WebKit::WebHTTPHeaderVisitor; using WebKit::WebHTTPLoadInfo; using WebKit::WebSecurityPolicy; using WebKit::WebString; using WebKit::WebURL; using WebKit::WebURLError; using WebKit::WebURLLoadTiming; using WebKit::WebURLLoader; using WebKit::WebURLLoaderClient; using WebKit::WebURLRequest; using WebKit::WebURLResponse; namespace webkit_glue { // Utilities ------------------------------------------------------------------ namespace { class HeaderFlattener : public WebHTTPHeaderVisitor { public: explicit HeaderFlattener(int load_flags) : load_flags_(load_flags), has_accept_header_(false) { } virtual void visitHeader(const WebString& name, const WebString& value) { // TODO(darin): is UTF-8 really correct here? It is if the strings are // already ASCII (i.e., if they are already escaped properly). const std::string& name_utf8 = name.utf8(); const std::string& value_utf8 = value.utf8(); // Skip over referrer headers found in the header map because we already // pulled it out as a separate parameter. if (LowerCaseEqualsASCII(name_utf8, "referer")) return; // Skip over "Cache-Control: max-age=0" header if the corresponding // load flag is already specified. FrameLoader sets both the flag and // the extra header -- the extra header is redundant since our network // implementation will add the necessary headers based on load flags. // See http://code.google.com/p/chromium/issues/detail?id=3434. if ((load_flags_ & net::LOAD_VALIDATE_CACHE) && LowerCaseEqualsASCII(name_utf8, "cache-control") && LowerCaseEqualsASCII(value_utf8, "max-age=0")) return; if (LowerCaseEqualsASCII(name_utf8, "accept")) has_accept_header_ = true; if (!buffer_.empty()) buffer_.append("\r\n"); buffer_.append(name_utf8 + ": " + value_utf8); } const std::string& GetBuffer() { // In some cases, WebKit doesn't add an Accept header, but not having the // header confuses some web servers. See bug 808613. if (!has_accept_header_) { if (!buffer_.empty()) buffer_.append("\r\n"); buffer_.append("Accept: */*"); has_accept_header_ = true; } return buffer_; } private: int load_flags_; std::string buffer_; bool has_accept_header_; }; ResourceType::Type FromTargetType(WebURLRequest::TargetType type) { switch (type) { case WebURLRequest::TargetIsMainFrame: return ResourceType::MAIN_FRAME; case WebURLRequest::TargetIsSubframe: return ResourceType::SUB_FRAME; case WebURLRequest::TargetIsSubresource: return ResourceType::SUB_RESOURCE; case WebURLRequest::TargetIsStyleSheet: return ResourceType::STYLESHEET; case WebURLRequest::TargetIsScript: return ResourceType::SCRIPT; case WebURLRequest::TargetIsFontResource: return ResourceType::FONT_RESOURCE; case WebURLRequest::TargetIsImage: return ResourceType::IMAGE; case WebURLRequest::TargetIsObject: return ResourceType::OBJECT; case WebURLRequest::TargetIsMedia: return ResourceType::MEDIA; case WebURLRequest::TargetIsWorker: return ResourceType::WORKER; case WebURLRequest::TargetIsSharedWorker: return ResourceType::SHARED_WORKER; case WebURLRequest::TargetIsPrefetch: return ResourceType::PREFETCH; case WebURLRequest::TargetIsFavicon: return ResourceType::FAVICON; default: NOTREACHED(); return ResourceType::SUB_RESOURCE; } } // Extracts the information from a data: url. bool GetInfoFromDataURL(const GURL& url, ResourceResponseInfo* info, std::string* data, net::URLRequestStatus* status) { std::string mime_type; std::string charset; if (net::DataURL::Parse(url, &mime_type, &charset, data)) { *status = net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0); info->request_time = Time::Now(); info->response_time = Time::Now(); info->headers = NULL; info->mime_type.swap(mime_type); info->charset.swap(charset); info->security_info.clear(); info->content_length = -1; info->encoded_data_length = 0; info->load_timing.base_time = Time::Now(); return true; } *status = net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_INVALID_URL); return false; } typedef ResourceDevToolsInfo::HeadersVector HeadersVector; void PopulateURLResponse( const GURL& url, const ResourceResponseInfo& info, WebURLResponse* response) { response->setURL(url); response->setResponseTime(info.response_time.ToDoubleT()); response->setMIMEType(WebString::fromUTF8(info.mime_type)); response->setTextEncodingName(WebString::fromUTF8(info.charset)); response->setExpectedContentLength(info.content_length); response->setSecurityInfo(info.security_info); response->setAppCacheID(info.appcache_id); response->setAppCacheManifestURL(info.appcache_manifest_url); response->setWasCached(!info.load_timing.base_time.is_null() && info.response_time < info.load_timing.base_time); response->setWasFetchedViaSPDY(info.was_fetched_via_spdy); response->setWasNpnNegotiated(info.was_npn_negotiated); response->setWasAlternateProtocolAvailable( info.was_alternate_protocol_available); response->setWasFetchedViaProxy(info.was_fetched_via_proxy); response->setRemoteIPAddress( WebString::fromUTF8(info.socket_address.host())); response->setRemotePort(info.socket_address.port()); response->setConnectionID(info.connection_id); response->setConnectionReused(info.connection_reused); response->setDownloadFilePath(FilePathToWebString(info.download_file_path)); const ResourceLoadTimingInfo& timing_info = info.load_timing; if (!timing_info.base_time.is_null()) { WebURLLoadTiming timing; timing.initialize(); timing.setRequestTime(timing_info.base_time.ToDoubleT()); timing.setProxyStart(timing_info.proxy_start); timing.setProxyEnd(timing_info.proxy_end); timing.setDNSStart(timing_info.dns_start); timing.setDNSEnd(timing_info.dns_end); timing.setConnectStart(timing_info.connect_start); timing.setConnectEnd(timing_info.connect_end); timing.setSSLStart(timing_info.ssl_start); timing.setSSLEnd(timing_info.ssl_end); timing.setSendStart(timing_info.send_start); timing.setSendEnd(timing_info.send_end); timing.setReceiveHeadersEnd(timing_info.receive_headers_end); response->setLoadTiming(timing); } if (info.devtools_info.get()) { WebHTTPLoadInfo load_info; load_info.setHTTPStatusCode(info.devtools_info->http_status_code); load_info.setHTTPStatusText(WebString::fromUTF8( info.devtools_info->http_status_text)); load_info.setEncodedDataLength(info.encoded_data_length); const HeadersVector& request_headers = info.devtools_info->request_headers; for (HeadersVector::const_iterator it = request_headers.begin(); it != request_headers.end(); ++it) { load_info.addRequestHeader(WebString::fromUTF8(it->first), WebString::fromUTF8(it->second)); } const HeadersVector& response_headers = info.devtools_info->response_headers; for (HeadersVector::const_iterator it = response_headers.begin(); it != response_headers.end(); ++it) { load_info.addResponseHeader(WebString::fromUTF8(it->first), WebString::fromUTF8(it->second)); } response->setHTTPLoadInfo(load_info); } const net::HttpResponseHeaders* headers = info.headers; if (!headers) return; response->setHTTPStatusCode(headers->response_code()); response->setHTTPStatusText(WebString::fromUTF8(headers->GetStatusText())); // TODO(darin): We should leverage HttpResponseHeaders for this, and this // should be using the same code as ResourceDispatcherHost. // TODO(jungshik): Figure out the actual value of the referrer charset and // pass it to GetSuggestedFilename. std::string value; if (headers->EnumerateHeader(NULL, "content-disposition", &value)) { response->setSuggestedFileName( net::GetSuggestedFilename(url, value, "", string16())); } Time time_val; if (headers->GetLastModifiedValue(&time_val)) response->setLastModifiedDate(time_val.ToDoubleT()); // Build up the header map. void* iter = NULL; std::string name; while (headers->EnumerateHeaderLines(&iter, &name, &value)) { response->addHTTPHeaderField(WebString::fromUTF8(name), WebString::fromUTF8(value)); } } } // namespace // WebURLLoaderImpl::Context -------------------------------------------------- // This inner class exists since the WebURLLoader may be deleted while inside a // call to WebURLLoaderClient. The bridge requires its Peer to stay alive // until it receives OnCompletedRequest. class WebURLLoaderImpl::Context : public base::RefCounted<Context>, public ResourceLoaderBridge::Peer { public: explicit Context(WebURLLoaderImpl* loader); WebURLLoaderClient* client() const { return client_; } void set_client(WebURLLoaderClient* client) { client_ = client; } void Cancel(); void SetDefersLoading(bool value); void Start( const WebURLRequest& request, ResourceLoaderBridge::SyncLoadResponse* sync_load_response); // ResourceLoaderBridge::Peer methods: virtual void OnUploadProgress(uint64 position, uint64 size); virtual bool OnReceivedRedirect( const GURL& new_url, const ResourceResponseInfo& info, bool* has_new_first_party_for_cookies, GURL* new_first_party_for_cookies); virtual void OnReceivedResponse(const ResourceResponseInfo& info); virtual void OnDownloadedData(int len); virtual void OnReceivedData(const char* data, int data_length, int encoded_data_length); virtual void OnReceivedCachedMetadata(const char* data, int len); virtual void OnCompletedRequest(const net::URLRequestStatus& status, const std::string& security_info, const base::Time& completion_time); private: friend class base::RefCounted<Context>; ~Context() {} // We can optimize the handling of data URLs in most cases. bool CanHandleDataURL(const GURL& url) const; void HandleDataURL(); WebURLLoaderImpl* loader_; WebURLRequest request_; WebURLLoaderClient* client_; scoped_ptr<ResourceLoaderBridge> bridge_; scoped_ptr<FtpDirectoryListingResponseDelegate> ftp_listing_delegate_; scoped_ptr<MultipartResponseDelegate> multipart_delegate_; scoped_ptr<ResourceLoaderBridge> completed_bridge_; // TODO(japhet): Storing this is a temporary hack for site isolation logging. WebURL response_url_; }; WebURLLoaderImpl::Context::Context(WebURLLoaderImpl* loader) : loader_(loader), client_(NULL) { } void WebURLLoaderImpl::Context::Cancel() { // The bridge will still send OnCompletedRequest, which will Release() us, so // we don't do that here. if (bridge_.get()) bridge_->Cancel(); // Ensure that we do not notify the multipart delegate anymore as it has // its own pointer to the client. if (multipart_delegate_.get()) multipart_delegate_->Cancel(); // Do not make any further calls to the client. client_ = NULL; loader_ = NULL; } void WebURLLoaderImpl::Context::SetDefersLoading(bool value) { if (bridge_.get()) bridge_->SetDefersLoading(value); } void WebURLLoaderImpl::Context::Start( const WebURLRequest& request, ResourceLoaderBridge::SyncLoadResponse* sync_load_response) { DCHECK(!bridge_.get()); request_ = request; // Save the request. GURL url = request.url(); if (url.SchemeIs("data") && CanHandleDataURL(url)) { if (sync_load_response) { // This is a sync load. Do the work now. sync_load_response->url = url; std::string data; GetInfoFromDataURL(sync_load_response->url, sync_load_response, &sync_load_response->data, &sync_load_response->status); } else { AddRef(); // Balanced in OnCompletedRequest MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, &Context::HandleDataURL)); } return; } GURL referrer_url( request.httpHeaderField(WebString::fromUTF8("Referer")).utf8()); const std::string& method = request.httpMethod().utf8(); int load_flags = net::LOAD_NORMAL; switch (request.cachePolicy()) { case WebURLRequest::ReloadIgnoringCacheData: // Required by LayoutTests/http/tests/misc/refresh-headers.php load_flags |= net::LOAD_VALIDATE_CACHE; break; case WebURLRequest::ReturnCacheDataElseLoad: load_flags |= net::LOAD_PREFERRING_CACHE; break; case WebURLRequest::ReturnCacheDataDontLoad: load_flags |= net::LOAD_ONLY_FROM_CACHE; break; case WebURLRequest::UseProtocolCachePolicy: break; } if (request.reportUploadProgress()) load_flags |= net::LOAD_ENABLE_UPLOAD_PROGRESS; if (request.reportLoadTiming()) load_flags |= net::LOAD_ENABLE_LOAD_TIMING; if (request.reportRawHeaders()) load_flags |= net::LOAD_REPORT_RAW_HEADERS; if (!request.allowCookies() || !request.allowStoredCredentials()) { load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; } if (!request.allowStoredCredentials()) load_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA; HeaderFlattener flattener(load_flags); request.visitHTTPHeaderFields(&flattener); // TODO(abarth): These are wrong! I need to figure out how to get the right // strings here. See: http://crbug.com/8706 std::string frame_origin = request.firstPartyForCookies().spec(); std::string main_frame_origin = request.firstPartyForCookies().spec(); // TODO(brettw) this should take parameter encoding into account when // creating the GURLs. ResourceLoaderBridge::RequestInfo request_info; request_info.method = method; request_info.url = url; request_info.first_party_for_cookies = request.firstPartyForCookies(); request_info.referrer = referrer_url; request_info.frame_origin = frame_origin; request_info.main_frame_origin = main_frame_origin; request_info.headers = flattener.GetBuffer(); request_info.load_flags = load_flags; // requestor_pid only needs to be non-zero if the request originates outside // the render process, so we can use requestorProcessID even for requests // from in-process plugins. request_info.requestor_pid = request.requestorProcessID(); request_info.request_type = FromTargetType(request.targetType()); request_info.appcache_host_id = request.appCacheHostID(); request_info.routing_id = request.requestorID(); request_info.download_to_file = request.downloadToFile(); request_info.has_user_gesture = request.hasUserGesture(); bridge_.reset(ResourceLoaderBridge::Create(request_info)); if (!request.httpBody().isNull()) { // GET and HEAD requests shouldn't have http bodies. DCHECK(method != "GET" && method != "HEAD"); const WebHTTPBody& httpBody = request.httpBody(); size_t i = 0; WebHTTPBody::Element element; while (httpBody.elementAt(i++, element)) { switch (element.type) { case WebHTTPBody::Element::TypeData: if (!element.data.isEmpty()) { // WebKit sometimes gives up empty data to append. These aren't // necessary so we just optimize those out here. bridge_->AppendDataToUpload( element.data.data(), static_cast<int>(element.data.size())); } break; case WebHTTPBody::Element::TypeFile: if (element.fileLength == -1) { bridge_->AppendFileToUpload( WebStringToFilePath(element.filePath)); } else { bridge_->AppendFileRangeToUpload( WebStringToFilePath(element.filePath), static_cast<uint64>(element.fileStart), static_cast<uint64>(element.fileLength), base::Time::FromDoubleT(element.modificationTime)); } break; case WebHTTPBody::Element::TypeBlob: bridge_->AppendBlobToUpload(GURL(element.blobURL)); break; default: NOTREACHED(); } } bridge_->SetUploadIdentifier(request.httpBody().identifier()); } if (sync_load_response) { bridge_->SyncLoad(sync_load_response); return; } if (bridge_->Start(this)) { AddRef(); // Balanced in OnCompletedRequest } else { bridge_.reset(); } } void WebURLLoaderImpl::Context::OnUploadProgress(uint64 position, uint64 size) { if (client_) client_->didSendData(loader_, position, size); } bool WebURLLoaderImpl::Context::OnReceivedRedirect( const GURL& new_url, const ResourceResponseInfo& info, bool* has_new_first_party_for_cookies, GURL* new_first_party_for_cookies) { if (!client_) return false; WebURLResponse response; response.initialize(); PopulateURLResponse(request_.url(), info, &response); // TODO(darin): We lack sufficient information to construct the actual // request that resulted from the redirect. WebURLRequest new_request(new_url); new_request.setFirstPartyForCookies(request_.firstPartyForCookies()); new_request.setDownloadToFile(request_.downloadToFile()); WebString referrer_string = WebString::fromUTF8("Referer"); WebString referrer = request_.httpHeaderField(referrer_string); if (!WebSecurityPolicy::shouldHideReferrer(new_url, referrer)) new_request.setHTTPHeaderField(referrer_string, referrer); if (response.httpStatusCode() == 307) new_request.setHTTPMethod(request_.httpMethod()); client_->willSendRequest(loader_, new_request, response); request_ = new_request; *has_new_first_party_for_cookies = true; *new_first_party_for_cookies = request_.firstPartyForCookies(); // Only follow the redirect if WebKit left the URL unmodified. if (new_url == GURL(new_request.url())) return true; // We assume that WebKit only changes the URL to suppress a redirect, and we // assume that it does so by setting it to be invalid. DCHECK(!new_request.url().isValid()); return false; } void WebURLLoaderImpl::Context::OnReceivedResponse( const ResourceResponseInfo& info) { if (!client_) return; WebURLResponse response; response.initialize(); PopulateURLResponse(request_.url(), info, &response); bool show_raw_listing = (GURL(request_.url()).query() == "raw"); if (info.mime_type == "text/vnd.chromium.ftp-dir") { if (show_raw_listing) { // Set the MIME type to plain text to prevent any active content. response.setMIMEType("text/plain"); } else { // We're going to produce a parsed listing in HTML. response.setMIMEType("text/html"); } } client_->didReceiveResponse(loader_, response); // We may have been cancelled after didReceiveResponse, which would leave us // without a client and therefore without much need to do further handling. if (!client_) return; DCHECK(!ftp_listing_delegate_.get()); DCHECK(!multipart_delegate_.get()); if (info.headers && info.mime_type == "multipart/x-mixed-replace") { std::string content_type; info.headers->EnumerateHeader(NULL, "content-type", &content_type); std::string boundary = net::GetHeaderParamValue( content_type, "boundary", net::QuoteRule::REMOVE_OUTER_QUOTES); TrimString(boundary, " \"", &boundary); // If there's no boundary, just handle the request normally. In the gecko // code, nsMultiMixedConv::OnStartRequest throws an exception. if (!boundary.empty()) { multipart_delegate_.reset( new MultipartResponseDelegate(client_, loader_, response, boundary)); } } else if (info.mime_type == "text/vnd.chromium.ftp-dir" && !show_raw_listing) { ftp_listing_delegate_.reset( new FtpDirectoryListingResponseDelegate(client_, loader_, response)); } response_url_ = response.url(); } void WebURLLoaderImpl::Context::OnDownloadedData(int len) { if (client_) client_->didDownloadData(loader_, len); } void WebURLLoaderImpl::Context::OnReceivedData(const char* data, int data_length, int encoded_data_length) { if (!client_) return; // Temporary logging, see site_isolation_metrics.h/cc. SiteIsolationMetrics::SniffCrossOriginHTML(response_url_, data, data_length); if (ftp_listing_delegate_.get()) { // The FTP listing delegate will make the appropriate calls to // client_->didReceiveData and client_->didReceiveResponse. ftp_listing_delegate_->OnReceivedData(data, data_length); } else if (multipart_delegate_.get()) { // The multipart delegate will make the appropriate calls to // client_->didReceiveData and client_->didReceiveResponse. multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length); } else { client_->didReceiveData(loader_, data, data_length, encoded_data_length); } } void WebURLLoaderImpl::Context::OnReceivedCachedMetadata( const char* data, int len) { if (client_) client_->didReceiveCachedMetadata(loader_, data, len); } void WebURLLoaderImpl::Context::OnCompletedRequest( const net::URLRequestStatus& status, const std::string& security_info, const base::Time& completion_time) { if (ftp_listing_delegate_.get()) { ftp_listing_delegate_->OnCompletedRequest(); ftp_listing_delegate_.reset(NULL); } else if (multipart_delegate_.get()) { multipart_delegate_->OnCompletedRequest(); multipart_delegate_.reset(NULL); } // Prevent any further IPC to the browser now that we're complete, but // don't delete it to keep any downloaded temp files alive. DCHECK(!completed_bridge_.get()); completed_bridge_.swap(bridge_); if (client_) { if (status.status() != net::URLRequestStatus::SUCCESS) { int error_code; if (status.status() == net::URLRequestStatus::HANDLED_EXTERNALLY) { // By marking this request as aborted we insure that we don't navigate // to an error page. error_code = net::ERR_ABORTED; } else { error_code = status.os_error(); } WebURLError error; error.domain = WebString::fromUTF8(net::kErrorDomain); error.reason = error_code; error.unreachableURL = request_.url(); client_->didFail(loader_, error); } else { client_->didFinishLoading(loader_, completion_time.ToDoubleT()); } } // Temporary logging, see site_isolation_metrics.h/cc SiteIsolationMetrics::RemoveCompletedResponse(response_url_); // We are done with the bridge now, and so we need to release the reference // to ourselves that we took on behalf of the bridge. This may cause our // destruction. Release(); } bool WebURLLoaderImpl::Context::CanHandleDataURL(const GURL& url) const { DCHECK(url.SchemeIs("data")); // Optimize for the case where we can handle a data URL locally. We must // skip this for data URLs targetted at frames since those could trigger a // download. // // NOTE: We special case MIME types we can render both for performance // reasons as well as to support unit tests, which do not have an underlying // ResourceLoaderBridge implementation. if (request_.targetType() != WebURLRequest::TargetIsMainFrame && request_.targetType() != WebURLRequest::TargetIsSubframe) return true; std::string mime_type, unused_charset; if (net::DataURL::Parse(url, &mime_type, &unused_charset, NULL) && net::IsSupportedMimeType(mime_type)) return true; return false; } void WebURLLoaderImpl::Context::HandleDataURL() { ResourceResponseInfo info; net::URLRequestStatus status; std::string data; if (GetInfoFromDataURL(request_.url(), &info, &data, &status)) { OnReceivedResponse(info); if (!data.empty()) OnReceivedData(data.data(), data.size(), 0); } OnCompletedRequest(status, info.security_info, base::Time::Now()); } // WebURLLoaderImpl ----------------------------------------------------------- WebURLLoaderImpl::WebURLLoaderImpl() : ALLOW_THIS_IN_INITIALIZER_LIST(context_(new Context(this))) { } WebURLLoaderImpl::~WebURLLoaderImpl() { cancel(); } void WebURLLoaderImpl::loadSynchronously(const WebURLRequest& request, WebURLResponse& response, WebURLError& error, WebData& data) { ResourceLoaderBridge::SyncLoadResponse sync_load_response; context_->Start(request, &sync_load_response); const GURL& final_url = sync_load_response.url; // TODO(tc): For file loads, we may want to include a more descriptive // status code or status text. const net::URLRequestStatus::Status& status = sync_load_response.status.status(); if (status != net::URLRequestStatus::SUCCESS && status != net::URLRequestStatus::HANDLED_EXTERNALLY) { response.setURL(final_url); error.domain = WebString::fromUTF8(net::kErrorDomain); error.reason = sync_load_response.status.os_error(); error.unreachableURL = final_url; return; } PopulateURLResponse(final_url, sync_load_response, &response); data.assign(sync_load_response.data.data(), sync_load_response.data.size()); } void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request, WebURLLoaderClient* client) { DCHECK(!context_->client()); context_->set_client(client); context_->Start(request, NULL); } void WebURLLoaderImpl::cancel() { context_->Cancel(); } void WebURLLoaderImpl::setDefersLoading(bool value) { context_->SetDefersLoading(value); } } // namespace webkit_glue