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.
5 #include "content/browser/webui/url_data_manager_backend.h"
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_context.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/content_browser_client.h"
32 #include "content/public/browser/render_process_host.h"
33 #include "content/public/browser/resource_request_info.h"
34 #include "content/public/common/url_constants.h"
35 #include "net/base/io_buffer.h"
36 #include "net/base/net_errors.h"
37 #include "net/http/http_response_headers.h"
38 #include "net/http/http_status_code.h"
39 #include "net/url_request/url_request.h"
40 #include "net/url_request/url_request_context.h"
41 #include "net/url_request/url_request_job.h"
42 #include "net/url_request/url_request_job_factory.h"
43 #include "url/url_util.h"
44 #include "webkit/browser/appcache/view_appcache_internals_job.h"
46 using appcache::AppCacheService;
52 // TODO(tsepez) remove unsafe-eval when bidichecker_packaged.js fixed.
53 const char kChromeURLContentSecurityPolicyHeaderBase[] =
54 "Content-Security-Policy: script-src chrome://resources "
55 "'self' 'unsafe-eval'; ";
57 const char kChromeURLXFrameOptionsHeader[] = "X-Frame-Options: DENY";
59 bool SchemeIsInSchemes(const std::string& scheme,
60 const std::vector<std::string>& schemes) {
61 return std::find(schemes.begin(), schemes.end(), scheme) != schemes.end();
64 // Returns whether |url| passes some sanity checks and is a valid GURL.
65 bool CheckURLIsValid(const GURL& url) {
66 std::vector<std::string> additional_schemes;
67 DCHECK(url.SchemeIs(kChromeDevToolsScheme) || url.SchemeIs(kChromeUIScheme) ||
68 (GetContentClient()->browser()->GetAdditionalWebUISchemes(
70 SchemeIsInSchemes(url.scheme(), additional_schemes)));
72 if (!url.is_valid()) {
80 // Parse |url| to get the path which will be used to resolve the request. The
81 // path is the remaining portion after the scheme and hostname.
82 void URLToRequestPath(const GURL& url, std::string* path) {
83 const std::string& spec = url.possibly_invalid_spec();
84 const url_parse::Parsed& parsed = url.parsed_for_possibly_invalid_spec();
85 // + 1 to skip the slash at the beginning of the path.
86 int offset = parsed.CountCharactersBefore(url_parse::Parsed::PATH, false) + 1;
88 if (offset < static_cast<int>(spec.size()))
89 path->assign(spec.substr(offset));
94 // URLRequestChromeJob is a net::URLRequestJob that manages running
95 // chrome-internal resource requests asynchronously.
96 // It hands off URL requests to ChromeURLDataManager, which asynchronously
97 // calls back once the data is available.
98 class URLRequestChromeJob : public net::URLRequestJob,
99 public base::SupportsWeakPtr<URLRequestChromeJob> {
101 // |is_incognito| set when job is generated from an incognito profile.
102 URLRequestChromeJob(net::URLRequest* request,
103 net::NetworkDelegate* network_delegate,
104 URLDataManagerBackend* backend,
107 // net::URLRequestJob implementation.
108 virtual void Start() OVERRIDE;
109 virtual void Kill() OVERRIDE;
110 virtual bool ReadRawData(net::IOBuffer* buf,
112 int* bytes_read) OVERRIDE;
113 virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
114 virtual int GetResponseCode() const OVERRIDE;
115 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE;
117 // Used to notify that the requested data's |mime_type| is ready.
118 void MimeTypeAvailable(const std::string& mime_type);
120 // Called by ChromeURLDataManager to notify us that the data blob is ready
122 void DataAvailable(base::RefCountedMemory* bytes);
124 void set_mime_type(const std::string& mime_type) {
125 mime_type_ = mime_type;
128 void set_allow_caching(bool allow_caching) {
129 allow_caching_ = allow_caching;
132 void set_add_content_security_policy(bool add_content_security_policy) {
133 add_content_security_policy_ = add_content_security_policy;
136 void set_content_security_policy_object_source(
137 const std::string& data) {
138 content_security_policy_object_source_ = data;
141 void set_content_security_policy_frame_source(
142 const std::string& data) {
143 content_security_policy_frame_source_ = data;
146 void set_deny_xframe_options(bool deny_xframe_options) {
147 deny_xframe_options_ = deny_xframe_options;
150 void set_send_content_type_header(bool send_content_type_header) {
151 send_content_type_header_ = send_content_type_header;
154 // Returns true when job was generated from an incognito profile.
155 bool is_incognito() const {
156 return is_incognito_;
160 virtual ~URLRequestChromeJob();
162 // Helper for Start(), to let us start asynchronously.
163 // (This pattern is shared by most net::URLRequestJob implementations.)
164 void StartAsync(bool allowed);
166 // Called on the UI thread to check if this request is allowed.
167 static void CheckStoragePartitionMatches(
168 int render_process_id,
170 const base::WeakPtr<URLRequestChromeJob>& job);
172 // Do the actual copy from data_ (the data we're serving) into |buf|.
173 // Separate from ReadRawData so we can handle async I/O.
174 void CompleteRead(net::IOBuffer* buf, int buf_size, int* bytes_read);
176 // The actual data we're serving. NULL until it's been fetched.
177 scoped_refptr<base::RefCountedMemory> data_;
178 // The current offset into the data that we're handing off to our
179 // callers via the Read interfaces.
182 // For async reads, we keep around a pointer to the buffer that
183 // we're reading into.
184 scoped_refptr<net::IOBuffer> pending_buf_;
185 int pending_buf_size_;
186 std::string mime_type_;
188 // If true, set a header in the response to prevent it from being cached.
191 // If true, set the Content Security Policy (CSP) header.
192 bool add_content_security_policy_;
194 // These are used with the CSP.
195 std::string content_security_policy_object_source_;
196 std::string content_security_policy_frame_source_;
198 // If true, sets the "X-Frame-Options: DENY" header.
199 bool deny_xframe_options_;
201 // If true, sets the "Content-Type: <mime-type>" header.
202 bool send_content_type_header_;
204 // True when job is generated from an incognito profile.
205 const bool is_incognito_;
207 // The backend is owned by ChromeURLRequestContext and always outlives us.
208 URLDataManagerBackend* backend_;
210 base::WeakPtrFactory<URLRequestChromeJob> weak_factory_;
212 DISALLOW_COPY_AND_ASSIGN(URLRequestChromeJob);
215 URLRequestChromeJob::URLRequestChromeJob(net::URLRequest* request,
216 net::NetworkDelegate* network_delegate,
217 URLDataManagerBackend* backend,
219 : net::URLRequestJob(request, network_delegate),
221 pending_buf_size_(0),
222 allow_caching_(true),
223 add_content_security_policy_(true),
224 content_security_policy_object_source_("object-src 'none';"),
225 content_security_policy_frame_source_("frame-src 'none';"),
226 deny_xframe_options_(true),
227 send_content_type_header_(false),
228 is_incognito_(is_incognito),
230 weak_factory_(this) {
234 URLRequestChromeJob::~URLRequestChromeJob() {
235 CHECK(!backend_->HasPendingJob(this));
238 void URLRequestChromeJob::Start() {
239 int render_process_id, unused;
240 ResourceRequestInfo::GetRenderFrameForRequest(
241 request_, &render_process_id, &unused);
242 BrowserThread::PostTask(
245 base::Bind(&URLRequestChromeJob::CheckStoragePartitionMatches,
246 render_process_id, request_->url(), AsWeakPtr()));
247 TRACE_EVENT_ASYNC_BEGIN1("browser", "DataManager:Request", this, "URL",
248 request_->url().possibly_invalid_spec());
251 void URLRequestChromeJob::Kill() {
252 backend_->RemoveRequest(this);
255 bool URLRequestChromeJob::GetMimeType(std::string* mime_type) const {
256 *mime_type = mime_type_;
257 return !mime_type_.empty();
260 int URLRequestChromeJob::GetResponseCode() const {
264 void URLRequestChromeJob::GetResponseInfo(net::HttpResponseInfo* info) {
265 DCHECK(!info->headers.get());
266 // Set the headers so that requests serviced by ChromeURLDataManager return a
267 // status code of 200. Without this they return a 0, which makes the status
268 // indistiguishable from other error types. Instant relies on getting a 200.
269 info->headers = new net::HttpResponseHeaders("HTTP/1.1 200 OK");
271 // Determine the least-privileged content security policy header, if any,
272 // that is compatible with a given WebUI URL, and append it to the existing
274 if (add_content_security_policy_) {
275 std::string base = kChromeURLContentSecurityPolicyHeaderBase;
276 base.append(content_security_policy_object_source_);
277 base.append(content_security_policy_frame_source_);
278 info->headers->AddHeader(base);
281 if (deny_xframe_options_)
282 info->headers->AddHeader(kChromeURLXFrameOptionsHeader);
285 info->headers->AddHeader("Cache-Control: no-cache");
287 if (send_content_type_header_ && !mime_type_.empty()) {
288 std::string content_type =
289 base::StringPrintf("%s:%s", net::HttpRequestHeaders::kContentType,
291 info->headers->AddHeader(content_type);
295 void URLRequestChromeJob::MimeTypeAvailable(const std::string& mime_type) {
296 set_mime_type(mime_type);
297 NotifyHeadersComplete();
300 void URLRequestChromeJob::DataAvailable(base::RefCountedMemory* bytes) {
301 TRACE_EVENT_ASYNC_END0("browser", "DataManager:Request", this);
303 // The request completed, and we have all the data.
304 // Clear any IO pending status.
305 SetStatus(net::URLRequestStatus());
309 if (pending_buf_.get()) {
310 CHECK(pending_buf_->data());
311 CompleteRead(pending_buf_.get(), pending_buf_size_, &bytes_read);
313 NotifyReadComplete(bytes_read);
316 // The request failed.
317 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
322 bool URLRequestChromeJob::ReadRawData(net::IOBuffer* buf, int buf_size,
325 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
326 DCHECK(!pending_buf_.get());
329 pending_buf_size_ = buf_size;
330 return false; // Tell the caller we're still waiting for data.
333 // Otherwise, the data is available.
334 CompleteRead(buf, buf_size, bytes_read);
338 void URLRequestChromeJob::CompleteRead(net::IOBuffer* buf, int buf_size,
340 int remaining = static_cast<int>(data_->size()) - data_offset_;
341 if (buf_size > remaining)
342 buf_size = remaining;
344 memcpy(buf->data(), data_->front() + data_offset_, buf_size);
345 data_offset_ += buf_size;
347 *bytes_read = buf_size;
350 void URLRequestChromeJob::CheckStoragePartitionMatches(
351 int render_process_id,
353 const base::WeakPtr<URLRequestChromeJob>& job) {
354 // The embedder could put some webui pages in separate storage partition.
355 // RenderProcessHostImpl::IsSuitableHost would guard against top level pages
356 // being in the same process. We do an extra check to guard against an
357 // exploited renderer pretending to add them as a subframe. We skip this check
359 // TODO(guohui): move URL constants for favicon, theme, thumb, thumb and
360 // thumbnails from chrome/common/url_constants.h to
361 // content/public/common/url_constants.h, so that they could be reused here.
362 bool allowed = false;
363 if (url.SchemeIs(kChromeUIScheme) &&
364 (url.host() == kChromeUIResourcesHost ||
365 url.host() == "favicon" ||
366 url.host() == "theme" ||
367 url.host() == "thumb" ||
368 url.host() == "thumb2" ||
369 url.host() == "thumbnails")) {
372 RenderProcessHost* process = RenderProcessHost::FromID(render_process_id);
374 StoragePartition* partition = BrowserContext::GetStoragePartitionForSite(
375 process->GetBrowserContext(), url);
376 allowed = partition == process->GetStoragePartition();
380 BrowserThread::PostTask(
383 base::Bind(&URLRequestChromeJob::StartAsync, job, allowed));
386 void URLRequestChromeJob::StartAsync(bool allowed) {
390 if (!allowed || !backend_->StartRequest(request_, this)) {
391 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
392 net::ERR_INVALID_URL));
398 // Gets mime type for data that is available from |source| by |path|.
399 // After that, notifies |job| that mime type is available. This method
400 // should be called on the UI thread, but notification is performed on
402 void GetMimeTypeOnUI(URLDataSourceImpl* source,
403 const std::string& path,
404 const base::WeakPtr<URLRequestChromeJob>& job) {
405 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
406 std::string mime_type = source->source()->GetMimeType(path);
407 BrowserThread::PostTask(
408 BrowserThread::IO, FROM_HERE,
409 base::Bind(&URLRequestChromeJob::MimeTypeAvailable, job, mime_type));
416 class ChromeProtocolHandler
417 : public net::URLRequestJobFactory::ProtocolHandler {
419 // |is_incognito| should be set for incognito profiles.
420 ChromeProtocolHandler(ResourceContext* resource_context,
422 AppCacheService* appcache_service,
423 ChromeBlobStorageContext* blob_storage_context)
424 : resource_context_(resource_context),
425 is_incognito_(is_incognito),
426 appcache_service_(appcache_service),
427 blob_storage_context_(blob_storage_context) {}
428 virtual ~ChromeProtocolHandler() {}
430 virtual net::URLRequestJob* MaybeCreateJob(
431 net::URLRequest* request,
432 net::NetworkDelegate* network_delegate) const OVERRIDE {
435 // Check for chrome://view-http-cache/*, which uses its own job type.
436 if (ViewHttpCacheJobFactory::IsSupportedURL(request->url()))
437 return ViewHttpCacheJobFactory::CreateJobForRequest(request,
440 // Next check for chrome://appcache-internals/, which uses its own job type.
441 if (request->url().SchemeIs(kChromeUIScheme) &&
442 request->url().host() == kChromeUIAppCacheInternalsHost) {
443 return appcache::ViewAppCacheInternalsJobFactory::CreateJobForRequest(
444 request, network_delegate, appcache_service_);
447 // Next check for chrome://blob-internals/, which uses its own job type.
448 if (ViewBlobInternalsJobFactory::IsSupportedURL(request->url())) {
449 return ViewBlobInternalsJobFactory::CreateJobForRequest(
450 request, network_delegate, blob_storage_context_->context());
453 #if defined(USE_TCMALLOC)
454 // Next check for chrome://tcmalloc/, which uses its own job type.
455 if (request->url().SchemeIs(kChromeUIScheme) &&
456 request->url().host() == kChromeUITcmallocHost) {
457 return new TcmallocInternalsRequestJob(request, network_delegate);
461 // Next check for chrome://histograms/, which uses its own job type.
462 if (request->url().SchemeIs(kChromeUIScheme) &&
463 request->url().host() == kChromeUIHistogramHost) {
464 return new HistogramInternalsRequestJob(request, network_delegate);
467 // Fall back to using a custom handler
468 return new URLRequestChromeJob(
469 request, network_delegate,
470 GetURLDataManagerForResourceContext(resource_context_), is_incognito_);
473 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE {
478 // These members are owned by ProfileIOData, which owns this ProtocolHandler.
479 content::ResourceContext* const resource_context_;
481 // True when generated from an incognito profile.
482 const bool is_incognito_;
483 AppCacheService* appcache_service_;
484 ChromeBlobStorageContext* blob_storage_context_;
486 DISALLOW_COPY_AND_ASSIGN(ChromeProtocolHandler);
491 URLDataManagerBackend::URLDataManagerBackend()
492 : next_request_id_(0) {
493 URLDataSource* shared_source = new SharedResourcesDataSource();
494 URLDataSourceImpl* source_impl =
495 new URLDataSourceImpl(shared_source->GetSource(), shared_source);
496 AddDataSource(source_impl);
499 URLDataManagerBackend::~URLDataManagerBackend() {
500 for (DataSourceMap::iterator i = data_sources_.begin();
501 i != data_sources_.end(); ++i) {
502 i->second->backend_ = NULL;
504 data_sources_.clear();
508 net::URLRequestJobFactory::ProtocolHandler*
509 URLDataManagerBackend::CreateProtocolHandler(
510 content::ResourceContext* resource_context,
512 AppCacheService* appcache_service,
513 ChromeBlobStorageContext* blob_storage_context) {
514 DCHECK(resource_context);
515 return new ChromeProtocolHandler(
516 resource_context, is_incognito, appcache_service, blob_storage_context);
519 void URLDataManagerBackend::AddDataSource(
520 URLDataSourceImpl* source) {
521 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
522 DataSourceMap::iterator i = data_sources_.find(source->source_name());
523 if (i != data_sources_.end()) {
524 if (!source->source()->ShouldReplaceExistingSource())
526 i->second->backend_ = NULL;
528 data_sources_[source->source_name()] = source;
529 source->backend_ = this;
532 bool URLDataManagerBackend::HasPendingJob(
533 URLRequestChromeJob* job) const {
534 for (PendingRequestMap::const_iterator i = pending_requests_.begin();
535 i != pending_requests_.end(); ++i) {
536 if (i->second == job)
542 bool URLDataManagerBackend::StartRequest(const net::URLRequest* request,
543 URLRequestChromeJob* job) {
544 if (!CheckURLIsValid(request->url()))
547 URLDataSourceImpl* source = GetDataSourceFromURL(request->url());
551 if (!source->source()->ShouldServiceRequest(request))
555 URLToRequestPath(request->url(), &path);
556 source->source()->WillServiceRequest(request, &path);
558 // Save this request so we know where to send the data.
559 RequestID request_id = next_request_id_++;
560 pending_requests_.insert(std::make_pair(request_id, job));
562 job->set_allow_caching(source->source()->AllowCaching());
563 job->set_add_content_security_policy(
564 source->source()->ShouldAddContentSecurityPolicy());
565 job->set_content_security_policy_object_source(
566 source->source()->GetContentSecurityPolicyObjectSrc());
567 job->set_content_security_policy_frame_source(
568 source->source()->GetContentSecurityPolicyFrameSrc());
569 job->set_deny_xframe_options(
570 source->source()->ShouldDenyXFrameOptions());
571 job->set_send_content_type_header(
572 source->source()->ShouldServeMimeTypeAsContentTypeHeader());
574 // Look up additional request info to pass down.
575 int render_process_id = -1;
576 int render_frame_id = -1;
577 ResourceRequestInfo::GetRenderFrameForRequest(request,
581 // Forward along the request to the data source.
582 base::MessageLoop* target_message_loop =
583 source->source()->MessageLoopForRequestPath(path);
584 if (!target_message_loop) {
585 job->MimeTypeAvailable(source->source()->GetMimeType(path));
586 // Eliminate potentially dangling pointer to avoid future use.
589 // The DataSource is agnostic to which thread StartDataRequest is called
590 // on for this path. Call directly into it from this thread, the IO
592 source->source()->StartDataRequest(
593 path, render_process_id, render_frame_id,
594 base::Bind(&URLDataSourceImpl::SendResponse, source, request_id));
596 // URLRequestChromeJob should receive mime type before data. This
597 // is guaranteed because request for mime type is placed in the
598 // message loop before request for data. And correspondingly their
599 // replies are put on the IO thread in the same order.
600 target_message_loop->PostTask(
602 base::Bind(&GetMimeTypeOnUI,
603 scoped_refptr<URLDataSourceImpl>(source),
604 path, job->AsWeakPtr()));
606 // The DataSource wants StartDataRequest to be called on a specific thread,
607 // usually the UI thread, for this path.
608 target_message_loop->PostTask(
610 base::Bind(&URLDataManagerBackend::CallStartRequest,
611 make_scoped_refptr(source), path, render_process_id,
612 render_frame_id, request_id));
617 URLDataSourceImpl* URLDataManagerBackend::GetDataSourceFromURL(
619 // The input usually looks like: chrome://source_name/extra_bits?foo
620 // so do a lookup using the host of the URL.
621 DataSourceMap::iterator i = data_sources_.find(url.host());
622 if (i != data_sources_.end())
623 return i->second.get();
625 // No match using the host of the URL, so do a lookup using the scheme for
626 // URLs on the form source_name://extra_bits/foo .
627 i = data_sources_.find(url.scheme() + "://");
628 if (i != data_sources_.end())
629 return i->second.get();
631 // No matches found, so give up.
635 void URLDataManagerBackend::CallStartRequest(
636 scoped_refptr<URLDataSourceImpl> source,
637 const std::string& path,
638 int render_process_id,
641 if (BrowserThread::CurrentlyOn(BrowserThread::UI) &&
642 render_process_id != -1 &&
643 !RenderProcessHost::FromID(render_process_id)) {
644 // Make the request fail if its initiating renderer is no longer valid.
645 // This can happen when the IO thread posts this task just before the
646 // renderer shuts down.
647 source->SendResponse(request_id, NULL);
650 source->source()->StartDataRequest(
654 base::Bind(&URLDataSourceImpl::SendResponse, source, request_id));
657 void URLDataManagerBackend::RemoveRequest(URLRequestChromeJob* job) {
658 // Remove the request from our list of pending requests.
659 // If/when the source sends the data that was requested, the data will just
661 for (PendingRequestMap::iterator i = pending_requests_.begin();
662 i != pending_requests_.end(); ++i) {
663 if (i->second == job) {
664 pending_requests_.erase(i);
670 void URLDataManagerBackend::DataAvailable(RequestID request_id,
671 base::RefCountedMemory* bytes) {
672 // Forward this data on to the pending net::URLRequest, if it exists.
673 PendingRequestMap::iterator i = pending_requests_.find(request_id);
674 if (i != pending_requests_.end()) {
675 URLRequestChromeJob* job(i->second);
676 pending_requests_.erase(i);
677 job->DataAvailable(bytes);
683 class DevToolsJobFactory
684 : public net::URLRequestJobFactory::ProtocolHandler {
686 // |is_incognito| should be set for incognito profiles.
687 DevToolsJobFactory(content::ResourceContext* resource_context,
689 virtual ~DevToolsJobFactory();
691 virtual net::URLRequestJob* MaybeCreateJob(
692 net::URLRequest* request,
693 net::NetworkDelegate* network_delegate) const OVERRIDE;
696 // |resource_context_| and |network_delegate_| are owned by ProfileIOData,
697 // which owns this ProtocolHandler.
698 content::ResourceContext* const resource_context_;
700 // True when generated from an incognito profile.
701 const bool is_incognito_;
703 DISALLOW_COPY_AND_ASSIGN(DevToolsJobFactory);
706 DevToolsJobFactory::DevToolsJobFactory(
707 content::ResourceContext* resource_context,
709 : resource_context_(resource_context),
710 is_incognito_(is_incognito) {
711 DCHECK(resource_context_);
714 DevToolsJobFactory::~DevToolsJobFactory() {}
717 DevToolsJobFactory::MaybeCreateJob(
718 net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
719 return new URLRequestChromeJob(
720 request, network_delegate,
721 GetURLDataManagerForResourceContext(resource_context_), is_incognito_);
726 net::URLRequestJobFactory::ProtocolHandler*
727 CreateDevToolsProtocolHandler(content::ResourceContext* resource_context,
729 return new DevToolsJobFactory(resource_context, is_incognito);
732 } // namespace content