[content shell] implement testRunner.overridePreference
[chromium-blink-merge.git] / content / browser / geolocation / network_location_provider_unittest.cc
blobf46f2ee47c6be373e547f79eff299679567f1c56
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 "base/json/json_reader.h"
6 #include "base/json/json_writer.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/string_number_conversions.h"
9 #include "base/string_util.h"
10 #include "base/stringprintf.h"
11 #include "base/utf_string_conversions.h"
12 #include "base/values.h"
13 #include "content/browser/geolocation/fake_access_token_store.h"
14 #include "content/browser/geolocation/location_arbitrator_impl.h"
15 #include "content/browser/geolocation/network_location_provider.h"
16 #include "net/url_request/test_url_fetcher_factory.h"
17 #include "net/url_request/url_request_status.h"
18 #include "testing/gtest/include/gtest/gtest.h"
20 namespace content {
22 // Constants used in multiple tests.
23 const char kTestServerUrl[] = "https://www.geolocation.test/service";
24 const char kAccessTokenString[] = "accessToken";
26 // Using #define so we can easily paste this into various other strings.
27 #define REFERENCE_ACCESS_TOKEN "2:k7j3G6LaL6u_lafw:4iXOeOpTh1glSXe"
29 // Stops the specified (nested) message loop when the listener is called back.
30 class MessageLoopQuitListener
31 : public LocationProviderBase::ListenerInterface {
32 public:
33 MessageLoopQuitListener()
34 : client_message_loop_(MessageLoop::current()),
35 updated_provider_(NULL),
36 movement_provider_(NULL) {
37 CHECK(client_message_loop_);
39 // ListenerInterface
40 virtual void LocationUpdateAvailable(LocationProviderBase* provider) {
41 EXPECT_EQ(client_message_loop_, MessageLoop::current());
42 updated_provider_ = provider;
43 client_message_loop_->Quit();
45 MessageLoop* client_message_loop_;
46 LocationProviderBase* updated_provider_;
47 LocationProviderBase* movement_provider_;
50 // A mock implementation of DeviceDataProviderImplBase for testing. Adapted from
51 // http://gears.googlecode.com/svn/trunk/gears/geolocation/geolocation_test.cc
52 template<typename DataType>
53 class MockDeviceDataProviderImpl
54 : public DeviceDataProviderImplBase<DataType> {
55 public:
56 // Factory method for use with DeviceDataProvider::SetFactory.
57 static DeviceDataProviderImplBase<DataType>* GetInstance() {
58 CHECK(instance_);
59 return instance_;
62 static MockDeviceDataProviderImpl<DataType>* CreateInstance() {
63 CHECK(!instance_);
64 instance_ = new MockDeviceDataProviderImpl<DataType>;
65 return instance_;
68 MockDeviceDataProviderImpl()
69 : start_calls_(0),
70 stop_calls_(0),
71 got_data_(true) {
74 virtual ~MockDeviceDataProviderImpl() {
75 CHECK(this == instance_);
76 instance_ = NULL;
79 // DeviceDataProviderImplBase implementation.
80 virtual bool StartDataProvider() {
81 ++start_calls_;
82 return true;
84 virtual void StopDataProvider() {
85 ++stop_calls_;
87 virtual bool GetData(DataType* data_out) {
88 CHECK(data_out);
89 *data_out = data_;
90 return got_data_;
93 void SetData(const DataType& new_data) {
94 got_data_ = true;
95 const bool differs = data_.DiffersSignificantly(new_data);
96 data_ = new_data;
97 if (differs)
98 this->NotifyListeners();
101 void set_got_data(bool got_data) { got_data_ = got_data; }
102 int start_calls_;
103 int stop_calls_;
105 private:
106 static MockDeviceDataProviderImpl<DataType>* instance_;
108 DataType data_;
109 bool got_data_;
111 DISALLOW_COPY_AND_ASSIGN(MockDeviceDataProviderImpl);
114 template<typename DataType>
115 MockDeviceDataProviderImpl<DataType>*
116 MockDeviceDataProviderImpl<DataType>::instance_ = NULL;
118 // Main test fixture
119 class GeolocationNetworkProviderTest : public testing::Test {
120 public:
121 virtual void SetUp() {
122 test_server_url_ = GURL(kTestServerUrl);
123 access_token_store_ = new FakeAccessTokenStore;
124 wifi_data_provider_ =
125 MockDeviceDataProviderImpl<WifiData>::CreateInstance();
128 virtual void TearDown() {
129 WifiDataProvider::ResetFactory();
132 LocationProviderBase* CreateProvider(bool set_permission_granted) {
133 LocationProviderBase* provider = NewNetworkLocationProvider(
134 access_token_store_.get(),
135 NULL, // No URLContextGetter needed, as using test urlfecther factory.
136 test_server_url_,
137 access_token_store_->access_token_set_[test_server_url_]);
138 if (set_permission_granted)
139 provider->OnPermissionGranted();
140 return provider;
143 protected:
144 GeolocationNetworkProviderTest() {
145 // TODO(joth): Really these should be in SetUp, not here, but they take no
146 // effect on Mac OS Release builds if done there. I kid not. Figure out why.
147 WifiDataProvider::SetFactory(
148 MockDeviceDataProviderImpl<WifiData>::GetInstance);
151 // Returns the current url fetcher (if any) and advances the id ready for the
152 // next test step.
153 net::TestURLFetcher* get_url_fetcher_and_advance_id() {
154 net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(
155 NetworkLocationRequest::url_fetcher_id_for_tests);
156 if (fetcher)
157 ++NetworkLocationRequest::url_fetcher_id_for_tests;
158 return fetcher;
161 static int IndexToChannel(int index) { return index + 4; }
163 // Creates wifi data containing the specified number of access points, with
164 // some differentiating charactistics in each.
165 static WifiData CreateReferenceWifiScanData(int ap_count) {
166 WifiData data;
167 for (int i = 0; i < ap_count; ++i) {
168 AccessPointData ap;
169 ap.mac_address =
170 ASCIIToUTF16(base::StringPrintf("%02d-34-56-78-54-32", i));
171 ap.radio_signal_strength = ap_count - i;
172 ap.channel = IndexToChannel(i);
173 ap.signal_to_noise = i + 42;
174 ap.ssid = ASCIIToUTF16("Some nice+network|name\\");
175 data.access_point_data.insert(ap);
177 return data;
180 static void CreateReferenceWifiScanDataJson(
181 int ap_count, int start_index, base::ListValue* wifi_access_point_list) {
182 std::vector<std::string> wifi_data;
183 for (int i = 0; i < ap_count; ++i) {
184 base::DictionaryValue* ap = new base::DictionaryValue();
185 ap->SetString("macAddress", base::StringPrintf("%02d-34-56-78-54-32", i));
186 ap->SetInteger("signalStrength", start_index + ap_count - i);
187 ap->SetInteger("age", 0);
188 ap->SetInteger("channel", IndexToChannel(i));
189 ap->SetInteger("signalToNoiseRatio", i + 42);
190 wifi_access_point_list->Append(ap);
194 static Geoposition CreateReferencePosition(int id) {
195 Geoposition pos;
196 pos.latitude = id;
197 pos.longitude = -(id + 1);
198 pos.altitude = 2 * id;
199 pos.timestamp = base::Time::Now();
200 return pos;
203 static std::string PrettyJson(const base::Value& value) {
204 std::string pretty;
205 base::JSONWriter::WriteWithOptions(
206 &value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty);
207 return pretty;
210 static testing::AssertionResult JsonGetList(
211 const std::string& field,
212 const base::DictionaryValue& dict,
213 const base::ListValue** output_list) {
214 if (!dict.GetList(field, output_list))
215 return testing::AssertionFailure() << "Dictionary " << PrettyJson(dict)
216 << " is missing list field " << field;
217 return testing::AssertionSuccess();
220 static testing::AssertionResult JsonFieldEquals(
221 const std::string& field,
222 const base::DictionaryValue& expected,
223 const base::DictionaryValue& actual) {
224 const base::Value* expected_value;
225 const base::Value* actual_value;
226 if (!expected.Get(field, &expected_value))
227 return testing::AssertionFailure()
228 << "Expected dictionary " << PrettyJson(expected)
229 << " is missing field " << field;
230 if (!expected.Get(field, &actual_value))
231 return testing::AssertionFailure()
232 << "Actual dictionary " << PrettyJson(actual)
233 << " is missing field " << field;
234 if (!expected_value->Equals(actual_value))
235 return testing::AssertionFailure()
236 << "Field " << field << " mismatch: " << PrettyJson(*expected_value)
237 << " != " << PrettyJson(*actual_value);
238 return testing::AssertionSuccess();
241 static GURL UrlWithoutQuery(const GURL& url) {
242 url_canon::Replacements<char> replacements;
243 replacements.ClearQuery();
244 return url.ReplaceComponents(replacements);
247 testing::AssertionResult IsTestServerUrl(const GURL& request_url) {
248 const GURL a(UrlWithoutQuery(test_server_url_));
249 const GURL b(UrlWithoutQuery(request_url));
250 if (a == b)
251 return testing::AssertionSuccess();
252 return testing::AssertionFailure() << a << " != " << b;
255 void CheckRequestIsValid(const net::TestURLFetcher& request,
256 int expected_routers,
257 int expected_wifi_aps,
258 int wifi_start_index,
259 const std::string& expected_access_token) {
260 const GURL& request_url = request.GetOriginalURL();
262 EXPECT_TRUE(IsTestServerUrl(request_url));
264 // Check to see that the api key is being appended for the default
265 // network provider url.
266 bool is_default_url = UrlWithoutQuery(request_url) ==
267 UrlWithoutQuery(GeolocationArbitratorImpl::DefaultNetworkProviderURL());
268 EXPECT_EQ(is_default_url, !request_url.query().empty());
270 const std::string& upload_data = request.upload_data();
271 ASSERT_FALSE(upload_data.empty());
272 std::string json_parse_error_msg;
273 scoped_ptr<base::Value> parsed_json(
274 base::JSONReader::ReadAndReturnError(
275 upload_data,
276 base::JSON_PARSE_RFC,
277 NULL,
278 &json_parse_error_msg));
279 EXPECT_TRUE(json_parse_error_msg.empty());
280 ASSERT_TRUE(parsed_json.get() != NULL);
282 const base::DictionaryValue* request_json;
283 ASSERT_TRUE(parsed_json->GetAsDictionary(&request_json));
285 if (!is_default_url) {
286 if (expected_access_token.empty())
287 ASSERT_FALSE(request_json->HasKey(kAccessTokenString));
288 else {
289 std::string access_token;
290 EXPECT_TRUE(request_json->GetString(kAccessTokenString, &access_token));
291 EXPECT_EQ(expected_access_token, access_token);
295 if (expected_wifi_aps) {
296 base::ListValue expected_wifi_aps_json;
297 CreateReferenceWifiScanDataJson(
298 expected_wifi_aps,
299 wifi_start_index,
300 &expected_wifi_aps_json);
301 EXPECT_EQ(size_t(expected_wifi_aps), expected_wifi_aps_json.GetSize());
303 const base::ListValue* wifi_aps_json;
304 ASSERT_TRUE(JsonGetList("wifiAccessPoints", *request_json,
305 &wifi_aps_json));
306 for (size_t i = 0; i < expected_wifi_aps_json.GetSize(); ++i ) {
307 const base::DictionaryValue* expected_json;
308 ASSERT_TRUE(expected_wifi_aps_json.GetDictionary(i, &expected_json));
309 const base::DictionaryValue* actual_json;
310 ASSERT_TRUE(wifi_aps_json->GetDictionary(i, &actual_json));
311 ASSERT_TRUE(JsonFieldEquals("macAddress", *expected_json,
312 *actual_json));
313 ASSERT_TRUE(JsonFieldEquals("signalStrength", *expected_json,
314 *actual_json));
315 ASSERT_TRUE(JsonFieldEquals("channel", *expected_json, *actual_json));
316 ASSERT_TRUE(JsonFieldEquals("signalToNoiseRatio", *expected_json,
317 *actual_json));
319 } else {
320 ASSERT_FALSE(request_json->HasKey("wifiAccessPoints"));
322 EXPECT_TRUE(request_url.is_valid());
325 GURL test_server_url_;
326 MessageLoop main_message_loop_;
327 scoped_refptr<FakeAccessTokenStore> access_token_store_;
328 net::TestURLFetcherFactory url_fetcher_factory_;
329 scoped_refptr<MockDeviceDataProviderImpl<WifiData> > wifi_data_provider_;
332 TEST_F(GeolocationNetworkProviderTest, CreateDestroy) {
333 // Test fixture members were SetUp correctly.
334 EXPECT_EQ(&main_message_loop_, MessageLoop::current());
335 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
336 EXPECT_TRUE(NULL != provider.get());
337 provider.reset();
338 SUCCEED();
341 TEST_F(GeolocationNetworkProviderTest, StartProvider) {
342 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
343 EXPECT_TRUE(provider->StartProvider(false));
344 net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
345 ASSERT_TRUE(fetcher != NULL);
346 CheckRequestIsValid(*fetcher, 0, 0, 0, "");
349 TEST_F(GeolocationNetworkProviderTest, StartProviderDefaultUrl) {
350 test_server_url_ = GeolocationArbitratorImpl::DefaultNetworkProviderURL();
351 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
352 EXPECT_TRUE(provider->StartProvider(false));
353 net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
354 ASSERT_TRUE(fetcher != NULL);
355 CheckRequestIsValid(*fetcher, 0, 0, 0, "");
359 TEST_F(GeolocationNetworkProviderTest, StartProviderLongRequest) {
360 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
361 EXPECT_TRUE(provider->StartProvider(false));
362 const int kFirstScanAps = 20;
363 wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
364 main_message_loop_.RunUntilIdle();
365 net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
366 ASSERT_TRUE(fetcher != NULL);
367 // The request url should have been shortened to less than 2048 characters
368 // in length by not including access points with the lowest signal strength
369 // in the request.
370 EXPECT_LT(fetcher->GetOriginalURL().spec().size(), size_t(2048));
371 CheckRequestIsValid(*fetcher, 0, 16, 4, "");
374 TEST_F(GeolocationNetworkProviderTest, MultiRegistrations) {
375 // TODO(joth): Strictly belongs in a base-class unit test file.
376 MessageLoopQuitListener listener;
377 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
378 EXPECT_FALSE(provider->has_listeners());
379 provider->RegisterListener(&listener);
380 EXPECT_TRUE(provider->has_listeners());
381 provider->RegisterListener(&listener);
382 EXPECT_TRUE(provider->has_listeners());
384 provider->UnregisterListener(&listener);
385 EXPECT_TRUE(provider->has_listeners());
386 provider->UnregisterListener(&listener);
387 EXPECT_FALSE(provider->has_listeners());
390 TEST_F(GeolocationNetworkProviderTest, MultipleWifiScansComplete) {
391 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
392 EXPECT_TRUE(provider->StartProvider(false));
394 net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
395 ASSERT_TRUE(fetcher != NULL);
396 EXPECT_TRUE(IsTestServerUrl(fetcher->GetOriginalURL()));
398 // Complete the network request with bad position fix.
399 const char* kNoFixNetworkResponse =
401 " \"status\": \"ZERO_RESULTS\""
402 "}";
403 fetcher->set_url(test_server_url_);
404 fetcher->set_status(net::URLRequestStatus());
405 fetcher->set_response_code(200); // OK
406 fetcher->SetResponseString(kNoFixNetworkResponse);
407 fetcher->delegate()->OnURLFetchComplete(fetcher);
409 Geoposition position;
410 provider->GetPosition(&position);
411 EXPECT_FALSE(position.Validate());
413 // Now wifi data arrives -- SetData will notify listeners.
414 const int kFirstScanAps = 6;
415 wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
416 main_message_loop_.RunUntilIdle();
417 fetcher = get_url_fetcher_and_advance_id();
418 ASSERT_TRUE(fetcher != NULL);
419 // The request should have the wifi data.
420 CheckRequestIsValid(*fetcher, 0, kFirstScanAps, 0, "");
422 // Send a reply with good position fix.
423 const char* kReferenceNetworkResponse =
425 " \"accessToken\": \"" REFERENCE_ACCESS_TOKEN "\","
426 " \"accuracy\": 1200.4,"
427 " \"location\": {"
428 " \"lat\": 51.0,"
429 " \"lng\": -0.1"
430 " }"
431 "}";
432 fetcher->set_url(test_server_url_);
433 fetcher->set_status(net::URLRequestStatus());
434 fetcher->set_response_code(200); // OK
435 fetcher->SetResponseString(kReferenceNetworkResponse);
436 fetcher->delegate()->OnURLFetchComplete(fetcher);
438 provider->GetPosition(&position);
439 EXPECT_EQ(51.0, position.latitude);
440 EXPECT_EQ(-0.1, position.longitude);
441 EXPECT_EQ(1200.4, position.accuracy);
442 EXPECT_FALSE(position.timestamp.is_null());
443 EXPECT_TRUE(position.Validate());
445 // Token should be in the store.
446 EXPECT_EQ(UTF8ToUTF16(REFERENCE_ACCESS_TOKEN),
447 access_token_store_->access_token_set_[test_server_url_]);
449 // Wifi updated again, with one less AP. This is 'close enough' to the
450 // previous scan, so no new request made.
451 const int kSecondScanAps = kFirstScanAps - 1;
452 wifi_data_provider_->SetData(CreateReferenceWifiScanData(kSecondScanAps));
453 main_message_loop_.RunUntilIdle();
454 fetcher = get_url_fetcher_and_advance_id();
455 EXPECT_FALSE(fetcher);
457 provider->GetPosition(&position);
458 EXPECT_EQ(51.0, position.latitude);
459 EXPECT_EQ(-0.1, position.longitude);
460 EXPECT_TRUE(position.Validate());
462 // Now a third scan with more than twice the original amount -> new request.
463 const int kThirdScanAps = kFirstScanAps * 2 + 1;
464 wifi_data_provider_->SetData(CreateReferenceWifiScanData(kThirdScanAps));
465 main_message_loop_.RunUntilIdle();
466 fetcher = get_url_fetcher_and_advance_id();
467 EXPECT_TRUE(fetcher);
468 CheckRequestIsValid(*fetcher, 0, kThirdScanAps, 0, REFERENCE_ACCESS_TOKEN);
469 // ...reply with a network error.
471 fetcher->set_url(test_server_url_);
472 fetcher->set_status(net::URLRequestStatus(net::URLRequestStatus::FAILED, -1));
473 fetcher->set_response_code(200); // should be ignored
474 fetcher->SetResponseString("");
475 fetcher->delegate()->OnURLFetchComplete(fetcher);
477 // Error means we now no longer have a fix.
478 provider->GetPosition(&position);
479 EXPECT_FALSE(position.Validate());
481 // Wifi scan returns to original set: should be serviced from cache.
482 wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
483 main_message_loop_.RunUntilIdle();
484 EXPECT_FALSE(get_url_fetcher_and_advance_id()); // No new request created.
486 provider->GetPosition(&position);
487 EXPECT_EQ(51.0, position.latitude);
488 EXPECT_EQ(-0.1, position.longitude);
489 EXPECT_TRUE(position.Validate());
492 TEST_F(GeolocationNetworkProviderTest, NoRequestOnStartupUntilWifiData) {
493 MessageLoopQuitListener listener;
494 wifi_data_provider_->set_got_data(false);
495 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
496 EXPECT_TRUE(provider->StartProvider(false));
497 provider->RegisterListener(&listener);
499 main_message_loop_.RunUntilIdle();
500 EXPECT_FALSE(get_url_fetcher_and_advance_id())
501 << "Network request should not be created right away on startup when "
502 "wifi data has not yet arrived";
504 wifi_data_provider_->SetData(CreateReferenceWifiScanData(1));
505 main_message_loop_.RunUntilIdle();
506 EXPECT_TRUE(get_url_fetcher_and_advance_id());
509 TEST_F(GeolocationNetworkProviderTest, NewDataReplacesExistingNetworkRequest) {
510 // Send initial request with empty device data
511 scoped_ptr<LocationProviderBase> provider(CreateProvider(true));
512 EXPECT_TRUE(provider->StartProvider(false));
513 net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
514 EXPECT_TRUE(fetcher);
516 // Now wifi data arrives; new request should be sent.
517 wifi_data_provider_->SetData(CreateReferenceWifiScanData(4));
518 main_message_loop_.RunUntilIdle();
519 fetcher = get_url_fetcher_and_advance_id();
520 EXPECT_TRUE(fetcher);
523 TEST_F(GeolocationNetworkProviderTest, NetworkRequestDeferredForPermission) {
524 scoped_ptr<LocationProviderBase> provider(CreateProvider(false));
525 EXPECT_TRUE(provider->StartProvider(false));
526 net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
527 EXPECT_FALSE(fetcher);
528 provider->OnPermissionGranted();
530 fetcher = get_url_fetcher_and_advance_id();
531 ASSERT_TRUE(fetcher != NULL);
533 EXPECT_TRUE(IsTestServerUrl(fetcher->GetOriginalURL()));
536 TEST_F(GeolocationNetworkProviderTest,
537 NetworkRequestWithWifiDataDeferredForPermission) {
538 access_token_store_->access_token_set_[test_server_url_] =
539 UTF8ToUTF16(REFERENCE_ACCESS_TOKEN);
540 scoped_ptr<LocationProviderBase> provider(CreateProvider(false));
541 EXPECT_TRUE(provider->StartProvider(false));
542 net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
543 EXPECT_FALSE(fetcher);
545 static const int kScanCount = 4;
546 wifi_data_provider_->SetData(CreateReferenceWifiScanData(kScanCount));
547 main_message_loop_.RunUntilIdle();
549 fetcher = get_url_fetcher_and_advance_id();
550 EXPECT_FALSE(fetcher);
552 provider->OnPermissionGranted();
554 fetcher = get_url_fetcher_and_advance_id();
555 ASSERT_TRUE(fetcher != NULL);
557 CheckRequestIsValid(*fetcher, 0, kScanCount, 0, REFERENCE_ACCESS_TOKEN);
560 TEST_F(GeolocationNetworkProviderTest, NetworkPositionCache) {
561 NetworkLocationProvider::PositionCache cache;
563 const int kCacheSize = NetworkLocationProvider::PositionCache::kMaximumSize;
564 for (int i = 1; i < kCacheSize * 2 + 1; ++i) {
565 Geoposition pos = CreateReferencePosition(i);
566 bool ret = cache.CachePosition(CreateReferenceWifiScanData(i), pos);
567 EXPECT_TRUE(ret) << i;
568 const Geoposition* item =
569 cache.FindPosition(CreateReferenceWifiScanData(i));
570 ASSERT_TRUE(item) << i;
571 EXPECT_EQ(pos.latitude, item->latitude) << i;
572 EXPECT_EQ(pos.longitude, item->longitude) << i;
573 if (i <= kCacheSize) {
574 // Nothing should have spilled yet; check oldest item is still there.
575 EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(1)));
576 } else {
577 const int evicted = i - kCacheSize;
578 EXPECT_FALSE(cache.FindPosition(CreateReferenceWifiScanData(evicted)));
579 EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(evicted + 1)));
584 } // namespace content