cc: Make picture pile base thread safe.
[chromium-blink-merge.git] / content / browser / service_worker / service_worker_url_request_job.cc
bloba041426213c33d26ee4ef0b1e841fa9517cfe23a
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/service_worker/service_worker_url_request_job.h"
7 #include <map>
8 #include <string>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/guid.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/time/time.h"
15 #include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
16 #include "content/browser/service_worker/service_worker_provider_host.h"
17 #include "content/common/resource_request_body.h"
18 #include "content/common/service_worker/service_worker_types.h"
19 #include "content/public/browser/blob_handle.h"
20 #include "content/public/browser/resource_request_info.h"
21 #include "net/base/net_errors.h"
22 #include "net/http/http_request_headers.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/http/http_response_info.h"
25 #include "net/http/http_util.h"
26 #include "storage/browser/blob/blob_data_handle.h"
27 #include "storage/browser/blob/blob_storage_context.h"
28 #include "storage/browser/blob/blob_url_request_job_factory.h"
29 #include "ui/base/page_transition_types.h"
31 namespace content {
33 namespace {
35 // Keep in sync with kDevToolsRequestInitiator and
36 // kDevToolsEmulateNetworkConditionsClientId defined in
37 // devtools_network_transaction.cc and InspectorResourceAgent.cpp.
38 const char kDevToolsRequestInitiator[] = "X-DevTools-Request-Initiator";
39 const char kDevToolsEmulateNetworkConditionsClientId[] =
40 "X-DevTools-Emulate-Network-Conditions-Client-Id";
42 } // namespace
44 ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
45 net::URLRequest* request,
46 net::NetworkDelegate* network_delegate,
47 base::WeakPtr<ServiceWorkerProviderHost> provider_host,
48 base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
49 FetchRequestMode request_mode,
50 FetchCredentialsMode credentials_mode,
51 RequestContextType request_context_type,
52 RequestContextFrameType frame_type,
53 scoped_refptr<ResourceRequestBody> body)
54 : net::URLRequestJob(request, network_delegate),
55 provider_host_(provider_host),
56 response_type_(NOT_DETERMINED),
57 is_started_(false),
58 service_worker_response_type_(blink::WebServiceWorkerResponseTypeDefault),
59 blob_storage_context_(blob_storage_context),
60 request_mode_(request_mode),
61 credentials_mode_(credentials_mode),
62 request_context_type_(request_context_type),
63 frame_type_(frame_type),
64 fall_back_required_(false),
65 body_(body),
66 weak_factory_(this) {
69 void ServiceWorkerURLRequestJob::FallbackToNetwork() {
70 DCHECK_EQ(NOT_DETERMINED, response_type_);
71 response_type_ = FALLBACK_TO_NETWORK;
72 MaybeStartRequest();
75 void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
76 DCHECK_EQ(NOT_DETERMINED, response_type_);
77 response_type_ = FORWARD_TO_SERVICE_WORKER;
78 MaybeStartRequest();
81 void ServiceWorkerURLRequestJob::Start() {
82 is_started_ = true;
83 MaybeStartRequest();
86 void ServiceWorkerURLRequestJob::Kill() {
87 net::URLRequestJob::Kill();
88 fetch_dispatcher_.reset();
89 blob_request_.reset();
90 weak_factory_.InvalidateWeakPtrs();
93 net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const {
94 // TODO(kinuko): refine this for better debug.
95 return net::URLRequestJob::GetLoadState();
98 bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) {
99 if (!http_info())
100 return false;
101 return http_info()->headers->GetCharset(charset);
104 bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const {
105 if (!http_info())
106 return false;
107 return http_info()->headers->GetMimeType(mime_type);
110 void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
111 if (!http_info())
112 return;
113 *info = *http_info();
114 info->response_time = response_time_;
117 void ServiceWorkerURLRequestJob::GetLoadTimingInfo(
118 net::LoadTimingInfo* load_timing_info) const {
119 *load_timing_info = load_timing_info_;
122 int ServiceWorkerURLRequestJob::GetResponseCode() const {
123 if (!http_info())
124 return -1;
125 return http_info()->headers->response_code();
128 void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
129 const net::HttpRequestHeaders& headers) {
130 std::string range_header;
131 std::vector<net::HttpByteRange> ranges;
132 if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) ||
133 !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
134 return;
137 // We don't support multiple range requests in one single URL request.
138 if (ranges.size() == 1U)
139 byte_range_ = ranges[0];
142 bool ServiceWorkerURLRequestJob::ReadRawData(
143 net::IOBuffer* buf, int buf_size, int *bytes_read) {
144 if (!blob_request_) {
145 *bytes_read = 0;
146 return true;
149 blob_request_->Read(buf, buf_size, bytes_read);
150 net::URLRequestStatus status = blob_request_->status();
151 SetStatus(status);
152 if (status.is_io_pending())
153 return false;
154 return status.is_success();
157 void ServiceWorkerURLRequestJob::OnReceivedRedirect(
158 net::URLRequest* request,
159 const net::RedirectInfo& redirect_info,
160 bool* defer_redirect) {
161 NOTREACHED();
164 void ServiceWorkerURLRequestJob::OnAuthRequired(
165 net::URLRequest* request,
166 net::AuthChallengeInfo* auth_info) {
167 NOTREACHED();
170 void ServiceWorkerURLRequestJob::OnCertificateRequested(
171 net::URLRequest* request,
172 net::SSLCertRequestInfo* cert_request_info) {
173 NOTREACHED();
176 void ServiceWorkerURLRequestJob::OnSSLCertificateError(
177 net::URLRequest* request,
178 const net::SSLInfo& ssl_info,
179 bool fatal) {
180 NOTREACHED();
183 void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request,
184 bool* defer) {
185 NOTREACHED();
188 void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) {
189 // TODO(falken): Add Content-Length, Content-Type if they were not provided in
190 // the ServiceWorkerResponse.
191 response_time_ = base::Time::Now();
192 CommitResponseHeader();
195 void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request,
196 int bytes_read) {
197 SetStatus(request->status());
198 if (!request->status().is_success()) {
199 NotifyDone(request->status());
200 return;
202 NotifyReadComplete(bytes_read);
203 if (bytes_read == 0)
204 NotifyDone(request->status());
207 const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
208 if (!http_response_info_)
209 return NULL;
210 if (range_response_info_)
211 return range_response_info_.get();
212 return http_response_info_.get();
215 void ServiceWorkerURLRequestJob::GetExtraResponseInfo(
216 bool* was_fetched_via_service_worker,
217 bool* was_fallback_required_by_service_worker,
218 GURL* original_url_via_service_worker,
219 blink::WebServiceWorkerResponseType* response_type_via_service_worker,
220 base::TimeTicks* fetch_start_time,
221 base::TimeTicks* fetch_ready_time,
222 base::TimeTicks* fetch_end_time) const {
223 if (response_type_ != FORWARD_TO_SERVICE_WORKER) {
224 *was_fetched_via_service_worker = false;
225 *was_fallback_required_by_service_worker = false;
226 *original_url_via_service_worker = GURL();
227 *response_type_via_service_worker =
228 blink::WebServiceWorkerResponseTypeDefault;
229 return;
231 *was_fetched_via_service_worker = true;
232 *was_fallback_required_by_service_worker = fall_back_required_;
233 *original_url_via_service_worker = response_url_;
234 *response_type_via_service_worker = service_worker_response_type_;
235 *fetch_start_time = fetch_start_time_;
236 *fetch_ready_time = fetch_ready_time_;
237 *fetch_end_time = fetch_end_time_;
241 ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
244 void ServiceWorkerURLRequestJob::MaybeStartRequest() {
245 if (is_started_ && response_type_ != NOT_DETERMINED) {
246 // Start asynchronously.
247 base::MessageLoop::current()->PostTask(
248 FROM_HERE,
249 base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
250 weak_factory_.GetWeakPtr()));
254 void ServiceWorkerURLRequestJob::StartRequest() {
255 switch (response_type_) {
256 case NOT_DETERMINED:
257 NOTREACHED();
258 return;
260 case FALLBACK_TO_NETWORK:
261 // Restart the request to create a new job. Our request handler will
262 // return NULL, and the default job (which will hit network) should be
263 // created.
264 NotifyRestartRequired();
265 return;
267 case FORWARD_TO_SERVICE_WORKER:
268 DCHECK(provider_host_ && provider_host_->active_version());
269 DCHECK(!fetch_dispatcher_);
270 // Send a fetch event to the ServiceWorker associated to the
271 // provider_host.
272 fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
273 CreateFetchRequest(),
274 provider_host_->active_version(),
275 base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent,
276 weak_factory_.GetWeakPtr()),
277 base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
278 weak_factory_.GetWeakPtr())));
279 fetch_start_time_ = base::TimeTicks::Now();
280 load_timing_info_.send_start = fetch_start_time_;
281 fetch_dispatcher_->Run();
282 return;
285 NOTREACHED();
288 scoped_ptr<ServiceWorkerFetchRequest>
289 ServiceWorkerURLRequestJob::CreateFetchRequest() {
290 std::string blob_uuid;
291 uint64 blob_size = 0;
292 CreateRequestBodyBlob(&blob_uuid, &blob_size);
293 scoped_ptr<ServiceWorkerFetchRequest> request(
294 new ServiceWorkerFetchRequest());
295 request->mode = request_mode_;
296 request->request_context_type = request_context_type_;
297 request->frame_type = frame_type_;
298 request->url = request_->url();
299 request->method = request_->method();
300 const net::HttpRequestHeaders& headers = request_->extra_request_headers();
301 for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) {
302 if (it.name() == kDevToolsRequestInitiator ||
303 it.name() == kDevToolsEmulateNetworkConditionsClientId) {
304 continue;
306 request->headers[it.name()] = it.value();
308 request->blob_uuid = blob_uuid;
309 request->blob_size = blob_size;
310 request->referrer = GURL(request_->referrer());
311 request->credentials_mode = credentials_mode_;
312 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
313 if (info) {
314 request->is_reload = ui::PageTransitionCoreTypeIs(
315 info->GetPageTransition(), ui::PAGE_TRANSITION_RELOAD);
317 return request.Pass();
320 bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string* blob_uuid,
321 uint64* blob_size) {
322 if (!body_.get() || !blob_storage_context_)
323 return false;
325 std::vector<const ResourceRequestBody::Element*> resolved_elements;
326 for (size_t i = 0; i < body_->elements()->size(); ++i) {
327 const ResourceRequestBody::Element& element = (*body_->elements())[i];
328 if (element.type() != ResourceRequestBody::Element::TYPE_BLOB) {
329 resolved_elements.push_back(&element);
330 continue;
332 scoped_ptr<storage::BlobDataHandle> handle =
333 blob_storage_context_->GetBlobDataFromUUID(element.blob_uuid());
334 if (handle->data()->items().empty())
335 continue;
336 for (size_t i = 0; i < handle->data()->items().size(); ++i) {
337 const storage::BlobData::Item& item = handle->data()->items().at(i);
338 DCHECK_NE(storage::BlobData::Item::TYPE_BLOB, item.type());
339 resolved_elements.push_back(&item);
343 const std::string uuid(base::GenerateGUID());
344 uint64 total_size = 0;
345 scoped_refptr<storage::BlobData> blob_data = new storage::BlobData(uuid);
346 for (size_t i = 0; i < resolved_elements.size(); ++i) {
347 const ResourceRequestBody::Element& element = *resolved_elements[i];
348 if (total_size != kuint64max && element.length() != kuint64max)
349 total_size += element.length();
350 else
351 total_size = kuint64max;
352 switch (element.type()) {
353 case ResourceRequestBody::Element::TYPE_BYTES:
354 blob_data->AppendData(element.bytes(), element.length());
355 break;
356 case ResourceRequestBody::Element::TYPE_FILE:
357 blob_data->AppendFile(element.path(),
358 element.offset(),
359 element.length(),
360 element.expected_modification_time());
361 break;
362 case ResourceRequestBody::Element::TYPE_BLOB:
363 // Blob elements should be resolved beforehand.
364 NOTREACHED();
365 break;
366 case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM:
367 blob_data->AppendFileSystemFile(element.filesystem_url(),
368 element.offset(),
369 element.length(),
370 element.expected_modification_time());
371 break;
372 default:
373 NOTIMPLEMENTED();
377 request_body_blob_data_handle_ =
378 blob_storage_context_->AddFinishedBlob(blob_data.get());
379 *blob_uuid = uuid;
380 *blob_size = total_size;
381 return true;
384 void ServiceWorkerURLRequestJob::DidPrepareFetchEvent() {
385 fetch_ready_time_ = base::TimeTicks::Now();
388 void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
389 ServiceWorkerStatusCode status,
390 ServiceWorkerFetchEventResult fetch_result,
391 const ServiceWorkerResponse& response) {
392 fetch_dispatcher_.reset();
394 // Check if we're not orphaned.
395 if (!request())
396 return;
398 if (status != SERVICE_WORKER_OK) {
399 // Dispatching event has been failed, falling back to the network.
400 // (Tentative behavior described on github)
401 // TODO(kinuko): consider returning error if we've come here because
402 // unexpected worker termination etc (so that we could fix bugs).
403 // TODO(kinuko): Would be nice to log the error case.
404 response_type_ = FALLBACK_TO_NETWORK;
405 NotifyRestartRequired();
406 return;
409 if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
410 // When the request_mode is |CORS| or |CORS-with-forced-preflight| we can't
411 // simply fallback to the network in the browser process. It is because the
412 // CORS preflight logic is implemented in the renderer. So we returns a
413 // fall_back_required response to the renderer.
414 if (request_mode_ == FETCH_REQUEST_MODE_CORS ||
415 request_mode_ == FETCH_REQUEST_MODE_CORS_WITH_FORCED_PREFLIGHT) {
416 fall_back_required_ = true;
417 CreateResponseHeader(
418 400, "Service Worker Fallback Required", ServiceWorkerHeaderMap());
419 CommitResponseHeader();
420 return;
422 // Change the response type and restart the request to fallback to
423 // the network.
424 response_type_ = FALLBACK_TO_NETWORK;
425 NotifyRestartRequired();
426 return;
429 // We should have a response now.
430 DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);
432 // Treat a response whose status is 0 as a Network Error.
433 if (response.status_code == 0) {
434 NotifyDone(
435 net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
436 return;
439 fetch_end_time_ = base::TimeTicks::Now();
440 load_timing_info_.send_end = fetch_end_time_;
442 // Set up a request for reading the blob.
443 if (!response.blob_uuid.empty() && blob_storage_context_) {
444 scoped_ptr<storage::BlobDataHandle> blob_data_handle =
445 blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
446 if (!blob_data_handle) {
447 // The renderer gave us a bad blob UUID.
448 DeliverErrorResponse();
449 return;
451 blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest(
452 blob_data_handle.Pass(), request()->context(), this);
453 blob_request_->Start();
456 response_url_ = response.url;
457 service_worker_response_type_ = response.response_type;
458 CreateResponseHeader(
459 response.status_code, response.status_text, response.headers);
460 load_timing_info_.receive_headers_end = base::TimeTicks::Now();
461 if (!blob_request_)
462 CommitResponseHeader();
465 void ServiceWorkerURLRequestJob::CreateResponseHeader(
466 int status_code,
467 const std::string& status_text,
468 const ServiceWorkerHeaderMap& headers) {
469 // TODO(kinuko): If the response has an identifier to on-disk cache entry,
470 // pull response header from the disk.
471 std::string status_line(
472 base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
473 status_line.push_back('\0');
474 http_response_headers_ = new net::HttpResponseHeaders(status_line);
475 for (ServiceWorkerHeaderMap::const_iterator it = headers.begin();
476 it != headers.end();
477 ++it) {
478 std::string header;
479 header.reserve(it->first.size() + 2 + it->second.size());
480 header.append(it->first);
481 header.append(": ");
482 header.append(it->second);
483 http_response_headers_->AddHeader(header);
487 void ServiceWorkerURLRequestJob::CommitResponseHeader() {
488 http_response_info_.reset(new net::HttpResponseInfo());
489 http_response_info_->headers.swap(http_response_headers_);
490 NotifyHeadersComplete();
493 void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
494 // TODO(falken): Print an error to the console of the ServiceWorker and of
495 // the requesting page.
496 CreateResponseHeader(
497 500, "Service Worker Response Error", ServiceWorkerHeaderMap());
498 CommitResponseHeader();
501 } // namespace content