| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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/dom_storage/local_storage_context_mojo.h" | 5 #include "content/browser/dom_storage/local_storage_context_mojo.h" |
| 6 | 6 |
| 7 #include "base/memory/ptr_util.h" | 7 #include "base/memory/ptr_util.h" |
| 8 #include "base/strings/string_number_conversions.h" | 8 #include "base/strings/string_number_conversions.h" |
| 9 #include "components/leveldb/public/cpp/util.h" | 9 #include "components/leveldb/public/cpp/util.h" |
| 10 #include "components/leveldb/public/interfaces/leveldb.mojom.h" | 10 #include "components/leveldb/public/interfaces/leveldb.mojom.h" |
| 11 #include "content/browser/dom_storage/dom_storage_area.h" |
| 12 #include "content/browser/dom_storage/dom_storage_database.h" |
| 13 #include "content/browser/dom_storage/dom_storage_task_runner.h" |
| 11 #include "content/browser/dom_storage/local_storage_database.pb.h" | 14 #include "content/browser/dom_storage/local_storage_database.pb.h" |
| 12 #include "content/browser/leveldb_wrapper_impl.h" | 15 #include "content/browser/leveldb_wrapper_impl.h" |
| 13 #include "content/common/dom_storage/dom_storage_types.h" | 16 #include "content/common/dom_storage/dom_storage_types.h" |
| 14 #include "content/public/browser/local_storage_usage_info.h" | 17 #include "content/public/browser/local_storage_usage_info.h" |
| 15 #include "services/file/public/interfaces/constants.mojom.h" | 18 #include "services/file/public/interfaces/constants.mojom.h" |
| 16 #include "services/service_manager/public/cpp/connection.h" | 19 #include "services/service_manager/public/cpp/connection.h" |
| 17 #include "services/service_manager/public/cpp/connector.h" | 20 #include "services/service_manager/public/cpp/connector.h" |
| 21 #include "sql/connection.h" |
| 18 | 22 |
| 19 namespace content { | 23 namespace content { |
| 20 | 24 |
| 21 // LevelDB database schema | 25 // LevelDB database schema |
| 22 // ======================= | 26 // ======================= |
| 23 // | 27 // |
| 24 // Version 1 (in sorted order): | 28 // Version 1 (in sorted order): |
| 25 // key: "VERSION" | 29 // key: "VERSION" |
| 26 // value: "1" | 30 // value: "1" |
| 27 // | 31 // |
| (...skipping 16 matching lines...) Expand all Loading... |
| 44 auto serialized_origin = leveldb::StdStringToUint8Vector(origin.Serialize()); | 48 auto serialized_origin = leveldb::StdStringToUint8Vector(origin.Serialize()); |
| 45 std::vector<uint8_t> key; | 49 std::vector<uint8_t> key; |
| 46 key.reserve(arraysize(kMetaPrefix) + serialized_origin.size()); | 50 key.reserve(arraysize(kMetaPrefix) + serialized_origin.size()); |
| 47 key.insert(key.end(), kMetaPrefix, kMetaPrefix + arraysize(kMetaPrefix)); | 51 key.insert(key.end(), kMetaPrefix, kMetaPrefix + arraysize(kMetaPrefix)); |
| 48 key.insert(key.end(), serialized_origin.begin(), serialized_origin.end()); | 52 key.insert(key.end(), serialized_origin.begin(), serialized_origin.end()); |
| 49 return key; | 53 return key; |
| 50 } | 54 } |
| 51 | 55 |
| 52 void NoOpSuccess(bool success) {} | 56 void NoOpSuccess(bool success) {} |
| 53 | 57 |
| 58 std::vector<uint8_t> String16ToUint8Vector(const base::string16& input) { |
| 59 const uint8_t* data = reinterpret_cast<const uint8_t*>(input.data()); |
| 60 return std::vector<uint8_t>(data, data + input.size() * sizeof(base::char16)); |
| 61 } |
| 62 |
| 63 void MigrateStorageHelper( |
| 64 base::FilePath db_path, |
| 65 const scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner, |
| 66 base::Callback<void(std::unique_ptr<LevelDBWrapperImpl::ValueMap>)> |
| 67 callback) { |
| 68 DOMStorageDatabase db(db_path); |
| 69 DOMStorageValuesMap map; |
| 70 db.ReadAllValues(&map); |
| 71 auto values = base::MakeUnique<LevelDBWrapperImpl::ValueMap>(); |
| 72 for (const auto& it : map) { |
| 73 (*values)[String16ToUint8Vector(it.first)] = |
| 74 String16ToUint8Vector(it.second.string()); |
| 75 } |
| 76 reply_task_runner->PostTask(FROM_HERE, |
| 77 base::Bind(callback, base::Passed(&values))); |
| 78 } |
| 79 |
| 80 // Helper to convert from OnceCallback to Callback. |
| 81 void CallMigrationCalback(LevelDBWrapperImpl::ValueMapCallback callback, |
| 82 std::unique_ptr<LevelDBWrapperImpl::ValueMap> data) { |
| 83 std::move(callback).Run(std::move(data)); |
| 84 } |
| 85 |
| 54 } // namespace | 86 } // namespace |
| 55 | 87 |
| 88 class LocalStorageContextMojo::LevelDBWrapperHolder |
| 89 : public LevelDBWrapperImpl::Delegate { |
| 90 public: |
| 91 LevelDBWrapperHolder(LocalStorageContextMojo* context, |
| 92 const url::Origin& origin) |
| 93 : context_(context), origin_(origin) { |
| 94 // Delay for a moment after a value is set in anticipation |
| 95 // of other values being set, so changes are batched. |
| 96 const int kCommitDefaultDelaySecs = 5; |
| 97 |
| 98 // To avoid excessive IO we apply limits to the amount of data being written |
| 99 // and the frequency of writes. |
| 100 const int kMaxBytesPerHour = kPerStorageAreaQuota; |
| 101 const int kMaxCommitsPerHour = 60; |
| 102 |
| 103 level_db_wrapper_ = base::MakeUnique<LevelDBWrapperImpl>( |
| 104 context_->database_.get(), |
| 105 kDataPrefix + origin_.Serialize() + kOriginSeparator, |
| 106 kPerStorageAreaQuota + kPerStorageAreaOverQuotaAllowance, |
| 107 base::TimeDelta::FromSeconds(kCommitDefaultDelaySecs), kMaxBytesPerHour, |
| 108 kMaxCommitsPerHour, this); |
| 109 level_db_wrapper_ptr_ = level_db_wrapper_.get(); |
| 110 } |
| 111 |
| 112 LevelDBWrapperImpl* level_db_wrapper() { return level_db_wrapper_ptr_; } |
| 113 |
| 114 void OnNoBindings() override { |
| 115 // Will delete |this|. |
| 116 DCHECK(context_->level_db_wrappers_.find(origin_) != |
| 117 context_->level_db_wrappers_.end()); |
| 118 context_->level_db_wrappers_.erase(origin_); |
| 119 } |
| 120 |
| 121 std::vector<leveldb::mojom::BatchedOperationPtr> PrepareToCommit() override { |
| 122 std::vector<leveldb::mojom::BatchedOperationPtr> operations; |
| 123 |
| 124 // Write schema version if not already done so before. |
| 125 if (!context_->database_initialized_) { |
| 126 leveldb::mojom::BatchedOperationPtr item = |
| 127 leveldb::mojom::BatchedOperation::New(); |
| 128 item->type = leveldb::mojom::BatchOperationType::PUT_KEY; |
| 129 item->key = leveldb::StdStringToUint8Vector(kVersionKey); |
| 130 item->value = leveldb::StdStringToUint8Vector( |
| 131 base::Int64ToString(kCurrentSchemaVersion)); |
| 132 operations.push_back(std::move(item)); |
| 133 context_->database_initialized_ = true; |
| 134 } |
| 135 |
| 136 leveldb::mojom::BatchedOperationPtr item = |
| 137 leveldb::mojom::BatchedOperation::New(); |
| 138 item->type = leveldb::mojom::BatchOperationType::PUT_KEY; |
| 139 item->key = CreateMetaDataKey(origin_); |
| 140 if (level_db_wrapper()->empty()) { |
| 141 item->type = leveldb::mojom::BatchOperationType::DELETE_KEY; |
| 142 } else { |
| 143 item->type = leveldb::mojom::BatchOperationType::PUT_KEY; |
| 144 LocalStorageOriginMetaData data; |
| 145 data.set_last_modified(base::Time::Now().ToInternalValue()); |
| 146 data.set_size_bytes(level_db_wrapper()->bytes_used()); |
| 147 item->value = leveldb::StdStringToUint8Vector(data.SerializeAsString()); |
| 148 } |
| 149 operations.push_back(std::move(item)); |
| 150 |
| 151 return operations; |
| 152 } |
| 153 |
| 154 void DidCommit(leveldb::mojom::DatabaseError error) override { |
| 155 // Delete any old database that might still exist if we successfully wrote |
| 156 // data to LevelDB, and our LevelDB is actually disk backed. |
| 157 if (error == leveldb::mojom::DatabaseError::OK && !deleted_old_data_ && |
| 158 !context_->subdirectory_.empty() && context_->task_runner_ && |
| 159 !context_->old_localstorage_path_.empty()) { |
| 160 deleted_old_data_ = true; |
| 161 context_->task_runner_->PostShutdownBlockingTask( |
| 162 FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE, |
| 163 base::Bind(base::IgnoreResult(&sql::Connection::Delete), |
| 164 sql_db_path())); |
| 165 } |
| 166 } |
| 167 |
| 168 void MigrateData(LevelDBWrapperImpl::ValueMapCallback callback) override { |
| 169 if (context_->task_runner_ && !context_->old_localstorage_path_.empty()) { |
| 170 context_->task_runner_->PostShutdownBlockingTask( |
| 171 FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE, |
| 172 base::Bind( |
| 173 &MigrateStorageHelper, sql_db_path(), |
| 174 base::ThreadTaskRunnerHandle::Get(), |
| 175 base::Bind(&CallMigrationCalback, base::Passed(&callback)))); |
| 176 return; |
| 177 } |
| 178 std::move(callback).Run(nullptr); |
| 179 } |
| 180 |
| 181 private: |
| 182 base::FilePath sql_db_path() const { |
| 183 if (context_->old_localstorage_path_.empty()) |
| 184 return base::FilePath(); |
| 185 return context_->old_localstorage_path_.Append( |
| 186 DOMStorageArea::DatabaseFileNameFromOrigin(origin_.GetURL())); |
| 187 } |
| 188 |
| 189 LocalStorageContextMojo* context_; |
| 190 url::Origin origin_; |
| 191 std::unique_ptr<LevelDBWrapperImpl> level_db_wrapper_; |
| 192 // Holds the same value as |level_db_wrapper_|. The reason for this is that |
| 193 // during destruction of the LevelDBWrapperImpl instance we might still get |
| 194 // called and need access to the LevelDBWrapperImpl instance. The unique_ptr |
| 195 // could already be null, but this field should still be valid. |
| 196 LevelDBWrapperImpl* level_db_wrapper_ptr_; |
| 197 bool deleted_old_data_ = false; |
| 198 }; |
| 199 |
| 56 LocalStorageContextMojo::LocalStorageContextMojo( | 200 LocalStorageContextMojo::LocalStorageContextMojo( |
| 57 service_manager::Connector* connector, | 201 service_manager::Connector* connector, |
| 202 scoped_refptr<DOMStorageTaskRunner> task_runner, |
| 203 const base::FilePath& old_localstorage_path, |
| 58 const base::FilePath& subdirectory) | 204 const base::FilePath& subdirectory) |
| 59 : connector_(connector), | 205 : connector_(connector), |
| 60 subdirectory_(subdirectory), | 206 subdirectory_(subdirectory), |
| 207 task_runner_(std::move(task_runner)), |
| 208 old_localstorage_path_(old_localstorage_path), |
| 61 weak_ptr_factory_(this) {} | 209 weak_ptr_factory_(this) {} |
| 62 | 210 |
| 63 LocalStorageContextMojo::~LocalStorageContextMojo() {} | 211 LocalStorageContextMojo::~LocalStorageContextMojo() {} |
| 64 | 212 |
| 65 void LocalStorageContextMojo::OpenLocalStorage( | 213 void LocalStorageContextMojo::OpenLocalStorage( |
| 66 const url::Origin& origin, | 214 const url::Origin& origin, |
| 67 mojom::LevelDBWrapperRequest request) { | 215 mojom::LevelDBWrapperRequest request) { |
| 68 RunWhenConnected(base::BindOnce(&LocalStorageContextMojo::BindLocalStorage, | 216 RunWhenConnected(base::BindOnce(&LocalStorageContextMojo::BindLocalStorage, |
| 69 weak_ptr_factory_.GetWeakPtr(), origin, | 217 weak_ptr_factory_.GetWeakPtr(), origin, |
| 70 std::move(request))); | 218 std::move(request))); |
| (...skipping 22 matching lines...) Expand all Loading... |
| 93 | 241 |
| 94 void LocalStorageContextMojo::DeleteStorageForPhysicalOrigin( | 242 void LocalStorageContextMojo::DeleteStorageForPhysicalOrigin( |
| 95 const url::Origin& origin) { | 243 const url::Origin& origin) { |
| 96 GetStorageUsage(base::BindOnce( | 244 GetStorageUsage(base::BindOnce( |
| 97 &LocalStorageContextMojo::OnGotStorageUsageForDeletePhysicalOrigin, | 245 &LocalStorageContextMojo::OnGotStorageUsageForDeletePhysicalOrigin, |
| 98 weak_ptr_factory_.GetWeakPtr(), origin)); | 246 weak_ptr_factory_.GetWeakPtr(), origin)); |
| 99 } | 247 } |
| 100 | 248 |
| 101 void LocalStorageContextMojo::Flush() { | 249 void LocalStorageContextMojo::Flush() { |
| 102 for (const auto& it : level_db_wrappers_) | 250 for (const auto& it : level_db_wrappers_) |
| 103 it.second->ScheduleImmediateCommit(); | 251 it.second->level_db_wrapper()->ScheduleImmediateCommit(); |
| 104 } | 252 } |
| 105 | 253 |
| 106 leveldb::mojom::LevelDBDatabaseAssociatedRequest | 254 leveldb::mojom::LevelDBDatabaseAssociatedRequest |
| 107 LocalStorageContextMojo::DatabaseRequestForTesting() { | 255 LocalStorageContextMojo::DatabaseRequestForTesting() { |
| 108 DCHECK_EQ(connection_state_, NO_CONNECTION); | 256 DCHECK_EQ(connection_state_, NO_CONNECTION); |
| 109 connection_state_ = CONNECTION_IN_PROGRESS; | 257 connection_state_ = CONNECTION_IN_PROGRESS; |
| 110 leveldb::mojom::LevelDBDatabaseAssociatedRequest request = | 258 leveldb::mojom::LevelDBDatabaseAssociatedRequest request = |
| 111 MakeRequestForTesting(&database_); | 259 MakeRequestForTesting(&database_); |
| 112 OnDatabaseOpened(leveldb::mojom::DatabaseError::OK); | 260 OnDatabaseOpened(leveldb::mojom::DatabaseError::OK); |
| 113 return request; | 261 return request; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 131 | 279 |
| 132 if (connection_state_ == CONNECTION_IN_PROGRESS) { | 280 if (connection_state_ == CONNECTION_IN_PROGRESS) { |
| 133 // Queue this OpenLocalStorage call for when we have a level db pointer. | 281 // Queue this OpenLocalStorage call for when we have a level db pointer. |
| 134 on_database_opened_callbacks_.push_back(std::move(callback)); | 282 on_database_opened_callbacks_.push_back(std::move(callback)); |
| 135 return; | 283 return; |
| 136 } | 284 } |
| 137 | 285 |
| 138 std::move(callback).Run(); | 286 std::move(callback).Run(); |
| 139 } | 287 } |
| 140 | 288 |
| 141 void LocalStorageContextMojo::OnLevelDBWrapperHasNoBindings( | |
| 142 const url::Origin& origin) { | |
| 143 DCHECK(level_db_wrappers_.find(origin) != level_db_wrappers_.end()); | |
| 144 level_db_wrappers_.erase(origin); | |
| 145 } | |
| 146 | |
| 147 std::vector<leveldb::mojom::BatchedOperationPtr> | |
| 148 LocalStorageContextMojo::OnLevelDBWrapperPrepareToCommit( | |
| 149 const url::Origin& origin, | |
| 150 const LevelDBWrapperImpl& wrapper) { | |
| 151 // |wrapper| might not exist in |level_db_wrappers_| anymore at this point, as | |
| 152 // it is possible this commit was triggered by destruction. | |
| 153 | |
| 154 std::vector<leveldb::mojom::BatchedOperationPtr> operations; | |
| 155 | |
| 156 // Write schema version if not already done so before. | |
| 157 if (!database_initialized_) { | |
| 158 leveldb::mojom::BatchedOperationPtr item = | |
| 159 leveldb::mojom::BatchedOperation::New(); | |
| 160 item->type = leveldb::mojom::BatchOperationType::PUT_KEY; | |
| 161 item->key = leveldb::StdStringToUint8Vector(kVersionKey); | |
| 162 item->value = leveldb::StdStringToUint8Vector( | |
| 163 base::Int64ToString(kCurrentSchemaVersion)); | |
| 164 operations.push_back(std::move(item)); | |
| 165 database_initialized_ = true; | |
| 166 } | |
| 167 | |
| 168 leveldb::mojom::BatchedOperationPtr item = | |
| 169 leveldb::mojom::BatchedOperation::New(); | |
| 170 item->type = leveldb::mojom::BatchOperationType::PUT_KEY; | |
| 171 item->key = CreateMetaDataKey(origin); | |
| 172 if (wrapper.bytes_used() == 0) { | |
| 173 item->type = leveldb::mojom::BatchOperationType::DELETE_KEY; | |
| 174 } else { | |
| 175 item->type = leveldb::mojom::BatchOperationType::PUT_KEY; | |
| 176 LocalStorageOriginMetaData data; | |
| 177 data.set_last_modified(base::Time::Now().ToInternalValue()); | |
| 178 data.set_size_bytes(wrapper.bytes_used()); | |
| 179 item->value = leveldb::StdStringToUint8Vector(data.SerializeAsString()); | |
| 180 } | |
| 181 operations.push_back(std::move(item)); | |
| 182 | |
| 183 return operations; | |
| 184 } | |
| 185 | |
| 186 void LocalStorageContextMojo::OnUserServiceConnectionComplete() { | 289 void LocalStorageContextMojo::OnUserServiceConnectionComplete() { |
| 187 CHECK_EQ(service_manager::mojom::ConnectResult::SUCCEEDED, | 290 CHECK_EQ(service_manager::mojom::ConnectResult::SUCCEEDED, |
| 188 file_service_connection_->GetResult()); | 291 file_service_connection_->GetResult()); |
| 189 } | 292 } |
| 190 | 293 |
| 191 void LocalStorageContextMojo::OnUserServiceConnectionError() { | 294 void LocalStorageContextMojo::OnUserServiceConnectionError() { |
| 192 CHECK(false); | 295 CHECK(false); |
| 193 } | 296 } |
| 194 | 297 |
| 195 void LocalStorageContextMojo::InitiateConnection(bool in_memory_only) { | 298 void LocalStorageContextMojo::InitiateConnection(bool in_memory_only) { |
| (...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 358 const url::Origin& origin, | 461 const url::Origin& origin, |
| 359 mojom::LevelDBWrapperRequest request) { | 462 mojom::LevelDBWrapperRequest request) { |
| 360 GetOrCreateDBWrapper(origin)->Bind(std::move(request)); | 463 GetOrCreateDBWrapper(origin)->Bind(std::move(request)); |
| 361 } | 464 } |
| 362 | 465 |
| 363 LevelDBWrapperImpl* LocalStorageContextMojo::GetOrCreateDBWrapper( | 466 LevelDBWrapperImpl* LocalStorageContextMojo::GetOrCreateDBWrapper( |
| 364 const url::Origin& origin) { | 467 const url::Origin& origin) { |
| 365 DCHECK_EQ(connection_state_, CONNECTION_FINISHED); | 468 DCHECK_EQ(connection_state_, CONNECTION_FINISHED); |
| 366 auto found = level_db_wrappers_.find(origin); | 469 auto found = level_db_wrappers_.find(origin); |
| 367 if (found != level_db_wrappers_.end()) | 470 if (found != level_db_wrappers_.end()) |
| 368 return found->second.get(); | 471 return found->second->level_db_wrapper(); |
| 369 | 472 |
| 370 // Delay for a moment after a value is set in anticipation | 473 auto holder = base::MakeUnique<LevelDBWrapperHolder>(this, origin); |
| 371 // of other values being set, so changes are batched. | 474 LevelDBWrapperImpl* wrapper_ptr = holder->level_db_wrapper(); |
| 372 const int kCommitDefaultDelaySecs = 5; | 475 level_db_wrappers_[origin] = std::move(holder); |
| 373 | |
| 374 // To avoid excessive IO we apply limits to the amount of data being written | |
| 375 // and the frequency of writes. | |
| 376 const int kMaxBytesPerHour = kPerStorageAreaQuota; | |
| 377 const int kMaxCommitsPerHour = 60; | |
| 378 | |
| 379 auto wrapper = base::MakeUnique<LevelDBWrapperImpl>( | |
| 380 database_.get(), kDataPrefix + origin.Serialize() + kOriginSeparator, | |
| 381 kPerStorageAreaQuota + kPerStorageAreaOverQuotaAllowance, | |
| 382 base::TimeDelta::FromSeconds(kCommitDefaultDelaySecs), kMaxBytesPerHour, | |
| 383 kMaxCommitsPerHour, | |
| 384 base::Bind(&LocalStorageContextMojo::OnLevelDBWrapperHasNoBindings, | |
| 385 base::Unretained(this), origin), | |
| 386 base::Bind(&LocalStorageContextMojo::OnLevelDBWrapperPrepareToCommit, | |
| 387 base::Unretained(this), origin)); | |
| 388 LevelDBWrapperImpl* wrapper_ptr = wrapper.get(); | |
| 389 level_db_wrappers_[origin] = std::move(wrapper); | |
| 390 return wrapper_ptr; | 476 return wrapper_ptr; |
| 391 } | 477 } |
| 392 | 478 |
| 393 void LocalStorageContextMojo::RetrieveStorageUsage( | 479 void LocalStorageContextMojo::RetrieveStorageUsage( |
| 394 GetStorageUsageCallback callback) { | 480 GetStorageUsageCallback callback) { |
| 395 database_->GetPrefixed( | 481 database_->GetPrefixed( |
| 396 std::vector<uint8_t>(kMetaPrefix, kMetaPrefix + arraysize(kMetaPrefix)), | 482 std::vector<uint8_t>(kMetaPrefix, kMetaPrefix + arraysize(kMetaPrefix)), |
| 397 base::Bind(&LocalStorageContextMojo::OnGotMetaData, | 483 base::Bind(&LocalStorageContextMojo::OnGotMetaData, |
| 398 weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback))); | 484 weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback))); |
| 399 } | 485 } |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 431 for (const auto& info : usage) { | 517 for (const auto& info : usage) { |
| 432 url::Origin origin_candidate(info.origin); | 518 url::Origin origin_candidate(info.origin); |
| 433 if (!origin_candidate.IsSameOriginWith(origin) && | 519 if (!origin_candidate.IsSameOriginWith(origin) && |
| 434 origin_candidate.IsSamePhysicalOriginWith(origin)) | 520 origin_candidate.IsSamePhysicalOriginWith(origin)) |
| 435 DeleteStorage(origin_candidate); | 521 DeleteStorage(origin_candidate); |
| 436 } | 522 } |
| 437 DeleteStorage(origin); | 523 DeleteStorage(origin); |
| 438 } | 524 } |
| 439 | 525 |
| 440 } // namespace content | 526 } // namespace content |
| OLD | NEW |