| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "chrome/browser/extensions/settings/settings_frontend.h" | 5 #include "chrome/browser/extensions/settings/settings_frontend.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/file_path.h" | 8 #include "base/file_path.h" |
| 9 #include "chrome/browser/extensions/extension_event_names.h" | 9 #include "chrome/browser/extensions/extension_event_names.h" |
| 10 #include "chrome/browser/extensions/extension_event_router.h" | 10 #include "chrome/browser/extensions/extension_event_router.h" |
| 11 #include "chrome/browser/extensions/extension_service.h" | 11 #include "chrome/browser/extensions/extension_service.h" |
| 12 #include "chrome/browser/extensions/settings/settings_backend.h" | 12 #include "chrome/browser/extensions/settings/settings_backend.h" |
| 13 #include "chrome/browser/extensions/settings/settings_namespace.h" |
| 13 #include "chrome/browser/extensions/settings/settings_leveldb_storage.h" | 14 #include "chrome/browser/extensions/settings/settings_leveldb_storage.h" |
| 14 #include "chrome/browser/profiles/profile.h" | 15 #include "chrome/browser/profiles/profile.h" |
| 15 #include "content/public/browser/browser_thread.h" | 16 #include "content/public/browser/browser_thread.h" |
| 16 #include "content/public/browser/notification_service.h" | 17 #include "content/public/browser/notification_service.h" |
| 17 | 18 |
| 19 using content::BrowserThread; |
| 20 |
| 18 namespace extensions { | 21 namespace extensions { |
| 19 | 22 |
| 20 using content::BrowserThread; | |
| 21 | |
| 22 namespace { | 23 namespace { |
| 23 | 24 |
| 24 struct Backends { | 25 // Settings change Observer which forwards changes on to the extension |
| 25 Backends( | 26 // processes for |profile| and its incognito partner if it exists. |
| 26 // Ownership taken. | 27 class DefaultObserver : public SettingsObserver { |
| 27 SettingsStorageFactory* storage_factory, | 28 public: |
| 28 const FilePath& profile_path, | 29 explicit DefaultObserver(Profile* profile) : profile_(profile) {} |
| 29 const scoped_refptr<SettingsObserverList>& observers) | |
| 30 : storage_factory_(storage_factory), | |
| 31 extensions_backend_( | |
| 32 storage_factory, | |
| 33 profile_path.AppendASCII( | |
| 34 ExtensionService::kExtensionSettingsDirectoryName), | |
| 35 observers), | |
| 36 apps_backend_( | |
| 37 storage_factory, | |
| 38 profile_path.AppendASCII( | |
| 39 ExtensionService::kAppSettingsDirectoryName), | |
| 40 observers) {} | |
| 41 | 30 |
| 42 scoped_ptr<SettingsStorageFactory> storage_factory_; | 31 // SettingsObserver implementation. |
| 43 SettingsBackend extensions_backend_; | 32 virtual void OnSettingsChanged( |
| 44 SettingsBackend apps_backend_; | 33 const std::string& extension_id, |
| 34 settings_namespace::Namespace settings_namespace, |
| 35 const std::string& change_json) OVERRIDE { |
| 36 profile_->GetExtensionEventRouter()->DispatchEventToExtension( |
| 37 extension_id, |
| 38 extension_event_names::kOnSettingsChanged, |
| 39 // This is the list of function arguments to pass to the onChanged |
| 40 // handler of extensions, an array of [changes, settings_namespace]. |
| 41 std::string("[") + change_json + ",\"" + |
| 42 settings_namespace::ToString(settings_namespace) + "\"]", |
| 43 NULL, |
| 44 GURL()); |
| 45 } |
| 46 |
| 47 private: |
| 48 Profile* const profile_; |
| 45 }; | 49 }; |
| 46 | 50 |
| 47 static void CallbackWithExtensionsBackend( | 51 void CallbackWithSyncableService( |
| 48 const SettingsFrontend::SyncableServiceCallback& callback, | 52 const SettingsFrontend::SyncableServiceCallback& callback, |
| 49 Backends* backends) { | 53 SettingsBackend* backend) { |
| 50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 51 callback.Run(&backends->extensions_backend_); | 55 callback.Run(backend); |
| 52 } | 56 } |
| 53 | 57 |
| 54 void CallbackWithAppsBackend( | 58 void CallbackWithStorage( |
| 55 const SettingsFrontend::SyncableServiceCallback& callback, | |
| 56 Backends* backends) { | |
| 57 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 58 callback.Run(&backends->apps_backend_); | |
| 59 } | |
| 60 | |
| 61 void CallbackWithExtensionsStorage( | |
| 62 const std::string& extension_id, | 59 const std::string& extension_id, |
| 63 const SettingsFrontend::StorageCallback& callback, | 60 const SettingsFrontend::StorageCallback& callback, |
| 64 Backends* backends) { | 61 SettingsBackend* backend) { |
| 65 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 66 callback.Run(backends->extensions_backend_.GetStorage(extension_id)); | 63 callback.Run(backend->GetStorage(extension_id)); |
| 67 } | |
| 68 | |
| 69 void CallbackWithAppsStorage( | |
| 70 const std::string& extension_id, | |
| 71 const SettingsFrontend::StorageCallback& callback, | |
| 72 Backends* backends) { | |
| 73 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 74 callback.Run(backends->apps_backend_.GetStorage(extension_id)); | |
| 75 } | 64 } |
| 76 | 65 |
| 77 void CallbackWithNullStorage( | 66 void CallbackWithNullStorage( |
| 78 const SettingsFrontend::StorageCallback& callback) { | 67 const SettingsFrontend::StorageCallback& callback) { |
| 79 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 68 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 80 callback.Run(NULL); | 69 callback.Run(NULL); |
| 81 } | 70 } |
| 82 | 71 |
| 83 void DeleteStorageOnFileThread( | 72 void DeleteStorageOnFileThread( |
| 84 const std::string& extension_id, Backends* backends) { | 73 const std::string& extension_id, SettingsBackend* backend) { |
| 85 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 74 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 86 backends->extensions_backend_.DeleteStorage(extension_id); | 75 backend->DeleteStorage(extension_id); |
| 87 backends->apps_backend_.DeleteStorage(extension_id); | |
| 88 } | 76 } |
| 89 | 77 |
| 90 } // namespace | 78 } // namespace |
| 91 | 79 |
| 92 // DefaultObserver | 80 // Ref-counted container for a SettingsBackend object. |
| 93 | 81 class SettingsFrontend::BackendWrapper |
| 94 SettingsFrontend::DefaultObserver::DefaultObserver(Profile* profile) | 82 : public base::RefCountedThreadSafe<BackendWrapper> { |
| 95 : profile_(profile) {} | |
| 96 | |
| 97 SettingsFrontend::DefaultObserver::~DefaultObserver() {} | |
| 98 | |
| 99 void SettingsFrontend::DefaultObserver::OnSettingsChanged( | |
| 100 const std::string& extension_id, const std::string& changes_json) { | |
| 101 profile_->GetExtensionEventRouter()->DispatchEventToExtension( | |
| 102 extension_id, | |
| 103 extension_event_names::kOnSettingsChanged, | |
| 104 // This is the list of function arguments to pass to the onChanged | |
| 105 // handler of extensions, a single argument with the list of changes. | |
| 106 std::string("[") + changes_json + "]", | |
| 107 NULL, | |
| 108 GURL()); | |
| 109 } | |
| 110 | |
| 111 // Core | |
| 112 | |
| 113 class SettingsFrontend::Core | |
| 114 : public base::RefCountedThreadSafe<Core> { | |
| 115 public: | 83 public: |
| 116 explicit Core( | 84 // Creates a new BackendWrapper and initializes it on the FILE thread. |
| 117 // Ownership taken. | 85 static scoped_refptr<BackendWrapper> CreateAndInit( |
| 118 SettingsStorageFactory* storage_factory, | 86 const scoped_refptr<SettingsStorageFactory>& factory, |
| 119 const scoped_refptr<SettingsObserverList>& observers) | 87 const scoped_refptr<SettingsObserverList>& observers, |
| 120 : storage_factory_(storage_factory), | 88 const FilePath& path) { |
| 121 observers_(observers), | |
| 122 backends_(NULL) { | |
| 123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 89 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 90 scoped_refptr<BackendWrapper> backend_wrapper = |
| 91 new BackendWrapper(factory, observers); |
| 92 BrowserThread::PostTask( |
| 93 BrowserThread::FILE, |
| 94 FROM_HERE, |
| 95 base::Bind( |
| 96 &SettingsFrontend::BackendWrapper::InitOnFileThread, |
| 97 backend_wrapper, |
| 98 path)); |
| 99 return backend_wrapper; |
| 124 } | 100 } |
| 125 | 101 |
| 126 typedef base::Callback<void(Backends*)> BackendsCallback; | 102 typedef base::Callback<void(SettingsBackend*)> BackendCallback; |
| 127 | 103 |
| 128 // Does any FILE thread specific initialization, such as construction of | 104 // Runs |callback| with the wrapped Backend on the FILE thread. |
| 129 // |backend_|. Must be called before any call to | 105 void RunWithBackend(const BackendCallback& callback) { |
| 130 // RunWithBackendOnFileThread(). | |
| 131 void InitOnFileThread(const FilePath& profile_path) { | |
| 132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 133 DCHECK(!backends_); | |
| 134 backends_ = | |
| 135 new Backends( | |
| 136 storage_factory_.release(), profile_path, observers_); | |
| 137 } | |
| 138 | |
| 139 // Runs |callback| with both the extensions and apps settings on the FILE | |
| 140 // thread. | |
| 141 void RunWithBackends(const BackendsCallback& callback) { | |
| 142 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 143 BrowserThread::PostTask( | 107 BrowserThread::PostTask( |
| 144 BrowserThread::FILE, | 108 BrowserThread::FILE, |
| 145 FROM_HERE, | 109 FROM_HERE, |
| 146 base::Bind( | 110 base::Bind( |
| 147 &SettingsFrontend::Core::RunWithBackendsOnFileThread, | 111 &SettingsFrontend::BackendWrapper::RunWithBackendOnFileThread, |
| 148 this, | 112 this, |
| 149 callback)); | 113 callback)); |
| 150 } | 114 } |
| 151 | 115 |
| 152 private: | 116 private: |
| 153 void RunWithBackendsOnFileThread(const BackendsCallback& callback) { | 117 friend class base::RefCountedThreadSafe<BackendWrapper>; |
| 154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 118 |
| 155 DCHECK(backends_); | 119 BackendWrapper( |
| 156 callback.Run(backends_); | 120 const scoped_refptr<SettingsStorageFactory>& storage_factory, |
| 121 const scoped_refptr<SettingsObserverList>& observers) |
| 122 : storage_factory_(storage_factory), |
| 123 observers_(observers), |
| 124 backend_(NULL) { |
| 125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 157 } | 126 } |
| 158 | 127 |
| 159 virtual ~Core() { | 128 virtual ~BackendWrapper() { |
| 160 if (BrowserThread::CurrentlyOn(BrowserThread::FILE)) { | 129 if (BrowserThread::CurrentlyOn(BrowserThread::FILE)) { |
| 161 delete backends_; | 130 delete backend_; |
| 162 } else if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 131 } else if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| 163 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, backends_); | 132 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, backend_); |
| 164 } else { | 133 } else { |
| 165 NOTREACHED(); | 134 NOTREACHED(); |
| 166 } | 135 } |
| 167 } | 136 } |
| 168 | 137 |
| 169 friend class base::RefCountedThreadSafe<Core>; | 138 void InitOnFileThread(const FilePath& path) { |
| 139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 140 DCHECK(!backend_); |
| 141 backend_ = new SettingsBackend(storage_factory_, path, observers_); |
| 142 storage_factory_ = NULL; |
| 143 observers_ = NULL; |
| 144 } |
| 170 | 145 |
| 171 // Leveldb storage area factory. Ownership passed to Backends on Init. | 146 void RunWithBackendOnFileThread(const BackendCallback& callback) { |
| 172 scoped_ptr<SettingsStorageFactory> storage_factory_; | 147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 148 DCHECK(backend_); |
| 149 callback.Run(backend_); |
| 150 } |
| 173 | 151 |
| 174 // Observers to settings changes (thread safe). | 152 // Only need these until |backend_| exists. |
| 153 scoped_refptr<SettingsStorageFactory> storage_factory_; |
| 175 scoped_refptr<SettingsObserverList> observers_; | 154 scoped_refptr<SettingsObserverList> observers_; |
| 176 | 155 |
| 177 // Backends for extensions and apps settings. Lives on FILE thread. | 156 // Wrapped Backend. Used exclusively on the FILE thread, and is created on |
| 178 Backends* backends_; | 157 // the FILE thread in InitOnFileThread. |
| 158 SettingsBackend* backend_; |
| 179 | 159 |
| 180 DISALLOW_COPY_AND_ASSIGN(Core); | 160 DISALLOW_COPY_AND_ASSIGN(BackendWrapper); |
| 181 }; | 161 }; |
| 182 | 162 |
| 183 // SettingsFrontend | 163 // SettingsFrontend |
| 184 | 164 |
| 185 /* static */ | 165 /* static */ |
| 186 SettingsFrontend* SettingsFrontend::Create(Profile* profile) { | 166 SettingsFrontend* SettingsFrontend::Create(Profile* profile) { |
| 187 return new SettingsFrontend(new SettingsLeveldbStorage::Factory(), profile); | 167 return new SettingsFrontend(new SettingsLeveldbStorage::Factory(), profile); |
| 188 } | 168 } |
| 189 | 169 |
| 190 /* static */ | 170 /* static */ |
| 191 SettingsFrontend* SettingsFrontend::Create( | 171 SettingsFrontend* SettingsFrontend::Create( |
| 192 SettingsStorageFactory* storage_factory, Profile* profile) { | 172 const scoped_refptr<SettingsStorageFactory>& storage_factory, |
| 173 Profile* profile) { |
| 193 return new SettingsFrontend(storage_factory, profile); | 174 return new SettingsFrontend(storage_factory, profile); |
| 194 } | 175 } |
| 195 | 176 |
| 196 SettingsFrontend::SettingsFrontend( | 177 SettingsFrontend::SettingsFrontend( |
| 197 SettingsStorageFactory* storage_factory, Profile* profile) | 178 const scoped_refptr<SettingsStorageFactory>& factory, Profile* profile) |
| 198 : profile_(profile), | 179 : profile_(profile), |
| 199 observers_(new SettingsObserverList()), | 180 observers_(new SettingsObserverList()), |
| 200 default_observer_(profile), | 181 profile_observer_(new DefaultObserver(profile)) { |
| 201 core_(new SettingsFrontend::Core(storage_factory, observers_)) { | |
| 202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 182 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 203 DCHECK(!profile->IsOffTheRecord()); | 183 DCHECK(!profile->IsOffTheRecord()); |
| 204 | 184 |
| 205 observers_->AddObserver(&default_observer_); | 185 observers_->AddObserver(profile_observer_.get()); |
| 206 | 186 |
| 207 BrowserThread::PostTask( | 187 const FilePath& profile_path = profile->GetPath(); |
| 208 BrowserThread::FILE, | 188 backends_[settings_namespace::LOCAL].app = |
| 209 FROM_HERE, | 189 BackendWrapper::CreateAndInit( |
| 210 base::Bind( | 190 factory, |
| 211 &SettingsFrontend::Core::InitOnFileThread, | 191 observers_, |
| 212 core_.get(), | 192 profile_path.AppendASCII( |
| 213 profile->GetPath())); | 193 ExtensionService::kLocalAppSettingsDirectoryName)); |
| 194 backends_[settings_namespace::LOCAL].extension = |
| 195 BackendWrapper::CreateAndInit( |
| 196 factory, |
| 197 observers_, |
| 198 profile_path.AppendASCII( |
| 199 ExtensionService::kLocalExtensionSettingsDirectoryName)); |
| 200 backends_[settings_namespace::SYNC].app = |
| 201 BackendWrapper::CreateAndInit( |
| 202 factory, |
| 203 observers_, |
| 204 profile_path.AppendASCII( |
| 205 ExtensionService::kSyncAppSettingsDirectoryName)); |
| 206 backends_[settings_namespace::SYNC].extension = |
| 207 BackendWrapper::CreateAndInit( |
| 208 factory, |
| 209 observers_, |
| 210 profile_path.AppendASCII( |
| 211 ExtensionService::kSyncExtensionSettingsDirectoryName)); |
| 214 } | 212 } |
| 215 | 213 |
| 216 SettingsFrontend::~SettingsFrontend() { | 214 SettingsFrontend::~SettingsFrontend() { |
| 217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 215 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 218 observers_->RemoveObserver(&default_observer_); | 216 observers_->RemoveObserver(profile_observer_.get()); |
| 219 } | 217 } |
| 220 | 218 |
| 221 void SettingsFrontend::RunWithSyncableService( | 219 void SettingsFrontend::RunWithSyncableService( |
| 222 syncable::ModelType model_type, const SyncableServiceCallback& callback) { | 220 syncable::ModelType model_type, const SyncableServiceCallback& callback) { |
| 223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 221 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 222 scoped_refptr<BackendWrapper> backend; |
| 224 switch (model_type) { | 223 switch (model_type) { |
| 224 case syncable::APP_SETTINGS: |
| 225 backend = backends_[settings_namespace::SYNC].app; |
| 226 break; |
| 227 |
| 225 case syncable::EXTENSION_SETTINGS: | 228 case syncable::EXTENSION_SETTINGS: |
| 226 core_->RunWithBackends( | 229 backend = backends_[settings_namespace::SYNC].extension; |
| 227 base::Bind(&CallbackWithExtensionsBackend, callback)); | |
| 228 break; | 230 break; |
| 229 case syncable::APP_SETTINGS: | 231 |
| 230 core_->RunWithBackends( | |
| 231 base::Bind(&CallbackWithAppsBackend, callback)); | |
| 232 break; | |
| 233 default: | 232 default: |
| 234 NOTREACHED(); | 233 NOTREACHED(); |
| 235 } | 234 } |
| 235 backend->RunWithBackend(base::Bind(&CallbackWithSyncableService, callback)); |
| 236 } | 236 } |
| 237 | 237 |
| 238 void SettingsFrontend::RunWithStorage( | 238 void SettingsFrontend::RunWithStorage( |
| 239 const std::string& extension_id, | 239 const std::string& extension_id, |
| 240 settings_namespace::Namespace settings_namespace, |
| 240 const StorageCallback& callback) { | 241 const StorageCallback& callback) { |
| 241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 242 | 243 |
| 243 const Extension* extension = | 244 const Extension* extension = |
| 244 profile_->GetExtensionService()->GetExtensionById(extension_id, true); | 245 profile_->GetExtensionService()->GetExtensionById(extension_id, true); |
| 245 if (!extension) { | 246 if (!extension) { |
| 246 BrowserThread::PostTask( | 247 BrowserThread::PostTask( |
| 247 BrowserThread::FILE, | 248 BrowserThread::FILE, |
| 248 FROM_HERE, | 249 FROM_HERE, |
| 249 base::Bind(&CallbackWithNullStorage, callback)); | 250 base::Bind(&CallbackWithNullStorage, callback)); |
| 250 return; | 251 return; |
| 251 } | 252 } |
| 252 | 253 |
| 254 scoped_refptr<BackendWrapper> backend; |
| 253 if (extension->is_app()) { | 255 if (extension->is_app()) { |
| 254 core_->RunWithBackends( | 256 backend = backends_[settings_namespace].app; |
| 255 base::Bind(&CallbackWithAppsStorage, extension_id, callback)); | |
| 256 } else { | 257 } else { |
| 257 core_->RunWithBackends( | 258 backend = backends_[settings_namespace].extension; |
| 258 base::Bind(&CallbackWithExtensionsStorage, extension_id, callback)); | |
| 259 } | 259 } |
| 260 backend->RunWithBackend( |
| 261 base::Bind(&CallbackWithStorage, extension_id, callback)); |
| 260 } | 262 } |
| 261 | 263 |
| 262 void SettingsFrontend::DeleteStorageSoon( | 264 void SettingsFrontend::DeleteStorageSoon( |
| 263 const std::string& extension_id) { | 265 const std::string& extension_id) { |
| 264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 266 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 265 core_->RunWithBackends(base::Bind(&DeleteStorageOnFileThread, extension_id)); | 267 SettingsFrontend::BackendWrapper::BackendCallback callback = |
| 268 base::Bind(&DeleteStorageOnFileThread, extension_id); |
| 269 for (std::map<settings_namespace::Namespace, BackendWrappers>::iterator it = |
| 270 backends_.begin(); it != backends_.end(); ++it) { |
| 271 it->second.app->RunWithBackend(callback); |
| 272 it->second.extension->RunWithBackend(callback); |
| 273 } |
| 266 } | 274 } |
| 267 | 275 |
| 268 scoped_refptr<SettingsObserverList> | 276 scoped_refptr<SettingsObserverList> SettingsFrontend::GetObservers() { |
| 269 SettingsFrontend::GetObservers() { | |
| 270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 271 return observers_; | 278 return observers_; |
| 272 } | 279 } |
| 273 | 280 |
| 281 // BackendWrappers |
| 282 |
| 283 SettingsFrontend::BackendWrappers::BackendWrappers() {} |
| 284 SettingsFrontend::BackendWrappers::~BackendWrappers() {} |
| 285 |
| 274 } // namespace extensions | 286 } // namespace extensions |
| OLD | NEW |