1 // Copyright (c) 2012 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. 4 5 #include "content/browser/webui/url_data_manager_backend.h" 6 7 #include <set> 8 9 #include "base/basictypes.h" 10 #include "base/bind.h" 11 #include "base/command_line.h" 12 #include "base/compiler_specific.h" 13 #include "base/debug/trace_event.h" 14 #include "base/lazy_instance.h" 15 #include "base/memory/ref_counted.h" 16 #include "base/memory/ref_counted_memory.h" 17 #include "base/memory/weak_ptr.h" 18 #include "base/message_loop/message_loop.h" 19 #include "base/strings/string_util.h" 20 #include "base/strings/stringprintf.h" 21 #include "content/browser/fileapi/chrome_blob_storage_context.h" 22 #include "content/browser/histogram_internals_request_job.h" 23 #include "content/browser/net/view_blob_internals_job_factory.h" 24 #include "content/browser/net/view_http_cache_job_factory.h" 25 #include "content/browser/resource_context_impl.h" 26 #include "content/browser/tcmalloc_internals_request_job.h" 27 #include "content/browser/webui/shared_resources_data_source.h" 28 #include "content/browser/webui/url_data_source_impl.h" 29 #include "content/public/browser/browser_thread.h" 30 #include "content/public/browser/content_browser_client.h" 31 #include "content/public/browser/render_process_host.h" 32 #include "content/public/browser/resource_request_info.h" 33 #include "content/public/common/url_constants.h" 34 #include "net/base/io_buffer.h" 35 #include "net/base/net_errors.h" 36 #include "net/http/http_response_headers.h" 37 #include "net/http/http_status_code.h" 38 #include "net/url_request/url_request.h" 39 #include "net/url_request/url_request_context.h" 40 #include "net/url_request/url_request_job.h" 41 #include "net/url_request/url_request_job_factory.h" 42 #include "url/url_util.h" 43 #include "webkit/browser/appcache/view_appcache_internals_job.h" 44 45 using appcache::AppCacheService; 46 47 namespace content { 48 49 namespace { 50 51 // TODO(tsepez) remove unsafe-eval when bidichecker_packaged.js fixed. 52 const char kChromeURLContentSecurityPolicyHeaderBase[] = 53 "Content-Security-Policy: script-src chrome://resources " 54 "'self' 'unsafe-eval'; "; 55 56 const char kChromeURLXFrameOptionsHeader[] = "X-Frame-Options: DENY"; 57 58 bool SchemeIsInSchemes(const std::string& scheme, 59 const std::vector<std::string>& schemes) { 60 return std::find(schemes.begin(), schemes.end(), scheme) != schemes.end(); 61 } 62 63 // Parse a URL into the components used to resolve its request. |source_name| 64 // is the hostname and |path| is the remaining portion of the URL. 65 void URLToRequest(const GURL& url, std::string* source_name, 66 std::string* path) { 67 std::vector<std::string> additional_schemes; 68 DCHECK(url.SchemeIs(chrome::kChromeDevToolsScheme) || 69 url.SchemeIs(chrome::kChromeUIScheme) || 70 (GetContentClient()->browser()->GetAdditionalWebUISchemes( 71 &additional_schemes), 72 SchemeIsInSchemes(url.scheme(), additional_schemes))); 73 74 if (!url.is_valid()) { 75 NOTREACHED(); 76 return; 77 } 78 79 // Our input looks like: chrome://source_name/extra_bits?foo . 80 // So the url's "host" is our source, and everything after the host is 81 // the path. 82 source_name->assign(url.host()); 83 84 const std::string& spec = url.possibly_invalid_spec(); 85 const url_parse::Parsed& parsed = url.parsed_for_possibly_invalid_spec(); 86 // + 1 to skip the slash at the beginning of the path. 87 int offset = parsed.CountCharactersBefore(url_parse::Parsed::PATH, false) + 1; 88 89 if (offset < static_cast<int>(spec.size())) 90 path->assign(spec.substr(offset)); 91 } 92 93 } // namespace 94 95 // URLRequestChromeJob is a net::URLRequestJob that manages running 96 // chrome-internal resource requests asynchronously. 97 // It hands off URL requests to ChromeURLDataManager, which asynchronously 98 // calls back once the data is available. 99 class URLRequestChromeJob : public net::URLRequestJob, 100 public base::SupportsWeakPtr<URLRequestChromeJob> { 101 public: 102 // |is_incognito| set when job is generated from an incognito profile. 103 URLRequestChromeJob(net::URLRequest* request, 104 net::NetworkDelegate* network_delegate, 105 URLDataManagerBackend* backend, 106 bool is_incognito); 107 108 // net::URLRequestJob implementation. 109 virtual void Start() OVERRIDE; 110 virtual void Kill() OVERRIDE; 111 virtual bool ReadRawData(net::IOBuffer* buf, 112 int buf_size, 113 int* bytes_read) OVERRIDE; 114 virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; 115 virtual int GetResponseCode() const OVERRIDE; 116 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; 117 118 // Used to notify that the requested data's |mime_type| is ready. 119 void MimeTypeAvailable(const std::string& mime_type); 120 121 // Called by ChromeURLDataManager to notify us that the data blob is ready 122 // for us. 123 void DataAvailable(base::RefCountedMemory* bytes); 124 125 void set_mime_type(const std::string& mime_type) { 126 mime_type_ = mime_type; 127 } 128 129 void set_allow_caching(bool allow_caching) { 130 allow_caching_ = allow_caching; 131 } 132 133 void set_add_content_security_policy(bool add_content_security_policy) { 134 add_content_security_policy_ = add_content_security_policy; 135 } 136 137 void set_content_security_policy_object_source( 138 const std::string& data) { 139 content_security_policy_object_source_ = data; 140 } 141 142 void set_content_security_policy_frame_source( 143 const std::string& data) { 144 content_security_policy_frame_source_ = data; 145 } 146 147 void set_deny_xframe_options(bool deny_xframe_options) { 148 deny_xframe_options_ = deny_xframe_options; 149 } 150 151 void set_send_content_type_header(bool send_content_type_header) { 152 send_content_type_header_ = send_content_type_header; 153 } 154 155 // Returns true when job was generated from an incognito profile. 156 bool is_incognito() const { 157 return is_incognito_; 158 } 159 160 private: 161 virtual ~URLRequestChromeJob(); 162 163 // Helper for Start(), to let us start asynchronously. 164 // (This pattern is shared by most net::URLRequestJob implementations.) 165 void StartAsync(); 166 167 // Do the actual copy from data_ (the data we're serving) into |buf|. 168 // Separate from ReadRawData so we can handle async I/O. 169 void CompleteRead(net::IOBuffer* buf, int buf_size, int* bytes_read); 170 171 // The actual data we're serving. NULL until it's been fetched. 172 scoped_refptr<base::RefCountedMemory> data_; 173 // The current offset into the data that we're handing off to our 174 // callers via the Read interfaces. 175 int data_offset_; 176 177 // For async reads, we keep around a pointer to the buffer that 178 // we're reading into. 179 scoped_refptr<net::IOBuffer> pending_buf_; 180 int pending_buf_size_; 181 std::string mime_type_; 182 183 // If true, set a header in the response to prevent it from being cached. 184 bool allow_caching_; 185 186 // If true, set the Content Security Policy (CSP) header. 187 bool add_content_security_policy_; 188 189 // These are used with the CSP. 190 std::string content_security_policy_object_source_; 191 std::string content_security_policy_frame_source_; 192 193 // If true, sets the "X-Frame-Options: DENY" header. 194 bool deny_xframe_options_; 195 196 // If true, sets the "Content-Type: <mime-type>" header. 197 bool send_content_type_header_; 198 199 // True when job is generated from an incognito profile. 200 const bool is_incognito_; 201 202 // The backend is owned by ChromeURLRequestContext and always outlives us. 203 URLDataManagerBackend* backend_; 204 205 base::WeakPtrFactory<URLRequestChromeJob> weak_factory_; 206 207 DISALLOW_COPY_AND_ASSIGN(URLRequestChromeJob); 208 }; 209 210 URLRequestChromeJob::URLRequestChromeJob(net::URLRequest* request, 211 net::NetworkDelegate* network_delegate, 212 URLDataManagerBackend* backend, 213 bool is_incognito) 214 : net::URLRequestJob(request, network_delegate), 215 data_offset_(0), 216 pending_buf_size_(0), 217 allow_caching_(true), 218 add_content_security_policy_(true), 219 content_security_policy_object_source_("object-src 'none';"), 220 content_security_policy_frame_source_("frame-src 'none';"), 221 deny_xframe_options_(true), 222 send_content_type_header_(false), 223 is_incognito_(is_incognito), 224 backend_(backend), 225 weak_factory_(this) { 226 DCHECK(backend); 227 } 228 229 URLRequestChromeJob::~URLRequestChromeJob() { 230 CHECK(!backend_->HasPendingJob(this)); 231 } 232 233 void URLRequestChromeJob::Start() { 234 // Start reading asynchronously so that all error reporting and data 235 // callbacks happen as they would for network requests. 236 base::MessageLoop::current()->PostTask( 237 FROM_HERE, 238 base::Bind(&URLRequestChromeJob::StartAsync, weak_factory_.GetWeakPtr())); 239 240 TRACE_EVENT_ASYNC_BEGIN1("browser", "DataManager:Request", this, "URL", 241 request_->url().possibly_invalid_spec()); 242 } 243 244 void URLRequestChromeJob::Kill() { 245 backend_->RemoveRequest(this); 246 } 247 248 bool URLRequestChromeJob::GetMimeType(std::string* mime_type) const { 249 *mime_type = mime_type_; 250 return !mime_type_.empty(); 251 } 252 253 int URLRequestChromeJob::GetResponseCode() const { 254 return net::HTTP_OK; 255 } 256 257 void URLRequestChromeJob::GetResponseInfo(net::HttpResponseInfo* info) { 258 DCHECK(!info->headers.get()); 259 // Set the headers so that requests serviced by ChromeURLDataManager return a 260 // status code of 200. Without this they return a 0, which makes the status 261 // indistiguishable from other error types. Instant relies on getting a 200. 262 info->headers = new net::HttpResponseHeaders("HTTP/1.1 200 OK"); 263 264 // Determine the least-privileged content security policy header, if any, 265 // that is compatible with a given WebUI URL, and append it to the existing 266 // response headers. 267 if (add_content_security_policy_) { 268 std::string base = kChromeURLContentSecurityPolicyHeaderBase; 269 base.append(content_security_policy_object_source_); 270 base.append(content_security_policy_frame_source_); 271 info->headers->AddHeader(base); 272 } 273 274 if (deny_xframe_options_) 275 info->headers->AddHeader(kChromeURLXFrameOptionsHeader); 276 277 if (!allow_caching_) 278 info->headers->AddHeader("Cache-Control: no-cache"); 279 280 if (send_content_type_header_ && !mime_type_.empty()) { 281 std::string content_type = 282 base::StringPrintf("%s:%s", net::HttpRequestHeaders::kContentType, 283 mime_type_.c_str()); 284 info->headers->AddHeader(content_type); 285 } 286 } 287 288 void URLRequestChromeJob::MimeTypeAvailable(const std::string& mime_type) { 289 set_mime_type(mime_type); 290 NotifyHeadersComplete(); 291 } 292 293 void URLRequestChromeJob::DataAvailable(base::RefCountedMemory* bytes) { 294 TRACE_EVENT_ASYNC_END0("browser", "DataManager:Request", this); 295 if (bytes) { 296 // The request completed, and we have all the data. 297 // Clear any IO pending status. 298 SetStatus(net::URLRequestStatus()); 299 300 data_ = bytes; 301 int bytes_read; 302 if (pending_buf_.get()) { 303 CHECK(pending_buf_->data()); 304 CompleteRead(pending_buf_.get(), pending_buf_size_, &bytes_read); 305 pending_buf_ = NULL; 306 NotifyReadComplete(bytes_read); 307 } 308 } else { 309 // The request failed. 310 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, 311 net::ERR_FAILED)); 312 } 313 } 314 315 bool URLRequestChromeJob::ReadRawData(net::IOBuffer* buf, int buf_size, 316 int* bytes_read) { 317 if (!data_.get()) { 318 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); 319 DCHECK(!pending_buf_.get()); 320 CHECK(buf->data()); 321 pending_buf_ = buf; 322 pending_buf_size_ = buf_size; 323 return false; // Tell the caller we're still waiting for data. 324 } 325 326 // Otherwise, the data is available. 327 CompleteRead(buf, buf_size, bytes_read); 328 return true; 329 } 330 331 void URLRequestChromeJob::CompleteRead(net::IOBuffer* buf, int buf_size, 332 int* bytes_read) { 333 int remaining = static_cast<int>(data_->size()) - data_offset_; 334 if (buf_size > remaining) 335 buf_size = remaining; 336 if (buf_size > 0) { 337 memcpy(buf->data(), data_->front() + data_offset_, buf_size); 338 data_offset_ += buf_size; 339 } 340 *bytes_read = buf_size; 341 } 342 343 void URLRequestChromeJob::StartAsync() { 344 if (!request_) 345 return; 346 347 if (!backend_->StartRequest(request_, this)) { 348 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, 349 net::ERR_INVALID_URL)); 350 } 351 } 352 353 namespace { 354 355 // Gets mime type for data that is available from |source| by |path|. 356 // After that, notifies |job| that mime type is available. This method 357 // should be called on the UI thread, but notification is performed on 358 // the IO thread. 359 void GetMimeTypeOnUI(URLDataSourceImpl* source, 360 const std::string& path, 361 const base::WeakPtr<URLRequestChromeJob>& job) { 362 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 363 std::string mime_type = source->source()->GetMimeType(path); 364 BrowserThread::PostTask( 365 BrowserThread::IO, FROM_HERE, 366 base::Bind(&URLRequestChromeJob::MimeTypeAvailable, job, mime_type)); 367 } 368 369 } // namespace 370 371 namespace { 372 373 class ChromeProtocolHandler 374 : public net::URLRequestJobFactory::ProtocolHandler { 375 public: 376 // |is_incognito| should be set for incognito profiles. 377 ChromeProtocolHandler(ResourceContext* resource_context, 378 bool is_incognito, 379 AppCacheService* appcache_service, 380 ChromeBlobStorageContext* blob_storage_context) 381 : resource_context_(resource_context), 382 is_incognito_(is_incognito), 383 appcache_service_(appcache_service), 384 blob_storage_context_(blob_storage_context) {} 385 virtual ~ChromeProtocolHandler() {} 386 387 virtual net::URLRequestJob* MaybeCreateJob( 388 net::URLRequest* request, 389 net::NetworkDelegate* network_delegate) const OVERRIDE { 390 DCHECK(request); 391 392 // Check for chrome://view-http-cache/*, which uses its own job type. 393 if (ViewHttpCacheJobFactory::IsSupportedURL(request->url())) 394 return ViewHttpCacheJobFactory::CreateJobForRequest(request, 395 network_delegate); 396 397 // Next check for chrome://appcache-internals/, which uses its own job type. 398 if (request->url().SchemeIs(chrome::kChromeUIScheme) && 399 request->url().host() == kChromeUIAppCacheInternalsHost) { 400 return appcache::ViewAppCacheInternalsJobFactory::CreateJobForRequest( 401 request, network_delegate, appcache_service_); 402 } 403 404 // Next check for chrome://blob-internals/, which uses its own job type. 405 if (ViewBlobInternalsJobFactory::IsSupportedURL(request->url())) { 406 return ViewBlobInternalsJobFactory::CreateJobForRequest( 407 request, network_delegate, blob_storage_context_->context()); 408 } 409 410 #if defined(USE_TCMALLOC) 411 // Next check for chrome://tcmalloc/, which uses its own job type. 412 if (request->url().SchemeIs(chrome::kChromeUIScheme) && 413 request->url().host() == kChromeUITcmallocHost) { 414 return new TcmallocInternalsRequestJob(request, network_delegate); 415 } 416 #endif 417 418 // Next check for chrome://histograms/, which uses its own job type. 419 if (request->url().SchemeIs(chrome::kChromeUIScheme) && 420 request->url().host() == kChromeUIHistogramHost) { 421 return new HistogramInternalsRequestJob(request, network_delegate); 422 } 423 424 // Fall back to using a custom handler 425 return new URLRequestChromeJob( 426 request, network_delegate, 427 GetURLDataManagerForResourceContext(resource_context_), is_incognito_); 428 } 429 430 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE { 431 return false; 432 } 433 434 private: 435 // These members are owned by ProfileIOData, which owns this ProtocolHandler. 436 content::ResourceContext* const resource_context_; 437 438 // True when generated from an incognito profile. 439 const bool is_incognito_; 440 AppCacheService* appcache_service_; 441 ChromeBlobStorageContext* blob_storage_context_; 442 443 DISALLOW_COPY_AND_ASSIGN(ChromeProtocolHandler); 444 }; 445 446 } // namespace 447 448 URLDataManagerBackend::URLDataManagerBackend() 449 : next_request_id_(0) { 450 URLDataSource* shared_source = new SharedResourcesDataSource(); 451 URLDataSourceImpl* source_impl = 452 new URLDataSourceImpl(shared_source->GetSource(), shared_source); 453 AddDataSource(source_impl); 454 } 455 456 URLDataManagerBackend::~URLDataManagerBackend() { 457 for (DataSourceMap::iterator i = data_sources_.begin(); 458 i != data_sources_.end(); ++i) { 459 i->second->backend_ = NULL; 460 } 461 data_sources_.clear(); 462 } 463 464 // static 465 net::URLRequestJobFactory::ProtocolHandler* 466 URLDataManagerBackend::CreateProtocolHandler( 467 content::ResourceContext* resource_context, 468 bool is_incognito, 469 AppCacheService* appcache_service, 470 ChromeBlobStorageContext* blob_storage_context) { 471 DCHECK(resource_context); 472 return new ChromeProtocolHandler( 473 resource_context, is_incognito, appcache_service, blob_storage_context); 474 } 475 476 void URLDataManagerBackend::AddDataSource( 477 URLDataSourceImpl* source) { 478 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 479 DataSourceMap::iterator i = data_sources_.find(source->source_name()); 480 if (i != data_sources_.end()) { 481 if (!source->source()->ShouldReplaceExistingSource()) 482 return; 483 i->second->backend_ = NULL; 484 } 485 data_sources_[source->source_name()] = source; 486 source->backend_ = this; 487 } 488 489 bool URLDataManagerBackend::HasPendingJob( 490 URLRequestChromeJob* job) const { 491 for (PendingRequestMap::const_iterator i = pending_requests_.begin(); 492 i != pending_requests_.end(); ++i) { 493 if (i->second == job) 494 return true; 495 } 496 return false; 497 } 498 499 bool URLDataManagerBackend::StartRequest(const net::URLRequest* request, 500 URLRequestChromeJob* job) { 501 // Parse the URL into a request for a source and path. 502 std::string source_name; 503 std::string path; 504 URLToRequest(request->url(), &source_name, &path); 505 506 // Look up the data source for the request. 507 DataSourceMap::iterator i = data_sources_.find(source_name); 508 if (i == data_sources_.end()) 509 return false; 510 511 URLDataSourceImpl* source = i->second.get(); 512 513 if (!source->source()->ShouldServiceRequest(request)) 514 return false; 515 source->source()->WillServiceRequest(request, &path); 516 517 // Save this request so we know where to send the data. 518 RequestID request_id = next_request_id_++; 519 pending_requests_.insert(std::make_pair(request_id, job)); 520 521 job->set_allow_caching(source->source()->AllowCaching()); 522 job->set_add_content_security_policy( 523 source->source()->ShouldAddContentSecurityPolicy()); 524 job->set_content_security_policy_object_source( 525 source->source()->GetContentSecurityPolicyObjectSrc()); 526 job->set_content_security_policy_frame_source( 527 source->source()->GetContentSecurityPolicyFrameSrc()); 528 job->set_deny_xframe_options( 529 source->source()->ShouldDenyXFrameOptions()); 530 job->set_send_content_type_header( 531 source->source()->ShouldServeMimeTypeAsContentTypeHeader()); 532 533 // Look up additional request info to pass down. 534 int render_process_id = -1; 535 int render_view_id = -1; 536 ResourceRequestInfo::GetRenderViewForRequest(request, 537 &render_process_id, 538 &render_view_id); 539 540 // Forward along the request to the data source. 541 base::MessageLoop* target_message_loop = 542 source->source()->MessageLoopForRequestPath(path); 543 if (!target_message_loop) { 544 job->MimeTypeAvailable(source->source()->GetMimeType(path)); 545 // Eliminate potentially dangling pointer to avoid future use. 546 job = NULL; 547 548 // The DataSource is agnostic to which thread StartDataRequest is called 549 // on for this path. Call directly into it from this thread, the IO 550 // thread. 551 source->source()->StartDataRequest( 552 path, render_process_id, render_view_id, 553 base::Bind(&URLDataSourceImpl::SendResponse, source, request_id)); 554 } else { 555 // URLRequestChromeJob should receive mime type before data. This 556 // is guaranteed because request for mime type is placed in the 557 // message loop before request for data. And correspondingly their 558 // replies are put on the IO thread in the same order. 559 target_message_loop->PostTask( 560 FROM_HERE, 561 base::Bind(&GetMimeTypeOnUI, 562 scoped_refptr<URLDataSourceImpl>(source), 563 path, job->AsWeakPtr())); 564 565 // The DataSource wants StartDataRequest to be called on a specific thread, 566 // usually the UI thread, for this path. 567 target_message_loop->PostTask( 568 FROM_HERE, 569 base::Bind(&URLDataManagerBackend::CallStartRequest, 570 make_scoped_refptr(source), path, render_process_id, 571 render_view_id, request_id)); 572 } 573 return true; 574 } 575 576 void URLDataManagerBackend::CallStartRequest( 577 scoped_refptr<URLDataSourceImpl> source, 578 const std::string& path, 579 int render_process_id, 580 int render_view_id, 581 int request_id) { 582 if (BrowserThread::CurrentlyOn(BrowserThread::UI) && 583 render_process_id != -1 && 584 !RenderProcessHost::FromID(render_process_id)) { 585 // Make the request fail if its initiating renderer is no longer valid. 586 // This can happen when the IO thread posts this task just before the 587 // renderer shuts down. 588 source->SendResponse(request_id, NULL); 589 return; 590 } 591 source->source()->StartDataRequest( 592 path, 593 render_process_id, 594 render_view_id, 595 base::Bind(&URLDataSourceImpl::SendResponse, source, request_id)); 596 } 597 598 void URLDataManagerBackend::RemoveRequest(URLRequestChromeJob* job) { 599 // Remove the request from our list of pending requests. 600 // If/when the source sends the data that was requested, the data will just 601 // be thrown away. 602 for (PendingRequestMap::iterator i = pending_requests_.begin(); 603 i != pending_requests_.end(); ++i) { 604 if (i->second == job) { 605 pending_requests_.erase(i); 606 return; 607 } 608 } 609 } 610 611 void URLDataManagerBackend::DataAvailable(RequestID request_id, 612 base::RefCountedMemory* bytes) { 613 // Forward this data on to the pending net::URLRequest, if it exists. 614 PendingRequestMap::iterator i = pending_requests_.find(request_id); 615 if (i != pending_requests_.end()) { 616 URLRequestChromeJob* job(i->second); 617 pending_requests_.erase(i); 618 job->DataAvailable(bytes); 619 } 620 } 621 622 namespace { 623 624 class DevToolsJobFactory 625 : public net::URLRequestJobFactory::ProtocolHandler { 626 public: 627 // |is_incognito| should be set for incognito profiles. 628 DevToolsJobFactory(content::ResourceContext* resource_context, 629 bool is_incognito); 630 virtual ~DevToolsJobFactory(); 631 632 virtual net::URLRequestJob* MaybeCreateJob( 633 net::URLRequest* request, 634 net::NetworkDelegate* network_delegate) const OVERRIDE; 635 636 private: 637 // |resource_context_| and |network_delegate_| are owned by ProfileIOData, 638 // which owns this ProtocolHandler. 639 content::ResourceContext* const resource_context_; 640 641 // True when generated from an incognito profile. 642 const bool is_incognito_; 643 644 DISALLOW_COPY_AND_ASSIGN(DevToolsJobFactory); 645 }; 646 647 DevToolsJobFactory::DevToolsJobFactory( 648 content::ResourceContext* resource_context, 649 bool is_incognito) 650 : resource_context_(resource_context), 651 is_incognito_(is_incognito) { 652 DCHECK(resource_context_); 653 } 654 655 DevToolsJobFactory::~DevToolsJobFactory() {} 656 657 net::URLRequestJob* 658 DevToolsJobFactory::MaybeCreateJob( 659 net::URLRequest* request, net::NetworkDelegate* network_delegate) const { 660 return new URLRequestChromeJob( 661 request, network_delegate, 662 GetURLDataManagerForResourceContext(resource_context_), is_incognito_); 663 } 664 665 } // namespace 666 667 net::URLRequestJobFactory::ProtocolHandler* 668 CreateDevToolsProtocolHandler(content::ResourceContext* resource_context, 669 bool is_incognito) { 670 return new DevToolsJobFactory(resource_context, is_incognito); 671 } 672 673 } // namespace content 674