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/child/web_url_loader_impl.h"
9 #include "base/command_line.h"
10 #include "base/macros.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/time/time.h"
15 #include "content/child/request_extra_data.h"
16 #include "content/child/request_info.h"
17 #include "content/child/resource_dispatcher.h"
18 #include "content/public/child/request_peer.h"
19 #include "content/public/common/content_switches.h"
20 #include "content/public/common/resource_response_info.h"
21 #include "net/base/net_errors.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_util.h"
24 #include "net/url_request/redirect_info.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "third_party/WebKit/public/platform/WebString.h"
27 #include "third_party/WebKit/public/platform/WebURLError.h"
28 #include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
29 #include "third_party/WebKit/public/platform/WebURLRequest.h"
30 #include "third_party/WebKit/public/platform/WebURLResponse.h"
36 const char kTestURL
[] = "http://foo";
37 const char kTestData
[] = "blah!";
39 const char kFtpDirMimeType
[] = "text/vnd.chromium.ftp-dir";
40 // Simple FTP directory listing. Tests are not concerned with correct parsing,
41 // but rather correct cleanup when deleted while parsing. Important details of
42 // this list are that it contains more than one entry that are not "." or "..".
43 const char kFtpDirListing
[] =
44 "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 goat\n"
45 "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 hat";
47 const char kMultipartResponseMimeType
[] = "multipart/x-mixed-replace";
48 const char kMultipartResponseHeaders
[] =
49 "HTTP/1.0 200 Peachy\r\n"
50 "Content-Type: multipart/x-mixed-replace; boundary=boundary\r\n\r\n";
51 // Simple multipart response. Imporant details for the tests are that it
52 // contains multiple chunks, and that it doesn't end with a boundary, so will
53 // send data in OnResponseComplete. Also, it will resolve to kTestData.
54 const char kMultipartResponse
[] =
56 "Content-type: text/html\n\n"
59 "Content-type: text/html\n\n"
62 class TestResourceDispatcher
: public ResourceDispatcher
{
64 TestResourceDispatcher() :
65 ResourceDispatcher(nullptr, nullptr),
70 ~TestResourceDispatcher() override
{}
72 // TestDispatcher implementation:
74 int StartAsync(const RequestInfo
& request_info
,
75 ResourceRequestBody
* request_body
,
76 RequestPeer
* peer
) override
{
79 url_
= request_info
.url
;
83 void Cancel(int request_id
) override
{
84 EXPECT_FALSE(canceled_
);
88 RequestPeer
* peer() { return peer_
; }
90 bool canceled() { return canceled_
; }
92 const GURL
& url() { return url_
; }
99 DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcher
);
102 class TestWebURLLoaderClient
: public blink::WebURLLoaderClient
{
104 TestWebURLLoaderClient(
105 ResourceDispatcher
* dispatcher
,
106 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
)
107 : loader_(new WebURLLoaderImpl(dispatcher
, task_runner
)),
108 expect_multipart_response_(false),
109 delete_on_receive_redirect_(false),
110 delete_on_receive_response_(false),
111 delete_on_receive_data_(false),
112 delete_on_finish_(false),
113 delete_on_fail_(false),
114 did_receive_redirect_(false),
115 did_receive_response_(false),
116 did_finish_(false) {}
118 virtual ~TestWebURLLoaderClient() {}
120 // blink::WebURLLoaderClient implementation:
121 virtual void willSendRequest(
122 blink::WebURLLoader
* loader
,
123 blink::WebURLRequest
& newRequest
,
124 const blink::WebURLResponse
& redirectResponse
) override
{
125 EXPECT_TRUE(loader_
);
126 EXPECT_EQ(loader_
.get(), loader
);
127 // No test currently simulates mutiple redirects.
128 EXPECT_FALSE(did_receive_redirect_
);
129 did_receive_redirect_
= true;
131 if (delete_on_receive_redirect_
)
135 virtual void didSendData(blink::WebURLLoader
* loader
,
136 unsigned long long bytesSent
,
137 unsigned long long totalBytesToBeSent
) override
{
138 EXPECT_TRUE(loader_
);
139 EXPECT_EQ(loader_
.get(), loader
);
142 virtual void didReceiveResponse(
143 blink::WebURLLoader
* loader
,
144 const blink::WebURLResponse
& response
) override
{
145 EXPECT_TRUE(loader_
);
146 EXPECT_EQ(loader_
.get(), loader
);
148 // Only multipart requests may receive multiple response headers.
149 EXPECT_TRUE(expect_multipart_response_
|| !did_receive_response_
);
151 did_receive_response_
= true;
152 response_
= response
;
153 if (delete_on_receive_response_
)
157 virtual void didDownloadData(blink::WebURLLoader
* loader
,
159 int encodedDataLength
) override
{
160 EXPECT_TRUE(loader_
);
161 EXPECT_EQ(loader_
.get(), loader
);
164 virtual void didReceiveData(blink::WebURLLoader
* loader
,
167 int encodedDataLength
) override
{
168 EXPECT_TRUE(loader_
);
169 EXPECT_EQ(loader_
.get(), loader
);
170 // The response should have started, but must not have finished, or failed.
171 EXPECT_TRUE(did_receive_response_
);
172 EXPECT_FALSE(did_finish_
);
173 EXPECT_EQ(net::OK
, error_
.reason
);
174 EXPECT_EQ("", error_
.domain
.utf8());
176 received_data_
.append(data
, dataLength
);
178 if (delete_on_receive_data_
)
182 virtual void didReceiveCachedMetadata(blink::WebURLLoader
* loader
,
184 int dataLength
) override
{
185 EXPECT_EQ(loader_
.get(), loader
);
188 virtual void didFinishLoading(blink::WebURLLoader
* loader
,
190 int64_t totalEncodedDataLength
) override
{
191 EXPECT_TRUE(loader_
);
192 EXPECT_EQ(loader_
.get(), loader
);
193 EXPECT_TRUE(did_receive_response_
);
194 EXPECT_FALSE(did_finish_
);
197 if (delete_on_finish_
)
201 virtual void didFail(blink::WebURLLoader
* loader
,
202 const blink::WebURLError
& error
) override
{
203 EXPECT_TRUE(loader_
);
204 EXPECT_EQ(loader_
.get(), loader
);
205 EXPECT_FALSE(did_finish_
);
212 WebURLLoaderImpl
* loader() { return loader_
.get(); }
213 void DeleteLoader() {
217 void set_expect_multipart_response() { expect_multipart_response_
= true; }
219 void set_delete_on_receive_redirect() { delete_on_receive_redirect_
= true; }
220 void set_delete_on_receive_response() { delete_on_receive_response_
= true; }
221 void set_delete_on_receive_data() { delete_on_receive_data_
= true; }
222 void set_delete_on_finish() { delete_on_finish_
= true; }
223 void set_delete_on_fail() { delete_on_fail_
= true; }
225 bool did_receive_redirect() const { return did_receive_redirect_
; }
226 bool did_receive_response() const { return did_receive_response_
; }
227 const std::string
& received_data() const { return received_data_
; }
228 bool did_finish() const { return did_finish_
; }
229 const blink::WebURLError
& error() const { return error_
; }
230 const blink::WebURLResponse
& response() const { return response_
; }
233 scoped_ptr
<WebURLLoaderImpl
> loader_
;
235 bool expect_multipart_response_
;
237 bool delete_on_receive_redirect_
;
238 bool delete_on_receive_response_
;
239 bool delete_on_receive_data_
;
240 bool delete_on_finish_
;
241 bool delete_on_fail_
;
243 bool did_receive_redirect_
;
244 bool did_receive_response_
;
245 std::string received_data_
;
247 blink::WebURLError error_
;
248 blink::WebURLResponse response_
;
250 DISALLOW_COPY_AND_ASSIGN(TestWebURLLoaderClient
);
253 class WebURLLoaderImplTest
: public testing::Test
{
255 explicit WebURLLoaderImplTest()
256 : client_(&dispatcher_
, message_loop_
.task_runner()) {}
257 ~WebURLLoaderImplTest() override
{}
259 void DoStartAsyncRequest() {
260 blink::WebURLRequest request
;
261 request
.initialize();
262 request
.setURL(GURL(kTestURL
));
263 client()->loader()->loadAsynchronously(request
, client());
267 void DoReceiveRedirect() {
268 EXPECT_FALSE(client()->did_receive_redirect());
269 net::RedirectInfo redirect_info
;
270 redirect_info
.status_code
= 302;
271 redirect_info
.new_method
= "GET";
272 redirect_info
.new_url
= GURL(kTestURL
);
273 redirect_info
.new_first_party_for_cookies
= GURL(kTestURL
);
274 peer()->OnReceivedRedirect(redirect_info
,
275 content::ResourceResponseInfo());
276 EXPECT_TRUE(client()->did_receive_redirect());
279 void DoReceiveResponse() {
280 EXPECT_FALSE(client()->did_receive_response());
281 peer()->OnReceivedResponse(content::ResourceResponseInfo());
282 EXPECT_TRUE(client()->did_receive_response());
285 // Assumes it is called only once for a request.
286 void DoReceiveData() {
287 EXPECT_EQ("", client()->received_data());
288 peer()->OnReceivedData(kTestData
, strlen(kTestData
), strlen(kTestData
));
289 EXPECT_EQ(kTestData
, client()->received_data());
292 void DoCompleteRequest() {
293 EXPECT_FALSE(client()->did_finish());
294 peer()->OnCompletedRequest(net::OK
, false, false, "", base::TimeTicks(),
296 EXPECT_TRUE(client()->did_finish());
297 // There should be no error.
298 EXPECT_EQ(net::OK
, client()->error().reason
);
299 EXPECT_EQ("", client()->error().domain
.utf8());
302 void DoFailRequest() {
303 EXPECT_FALSE(client()->did_finish());
304 peer()->OnCompletedRequest(net::ERR_FAILED
, false, false, "",
305 base::TimeTicks(), strlen(kTestData
));
306 EXPECT_FALSE(client()->did_finish());
307 EXPECT_EQ(net::ERR_FAILED
, client()->error().reason
);
308 EXPECT_EQ(net::kErrorDomain
, client()->error().domain
.utf8());
311 void DoReceiveResponseFtp() {
312 EXPECT_FALSE(client()->did_receive_response());
313 content::ResourceResponseInfo response_info
;
314 response_info
.mime_type
= kFtpDirMimeType
;
315 peer()->OnReceivedResponse(response_info
);
316 EXPECT_TRUE(client()->did_receive_response());
319 void DoReceiveDataFtp() {
320 peer()->OnReceivedData(kFtpDirListing
, strlen(kFtpDirListing
),
321 strlen(kFtpDirListing
));
322 // The FTP delegate should modify the data the client sees.
323 EXPECT_NE(kFtpDirListing
, client()->received_data());
326 void DoReceiveResponseMultipart() {
327 EXPECT_FALSE(client()->did_receive_response());
328 content::ResourceResponseInfo response_info
;
329 response_info
.headers
= new net::HttpResponseHeaders(
330 net::HttpUtil::AssembleRawHeaders(kMultipartResponseHeaders
,
331 strlen(kMultipartResponseHeaders
)));
332 response_info
.mime_type
= kMultipartResponseMimeType
;
333 peer()->OnReceivedResponse(response_info
);
334 EXPECT_TRUE(client()->did_receive_response());
337 void DoReceiveDataMultipart() {
338 peer()->OnReceivedData(kMultipartResponse
, strlen(kMultipartResponse
),
339 strlen(kMultipartResponse
));
340 // Multipart delegate should modify the data the client sees.
341 EXPECT_NE(kMultipartResponse
, client()->received_data());
344 TestWebURLLoaderClient
* client() { return &client_
; }
345 TestResourceDispatcher
* dispatcher() { return &dispatcher_
; }
346 RequestPeer
* peer() { return dispatcher()->peer(); }
347 base::MessageLoop
* message_loop() { return &message_loop_
; }
350 base::MessageLoop message_loop_
;
351 TestResourceDispatcher dispatcher_
;
352 TestWebURLLoaderClient client_
;
355 TEST_F(WebURLLoaderImplTest
, Success
) {
356 DoStartAsyncRequest();
360 EXPECT_FALSE(dispatcher()->canceled());
361 EXPECT_EQ(kTestData
, client()->received_data());
364 TEST_F(WebURLLoaderImplTest
, Redirect
) {
365 DoStartAsyncRequest();
370 EXPECT_FALSE(dispatcher()->canceled());
371 EXPECT_EQ(kTestData
, client()->received_data());
374 TEST_F(WebURLLoaderImplTest
, Failure
) {
375 DoStartAsyncRequest();
379 EXPECT_FALSE(dispatcher()->canceled());
382 // The client may delete the WebURLLoader during any callback from the loader.
383 // These tests make sure that doesn't result in a crash.
384 TEST_F(WebURLLoaderImplTest
, DeleteOnReceiveRedirect
) {
385 client()->set_delete_on_receive_redirect();
386 DoStartAsyncRequest();
390 TEST_F(WebURLLoaderImplTest
, DeleteOnReceiveResponse
) {
391 client()->set_delete_on_receive_response();
392 DoStartAsyncRequest();
396 TEST_F(WebURLLoaderImplTest
, DeleteOnReceiveData
) {
397 client()->set_delete_on_receive_data();
398 DoStartAsyncRequest();
403 TEST_F(WebURLLoaderImplTest
, DeleteOnFinish
) {
404 client()->set_delete_on_finish();
405 DoStartAsyncRequest();
411 TEST_F(WebURLLoaderImplTest
, DeleteOnFail
) {
412 client()->set_delete_on_fail();
413 DoStartAsyncRequest();
419 TEST_F(WebURLLoaderImplTest
, DeleteBeforeResponseDataURL
) {
420 blink::WebURLRequest request
;
421 request
.initialize();
422 request
.setURL(GURL("data:text/html;charset=utf-8,blah!"));
423 client()->loader()->loadAsynchronously(request
, client());
424 client()->DeleteLoader();
425 message_loop()->RunUntilIdle();
426 EXPECT_FALSE(client()->did_receive_response());
431 TEST_F(WebURLLoaderImplTest
, DataURL
) {
432 blink::WebURLRequest request
;
433 request
.initialize();
434 request
.setURL(GURL("data:text/html;charset=utf-8,blah!"));
435 client()->loader()->loadAsynchronously(request
, client());
436 message_loop()->RunUntilIdle();
437 EXPECT_EQ("blah!", client()->received_data());
438 EXPECT_TRUE(client()->did_finish());
439 EXPECT_EQ(net::OK
, client()->error().reason
);
440 EXPECT_EQ("", client()->error().domain
.utf8());
443 TEST_F(WebURLLoaderImplTest
, DataURLDeleteOnReceiveResponse
) {
444 blink::WebURLRequest request
;
445 request
.initialize();
446 request
.setURL(GURL("data:text/html;charset=utf-8,blah!"));
447 client()->set_delete_on_receive_response();
448 client()->loader()->loadAsynchronously(request
, client());
449 message_loop()->RunUntilIdle();
450 EXPECT_TRUE(client()->did_receive_response());
451 EXPECT_EQ("", client()->received_data());
452 EXPECT_FALSE(client()->did_finish());
455 TEST_F(WebURLLoaderImplTest
, DataURLDeleteOnReceiveData
) {
456 blink::WebURLRequest request
;
457 request
.initialize();
458 request
.setURL(GURL("data:text/html;charset=utf-8,blah!"));
459 client()->set_delete_on_receive_data();
460 client()->loader()->loadAsynchronously(request
, client());
461 message_loop()->RunUntilIdle();
462 EXPECT_TRUE(client()->did_receive_response());
463 EXPECT_EQ("blah!", client()->received_data());
464 EXPECT_FALSE(client()->did_finish());
467 TEST_F(WebURLLoaderImplTest
, DataURLDeleteOnFinish
) {
468 blink::WebURLRequest request
;
469 request
.initialize();
470 request
.setURL(GURL("data:text/html;charset=utf-8,blah!"));
471 client()->set_delete_on_finish();
472 client()->loader()->loadAsynchronously(request
, client());
473 message_loop()->RunUntilIdle();
474 EXPECT_TRUE(client()->did_receive_response());
475 EXPECT_EQ("blah!", client()->received_data());
476 EXPECT_TRUE(client()->did_finish());
479 TEST_F(WebURLLoaderImplTest
, DataURLDefersLoading
) {
480 blink::WebURLRequest request
;
481 request
.initialize();
482 request
.setURL(GURL("data:text/html;charset=utf-8,blah!"));
483 client()->loader()->loadAsynchronously(request
, client());
485 // setDefersLoading() might be called with either false or true in no
486 // specific order. The user of the API will not have sufficient information
487 // about the WebURLLoader's internal state, so the latter gracefully needs to
488 // handle calling setDefersLoading any number of times with any values from
489 // any point in time.
491 client()->loader()->setDefersLoading(false);
492 client()->loader()->setDefersLoading(true);
493 client()->loader()->setDefersLoading(true);
494 message_loop()->RunUntilIdle();
495 EXPECT_FALSE(client()->did_finish());
497 client()->loader()->setDefersLoading(false);
498 client()->loader()->setDefersLoading(true);
499 message_loop()->RunUntilIdle();
500 EXPECT_FALSE(client()->did_finish());
502 client()->loader()->setDefersLoading(false);
503 message_loop()->RunUntilIdle();
504 EXPECT_TRUE(client()->did_finish());
506 client()->loader()->setDefersLoading(true);
507 client()->loader()->setDefersLoading(false);
508 client()->loader()->setDefersLoading(false);
509 message_loop()->RunUntilIdle();
510 EXPECT_TRUE(client()->did_finish());
512 EXPECT_EQ("blah!", client()->received_data());
513 EXPECT_EQ(net::OK
, client()->error().reason
);
514 EXPECT_EQ("", client()->error().domain
.utf8());
517 // FTP integration tests. These are focused more on safe deletion than correct
518 // parsing of FTP responses.
520 TEST_F(WebURLLoaderImplTest
, Ftp
) {
521 DoStartAsyncRequest();
522 DoReceiveResponseFtp();
525 EXPECT_FALSE(dispatcher()->canceled());
528 TEST_F(WebURLLoaderImplTest
, FtpDeleteOnReceiveResponse
) {
529 client()->set_delete_on_receive_response();
530 DoStartAsyncRequest();
531 DoReceiveResponseFtp();
533 // No data should have been received.
534 EXPECT_EQ("", client()->received_data());
537 TEST_F(WebURLLoaderImplTest
, FtpDeleteOnReceiveFirstData
) {
538 client()->set_delete_on_receive_data();
539 DoStartAsyncRequest();
540 DoReceiveResponseFtp();
542 EXPECT_NE("", client()->received_data());
545 TEST_F(WebURLLoaderImplTest
, FtpDeleteOnReceiveMoreData
) {
546 DoStartAsyncRequest();
547 DoReceiveResponseFtp();
550 // Directory listings are only parsed once the request completes, so this will
551 // cancel in DoReceiveDataFtp, before the request finishes.
552 client()->set_delete_on_receive_data();
553 peer()->OnCompletedRequest(net::OK
, false, false, "", base::TimeTicks(),
555 EXPECT_FALSE(client()->did_finish());
558 TEST_F(WebURLLoaderImplTest
, FtpDeleteOnFinish
) {
559 client()->set_delete_on_finish();
560 DoStartAsyncRequest();
561 DoReceiveResponseFtp();
566 TEST_F(WebURLLoaderImplTest
, FtpDeleteOnFail
) {
567 client()->set_delete_on_fail();
568 DoStartAsyncRequest();
569 DoReceiveResponseFtp();
574 // Multipart integration tests. These are focused more on safe deletion than
575 // correct parsing of Multipart responses.
577 TEST_F(WebURLLoaderImplTest
, Multipart
) {
578 client()->set_expect_multipart_response();
579 DoStartAsyncRequest();
580 DoReceiveResponseMultipart();
581 DoReceiveDataMultipart();
583 EXPECT_EQ(kTestData
, client()->received_data());
584 EXPECT_FALSE(dispatcher()->canceled());
587 TEST_F(WebURLLoaderImplTest
, MultipartDeleteOnReceiveFirstResponse
) {
588 client()->set_expect_multipart_response();
589 client()->set_delete_on_receive_response();
590 DoStartAsyncRequest();
591 DoReceiveResponseMultipart();
592 EXPECT_EQ("", client()->received_data());
595 TEST_F(WebURLLoaderImplTest
, MultipartDeleteOnReceiveSecondResponse
) {
596 client()->set_expect_multipart_response();
597 DoStartAsyncRequest();
598 DoReceiveResponseMultipart();
599 client()->set_delete_on_receive_response();
600 DoReceiveDataMultipart();
601 EXPECT_EQ("", client()->received_data());
604 TEST_F(WebURLLoaderImplTest
, MultipartDeleteOnReceiveFirstData
) {
605 client()->set_expect_multipart_response();
606 client()->set_delete_on_receive_data();
607 DoStartAsyncRequest();
608 DoReceiveResponseMultipart();
609 DoReceiveDataMultipart();
610 EXPECT_EQ("bl", client()->received_data());
613 TEST_F(WebURLLoaderImplTest
, MultipartDeleteOnReceiveMoreData
) {
614 client()->set_expect_multipart_response();
615 DoStartAsyncRequest();
616 DoReceiveResponseMultipart();
617 DoReceiveDataMultipart();
618 // For multipart responses, the delegate may send some data when notified
619 // of a request completing.
620 client()->set_delete_on_receive_data();
621 peer()->OnCompletedRequest(net::OK
, false, false, "", base::TimeTicks(),
623 EXPECT_FALSE(client()->did_finish());
624 EXPECT_EQ(kTestData
, client()->received_data());
627 TEST_F(WebURLLoaderImplTest
, MultipartDeleteFinish
) {
628 client()->set_expect_multipart_response();
629 client()->set_delete_on_finish();
630 DoStartAsyncRequest();
631 DoReceiveResponseMultipart();
632 DoReceiveDataMultipart();
634 EXPECT_EQ(kTestData
, client()->received_data());
637 TEST_F(WebURLLoaderImplTest
, MultipartDeleteFail
) {
638 client()->set_expect_multipart_response();
639 client()->set_delete_on_fail();
640 DoStartAsyncRequest();
641 DoReceiveResponseMultipart();
642 DoReceiveDataMultipart();
646 // PlzNavigate: checks that the stream override parameters provided on
647 // navigation commit are properly applied.
648 TEST_F(WebURLLoaderImplTest
, BrowserSideNavigationCommit
) {
649 // Initialize the request and the stream override.
650 const GURL kStreamURL
= GURL("http://bar");
651 const std::string kMimeType
= "text/html";
652 blink::WebURLRequest request
;
653 request
.initialize();
654 request
.setURL(GURL(kTestURL
));
655 request
.setFrameType(blink::WebURLRequest::FrameTypeTopLevel
);
656 request
.setRequestContext(blink::WebURLRequest::RequestContextFrame
);
657 scoped_ptr
<StreamOverrideParameters
> stream_override(
658 new StreamOverrideParameters());
659 stream_override
->stream_url
= kStreamURL
;
660 stream_override
->response
.mime_type
= kMimeType
;
661 RequestExtraData
* extra_data
= new RequestExtraData();
662 extra_data
->set_stream_override(stream_override
.Pass());
663 request
.setExtraData(extra_data
);
664 base::CommandLine::ForCurrentProcess()->AppendSwitch(
665 switches::kEnableBrowserSideNavigation
);
667 client()->loader()->loadAsynchronously(request
, client());
669 // The stream url should have been requestead instead of the request url.
671 EXPECT_EQ(kStreamURL
, dispatcher()->url());
673 EXPECT_FALSE(client()->did_receive_response());
674 peer()->OnReceivedResponse(content::ResourceResponseInfo());
675 EXPECT_TRUE(client()->did_receive_response());
677 // The response info should have been overriden.
678 ASSERT_FALSE(client()->response().isNull());
679 EXPECT_EQ(kMimeType
, client()->response().mimeType().latin1());
683 EXPECT_FALSE(dispatcher()->canceled());
684 EXPECT_EQ(kTestData
, client()->received_data());
688 } // namespace content