OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/service_worker/service_worker_storage.h" | 5 #include "content/browser/service_worker/service_worker_storage.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 | 8 |
9 #include "base/bind_helpers.h" | 9 #include "base/bind_helpers.h" |
10 #include "base/debug/trace_event.h" | 10 #include "base/debug/trace_event.h" |
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
472 data.resources_total_size_bytes = resources_total_size_bytes; | 472 data.resources_total_size_bytes = resources_total_size_bytes; |
473 | 473 |
474 if (!has_checked_for_stale_resources_) | 474 if (!has_checked_for_stale_resources_) |
475 DeleteStaleResources(); | 475 DeleteStaleResources(); |
476 | 476 |
477 database_task_manager_->GetTaskRunner()->PostTask( | 477 database_task_manager_->GetTaskRunner()->PostTask( |
478 FROM_HERE, | 478 FROM_HERE, |
479 base::Bind(&WriteRegistrationInDB, | 479 base::Bind(&WriteRegistrationInDB, |
480 database_.get(), | 480 database_.get(), |
481 base::MessageLoopProxy::current(), | 481 base::MessageLoopProxy::current(), |
482 data, resources, | 482 data, |
| 483 resources, |
483 base::Bind(&ServiceWorkerStorage::DidStoreRegistration, | 484 base::Bind(&ServiceWorkerStorage::DidStoreRegistration, |
484 weak_factory_.GetWeakPtr(), | 485 weak_factory_.GetWeakPtr(), |
485 callback))); | 486 callback, |
| 487 data))); |
486 | 488 |
487 registration->set_is_deleted(false); | 489 registration->set_is_deleted(false); |
488 | |
489 // TODO(dmurph): Add correct byte delta. | |
490 if (quota_manager_proxy_.get()) { | |
491 // Can be nullptr in tests. | |
492 quota_manager_proxy_->NotifyStorageModified( | |
493 storage::QuotaClient::kServiceWorker, | |
494 registration->pattern().GetOrigin(), | |
495 storage::StorageType::kStorageTypeTemporary, | |
496 0); | |
497 } | |
498 } | 490 } |
499 | 491 |
500 void ServiceWorkerStorage::UpdateToActiveState( | 492 void ServiceWorkerStorage::UpdateToActiveState( |
501 ServiceWorkerRegistration* registration, | 493 ServiceWorkerRegistration* registration, |
502 const StatusCallback& callback) { | 494 const StatusCallback& callback) { |
503 DCHECK(registration); | 495 DCHECK(registration); |
504 | 496 |
505 DCHECK(state_ == INITIALIZED || state_ == DISABLED) << state_; | 497 DCHECK(state_ == INITIALIZED || state_ == DISABLED) << state_; |
506 if (IsDisabled() || !context_) { | 498 if (IsDisabled() || !context_) { |
507 RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED)); | 499 RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED)); |
(...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
920 if (status != ServiceWorkerDatabase::STATUS_OK && | 912 if (status != ServiceWorkerDatabase::STATUS_OK && |
921 status != ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) { | 913 status != ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) { |
922 ScheduleDeleteAndStartOver(); | 914 ScheduleDeleteAndStartOver(); |
923 callback.Run(std::vector<ServiceWorkerRegistrationInfo>()); | 915 callback.Run(std::vector<ServiceWorkerRegistrationInfo>()); |
924 return; | 916 return; |
925 } | 917 } |
926 | 918 |
927 // Add all stored registrations. | 919 // Add all stored registrations. |
928 std::set<int64> pushed_registrations; | 920 std::set<int64> pushed_registrations; |
929 std::vector<ServiceWorkerRegistrationInfo> infos; | 921 std::vector<ServiceWorkerRegistrationInfo> infos; |
930 for (RegistrationList::const_iterator it = registrations->begin(); | 922 for (const auto& registration_data : *registrations) { |
931 it != registrations->end(); ++it) { | |
932 const bool inserted = | 923 const bool inserted = |
933 pushed_registrations.insert(it->registration_id).second; | 924 pushed_registrations.insert(registration_data.registration_id).second; |
934 DCHECK(inserted); | 925 DCHECK(inserted); |
935 | 926 |
936 ServiceWorkerRegistration* registration = | 927 ServiceWorkerRegistration* registration = |
937 context_->GetLiveRegistration(it->registration_id); | 928 context_->GetLiveRegistration(registration_data.registration_id); |
938 if (registration) { | 929 if (registration) { |
939 infos.push_back(registration->GetInfo()); | 930 infos.push_back(registration->GetInfo()); |
940 continue; | 931 continue; |
941 } | 932 } |
942 | 933 |
943 ServiceWorkerRegistrationInfo info; | 934 ServiceWorkerRegistrationInfo info; |
944 info.pattern = it->scope; | 935 info.pattern = registration_data.scope; |
945 info.registration_id = it->registration_id; | 936 info.registration_id = registration_data.registration_id; |
| 937 info.stored_version_size_bytes = |
| 938 registration_data.resources_total_size_bytes; |
946 if (ServiceWorkerVersion* version = | 939 if (ServiceWorkerVersion* version = |
947 context_->GetLiveVersion(it->version_id)) { | 940 context_->GetLiveVersion(registration_data.version_id)) { |
948 if (it->is_active) | 941 if (registration_data.is_active) |
949 info.active_version = version->GetInfo(); | 942 info.active_version = version->GetInfo(); |
950 else | 943 else |
951 info.waiting_version = version->GetInfo(); | 944 info.waiting_version = version->GetInfo(); |
952 infos.push_back(info); | 945 infos.push_back(info); |
953 continue; | 946 continue; |
954 } | 947 } |
955 | 948 |
956 if (it->is_active) { | 949 if (registration_data.is_active) { |
957 info.active_version.status = ServiceWorkerVersion::ACTIVATED; | 950 info.active_version.status = ServiceWorkerVersion::ACTIVATED; |
958 info.active_version.version_id = it->version_id; | 951 info.active_version.version_id = registration_data.version_id; |
959 } else { | 952 } else { |
960 info.waiting_version.status = ServiceWorkerVersion::INSTALLED; | 953 info.waiting_version.status = ServiceWorkerVersion::INSTALLED; |
961 info.waiting_version.version_id = it->version_id; | 954 info.waiting_version.version_id = registration_data.version_id; |
962 } | 955 } |
963 infos.push_back(info); | 956 infos.push_back(info); |
964 } | 957 } |
965 | 958 |
966 // Add unstored registrations that are being installed. | 959 // Add unstored registrations that are being installed. |
967 for (RegistrationRefsById::const_iterator it = | 960 for (RegistrationRefsById::const_iterator it = |
968 installing_registrations_.begin(); | 961 installing_registrations_.begin(); |
969 it != installing_registrations_.end(); ++it) { | 962 it != installing_registrations_.end(); ++it) { |
970 if (pushed_registrations.insert(it->first).second) | 963 if (pushed_registrations.insert(it->first).second) |
971 infos.push_back(it->second->GetInfo()); | 964 infos.push_back(it->second->GetInfo()); |
972 } | 965 } |
973 | 966 |
974 callback.Run(infos); | 967 callback.Run(infos); |
975 } | 968 } |
976 | 969 |
977 void ServiceWorkerStorage::DidStoreRegistration( | 970 void ServiceWorkerStorage::DidStoreRegistration( |
978 const StatusCallback& callback, | 971 const StatusCallback& callback, |
| 972 const ServiceWorkerDatabase::RegistrationData& new_version, |
979 const GURL& origin, | 973 const GURL& origin, |
980 int64 deleted_version_id, | 974 const ServiceWorkerDatabase::RegistrationData& deleted_version, |
981 const std::vector<int64>& newly_purgeable_resources, | 975 const std::vector<int64>& newly_purgeable_resources, |
982 ServiceWorkerDatabase::Status status) { | 976 ServiceWorkerDatabase::Status status) { |
983 if (status != ServiceWorkerDatabase::STATUS_OK) { | 977 if (status != ServiceWorkerDatabase::STATUS_OK) { |
984 ScheduleDeleteAndStartOver(); | 978 ScheduleDeleteAndStartOver(); |
985 callback.Run(DatabaseStatusToStatusCode(status)); | 979 callback.Run(DatabaseStatusToStatusCode(status)); |
986 return; | 980 return; |
987 } | 981 } |
988 registered_origins_.insert(origin); | 982 registered_origins_.insert(origin); |
| 983 |
| 984 scoped_refptr<ServiceWorkerRegistration> registration = |
| 985 context_->GetLiveRegistration(new_version.registration_id); |
| 986 registration->set_resources_total_size_bytes( |
| 987 new_version.resources_total_size_bytes); |
| 988 if (quota_manager_proxy_.get()) { |
| 989 // Can be nullptr in tests. |
| 990 quota_manager_proxy_->NotifyStorageModified( |
| 991 storage::QuotaClient::kServiceWorker, |
| 992 origin, |
| 993 storage::StorageType::kStorageTypeTemporary, |
| 994 new_version.resources_total_size_bytes - |
| 995 deleted_version.resources_total_size_bytes); |
| 996 } |
| 997 |
989 callback.Run(SERVICE_WORKER_OK); | 998 callback.Run(SERVICE_WORKER_OK); |
990 | 999 |
991 if (!context_ || !context_->GetLiveVersion(deleted_version_id)) | 1000 if (!context_ || !context_->GetLiveVersion(deleted_version.version_id)) |
992 StartPurgingResources(newly_purgeable_resources); | 1001 StartPurgingResources(newly_purgeable_resources); |
993 } | 1002 } |
994 | 1003 |
995 void ServiceWorkerStorage::DidUpdateToActiveState( | 1004 void ServiceWorkerStorage::DidUpdateToActiveState( |
996 const StatusCallback& callback, | 1005 const StatusCallback& callback, |
997 ServiceWorkerDatabase::Status status) { | 1006 ServiceWorkerDatabase::Status status) { |
998 if (status != ServiceWorkerDatabase::STATUS_OK && | 1007 if (status != ServiceWorkerDatabase::STATUS_OK && |
999 status != ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) { | 1008 status != ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) { |
1000 ScheduleDeleteAndStartOver(); | 1009 ScheduleDeleteAndStartOver(); |
1001 } | 1010 } |
1002 callback.Run(DatabaseStatusToStatusCode(status)); | 1011 callback.Run(DatabaseStatusToStatusCode(status)); |
1003 } | 1012 } |
1004 | 1013 |
1005 void ServiceWorkerStorage::DidDeleteRegistration( | 1014 void ServiceWorkerStorage::DidDeleteRegistration( |
1006 const DidDeleteRegistrationParams& params, | 1015 const DidDeleteRegistrationParams& params, |
1007 bool origin_is_deletable, | 1016 bool origin_is_deletable, |
1008 int64 version_id, | 1017 const ServiceWorkerDatabase::RegistrationData& deleted_version, |
1009 const std::vector<int64>& newly_purgeable_resources, | 1018 const std::vector<int64>& newly_purgeable_resources, |
1010 ServiceWorkerDatabase::Status status) { | 1019 ServiceWorkerDatabase::Status status) { |
1011 pending_deletions_.erase(params.registration_id); | 1020 pending_deletions_.erase(params.registration_id); |
1012 if (status != ServiceWorkerDatabase::STATUS_OK) { | 1021 if (status != ServiceWorkerDatabase::STATUS_OK) { |
1013 ScheduleDeleteAndStartOver(); | 1022 ScheduleDeleteAndStartOver(); |
1014 params.callback.Run(DatabaseStatusToStatusCode(status)); | 1023 params.callback.Run(DatabaseStatusToStatusCode(status)); |
1015 return; | 1024 return; |
1016 } | 1025 } |
| 1026 if (quota_manager_proxy_.get()) { |
| 1027 // Can be nullptr in tests. |
| 1028 quota_manager_proxy_->NotifyStorageModified( |
| 1029 storage::QuotaClient::kServiceWorker, |
| 1030 params.origin, |
| 1031 storage::StorageType::kStorageTypeTemporary, |
| 1032 -deleted_version.resources_total_size_bytes); |
| 1033 } |
1017 if (origin_is_deletable) | 1034 if (origin_is_deletable) |
1018 registered_origins_.erase(params.origin); | 1035 registered_origins_.erase(params.origin); |
1019 params.callback.Run(SERVICE_WORKER_OK); | 1036 params.callback.Run(SERVICE_WORKER_OK); |
1020 | 1037 |
1021 if (!context_ || !context_->GetLiveVersion(version_id)) | 1038 if (!context_ || !context_->GetLiveVersion(deleted_version.version_id)) |
1022 StartPurgingResources(newly_purgeable_resources); | 1039 StartPurgingResources(newly_purgeable_resources); |
1023 } | 1040 } |
1024 | 1041 |
1025 scoped_refptr<ServiceWorkerRegistration> | 1042 scoped_refptr<ServiceWorkerRegistration> |
1026 ServiceWorkerStorage::GetOrCreateRegistration( | 1043 ServiceWorkerStorage::GetOrCreateRegistration( |
1027 const ServiceWorkerDatabase::RegistrationData& data, | 1044 const ServiceWorkerDatabase::RegistrationData& data, |
1028 const ResourceList& resources) { | 1045 const ResourceList& resources) { |
1029 scoped_refptr<ServiceWorkerRegistration> registration = | 1046 scoped_refptr<ServiceWorkerRegistration> registration = |
1030 context_->GetLiveRegistration(data.registration_id); | 1047 context_->GetLiveRegistration(data.registration_id); |
1031 if (registration.get()) | 1048 if (registration.get()) |
1032 return registration; | 1049 return registration; |
1033 | 1050 |
1034 registration = new ServiceWorkerRegistration( | 1051 registration = new ServiceWorkerRegistration( |
1035 data.scope, data.registration_id, context_); | 1052 data.scope, data.registration_id, context_); |
| 1053 registration->set_resources_total_size_bytes(data.resources_total_size_bytes); |
1036 registration->set_last_update_check(data.last_update_check); | 1054 registration->set_last_update_check(data.last_update_check); |
1037 if (pending_deletions_.find(data.registration_id) != | 1055 if (pending_deletions_.find(data.registration_id) != |
1038 pending_deletions_.end()) { | 1056 pending_deletions_.end()) { |
1039 registration->set_is_deleted(true); | 1057 registration->set_is_deleted(true); |
1040 } | 1058 } |
1041 scoped_refptr<ServiceWorkerVersion> version = | 1059 scoped_refptr<ServiceWorkerVersion> version = |
1042 context_->GetLiveVersion(data.version_id); | 1060 context_->GetLiveVersion(data.version_id); |
1043 if (!version.get()) { | 1061 if (!version.get()) { |
1044 version = new ServiceWorkerVersion( | 1062 version = new ServiceWorkerVersion( |
1045 registration.get(), data.script, data.version_id, context_); | 1063 registration.get(), data.script, data.version_id, context_); |
(...skipping 239 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1285 } | 1303 } |
1286 | 1304 |
1287 void ServiceWorkerStorage::DeleteRegistrationFromDB( | 1305 void ServiceWorkerStorage::DeleteRegistrationFromDB( |
1288 ServiceWorkerDatabase* database, | 1306 ServiceWorkerDatabase* database, |
1289 scoped_refptr<base::SequencedTaskRunner> original_task_runner, | 1307 scoped_refptr<base::SequencedTaskRunner> original_task_runner, |
1290 int64 registration_id, | 1308 int64 registration_id, |
1291 const GURL& origin, | 1309 const GURL& origin, |
1292 const DeleteRegistrationCallback& callback) { | 1310 const DeleteRegistrationCallback& callback) { |
1293 DCHECK(database); | 1311 DCHECK(database); |
1294 | 1312 |
1295 int64 version_id = kInvalidServiceWorkerVersionId; | 1313 ServiceWorkerDatabase::RegistrationData deleted_version; |
1296 std::vector<int64> newly_purgeable_resources; | 1314 std::vector<int64> newly_purgeable_resources; |
1297 ServiceWorkerDatabase::Status status = database->DeleteRegistration( | 1315 ServiceWorkerDatabase::Status status = database->DeleteRegistration( |
1298 registration_id, origin, &version_id, &newly_purgeable_resources); | 1316 registration_id, origin, &deleted_version, &newly_purgeable_resources); |
1299 if (status != ServiceWorkerDatabase::STATUS_OK) { | 1317 if (status != ServiceWorkerDatabase::STATUS_OK) { |
1300 original_task_runner->PostTask(FROM_HERE, | 1318 original_task_runner->PostTask( |
1301 base::Bind(callback, | 1319 FROM_HERE, |
1302 false, | 1320 base::Bind( |
1303 kInvalidServiceWorkerVersionId, | 1321 callback, false, deleted_version, std::vector<int64>(), status)); |
1304 std::vector<int64>(), | |
1305 status)); | |
1306 return; | 1322 return; |
1307 } | 1323 } |
1308 | 1324 |
1309 // TODO(nhiroki): Add convenient method to ServiceWorkerDatabase to check the | 1325 // TODO(nhiroki): Add convenient method to ServiceWorkerDatabase to check the |
1310 // unique origin list. | 1326 // unique origin list. |
1311 std::vector<ServiceWorkerDatabase::RegistrationData> registrations; | 1327 std::vector<ServiceWorkerDatabase::RegistrationData> registrations; |
1312 status = database->GetRegistrationsForOrigin(origin, ®istrations); | 1328 status = database->GetRegistrationsForOrigin(origin, ®istrations); |
1313 if (status != ServiceWorkerDatabase::STATUS_OK) { | 1329 if (status != ServiceWorkerDatabase::STATUS_OK) { |
1314 original_task_runner->PostTask(FROM_HERE, | 1330 original_task_runner->PostTask( |
1315 base::Bind(callback, | 1331 FROM_HERE, |
1316 false, | 1332 base::Bind( |
1317 kInvalidServiceWorkerVersionId, | 1333 callback, false, deleted_version, std::vector<int64>(), status)); |
1318 std::vector<int64>(), | |
1319 status)); | |
1320 return; | 1334 return; |
1321 } | 1335 } |
1322 | 1336 |
1323 bool deletable = registrations.empty(); | 1337 bool deletable = registrations.empty(); |
1324 original_task_runner->PostTask( | 1338 original_task_runner->PostTask(FROM_HERE, |
1325 FROM_HERE, | 1339 base::Bind(callback, |
1326 base::Bind( | 1340 deletable, |
1327 callback, deletable, version_id, newly_purgeable_resources, status)); | 1341 deleted_version, |
| 1342 newly_purgeable_resources, |
| 1343 status)); |
1328 } | 1344 } |
1329 | 1345 |
1330 void ServiceWorkerStorage::WriteRegistrationInDB( | 1346 void ServiceWorkerStorage::WriteRegistrationInDB( |
1331 ServiceWorkerDatabase* database, | 1347 ServiceWorkerDatabase* database, |
1332 scoped_refptr<base::SequencedTaskRunner> original_task_runner, | 1348 scoped_refptr<base::SequencedTaskRunner> original_task_runner, |
1333 const ServiceWorkerDatabase::RegistrationData& data, | 1349 const ServiceWorkerDatabase::RegistrationData& data, |
1334 const ResourceList& resources, | 1350 const ResourceList& resources, |
1335 const WriteRegistrationCallback& callback) { | 1351 const WriteRegistrationCallback& callback) { |
1336 DCHECK(database); | 1352 DCHECK(database); |
1337 int64 deleted_version_id = kInvalidServiceWorkerVersionId; | 1353 ServiceWorkerDatabase::RegistrationData deleted_version; |
1338 std::vector<int64> newly_purgeable_resources; | 1354 std::vector<int64> newly_purgeable_resources; |
1339 ServiceWorkerDatabase::Status status = database->WriteRegistration( | 1355 ServiceWorkerDatabase::Status status = database->WriteRegistration( |
1340 data, resources, &deleted_version_id, &newly_purgeable_resources); | 1356 data, resources, &deleted_version, &newly_purgeable_resources); |
1341 original_task_runner->PostTask(FROM_HERE, | 1357 original_task_runner->PostTask(FROM_HERE, |
1342 base::Bind(callback, | 1358 base::Bind(callback, |
1343 data.script.GetOrigin(), | 1359 data.script.GetOrigin(), |
1344 deleted_version_id, | 1360 deleted_version, |
1345 newly_purgeable_resources, | 1361 newly_purgeable_resources, |
1346 status)); | 1362 status)); |
1347 } | 1363 } |
1348 | 1364 |
1349 void ServiceWorkerStorage::FindForDocumentInDB( | 1365 void ServiceWorkerStorage::FindForDocumentInDB( |
1350 ServiceWorkerDatabase* database, | 1366 ServiceWorkerDatabase* database, |
1351 scoped_refptr<base::SequencedTaskRunner> original_task_runner, | 1367 scoped_refptr<base::SequencedTaskRunner> original_task_runner, |
1352 const GURL& document_url, | 1368 const GURL& document_url, |
1353 const FindInDBCallback& callback) { | 1369 const FindInDBCallback& callback) { |
1354 GURL origin = document_url.GetOrigin(); | 1370 GURL origin = document_url.GetOrigin(); |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1443 | 1459 |
1444 std::vector<int64> newly_purgeable_resources; | 1460 std::vector<int64> newly_purgeable_resources; |
1445 database->DeleteAllDataForOrigins(origins, &newly_purgeable_resources); | 1461 database->DeleteAllDataForOrigins(origins, &newly_purgeable_resources); |
1446 } | 1462 } |
1447 | 1463 |
1448 // TODO(nhiroki): The corruption recovery should not be scheduled if the error | 1464 // TODO(nhiroki): The corruption recovery should not be scheduled if the error |
1449 // is transient and it can get healed soon (e.g. IO error). To do that, the | 1465 // is transient and it can get healed soon (e.g. IO error). To do that, the |
1450 // database should not disable itself when an error occurs and the storage | 1466 // database should not disable itself when an error occurs and the storage |
1451 // controls it instead. | 1467 // controls it instead. |
1452 void ServiceWorkerStorage::ScheduleDeleteAndStartOver() { | 1468 void ServiceWorkerStorage::ScheduleDeleteAndStartOver() { |
| 1469 // TODO(dmurph): Notify the quota manager somehow that all of our data is now |
| 1470 // removed. |
1453 if (state_ == DISABLED) { | 1471 if (state_ == DISABLED) { |
1454 // Recovery process has already been scheduled. | 1472 // Recovery process has already been scheduled. |
1455 return; | 1473 return; |
1456 } | 1474 } |
1457 Disable(); | 1475 Disable(); |
1458 | 1476 |
1459 DVLOG(1) << "Schedule to delete the context and start over."; | 1477 DVLOG(1) << "Schedule to delete the context and start over."; |
1460 context_->ScheduleDeleteAndStartOver(); | 1478 context_->ScheduleDeleteAndStartOver(); |
1461 } | 1479 } |
1462 | 1480 |
(...skipping 30 matching lines...) Expand all Loading... |
1493 // Give up the corruption recovery until the browser restarts. | 1511 // Give up the corruption recovery until the browser restarts. |
1494 LOG(ERROR) << "Failed to delete the diskcache."; | 1512 LOG(ERROR) << "Failed to delete the diskcache."; |
1495 callback.Run(SERVICE_WORKER_ERROR_FAILED); | 1513 callback.Run(SERVICE_WORKER_ERROR_FAILED); |
1496 return; | 1514 return; |
1497 } | 1515 } |
1498 DVLOG(1) << "Deleted ServiceWorkerDiskCache successfully."; | 1516 DVLOG(1) << "Deleted ServiceWorkerDiskCache successfully."; |
1499 callback.Run(SERVICE_WORKER_OK); | 1517 callback.Run(SERVICE_WORKER_OK); |
1500 } | 1518 } |
1501 | 1519 |
1502 } // namespace content | 1520 } // namespace content |
OLD | NEW |