Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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/component_loader.h" | 5 #include "chrome/browser/extensions/component_loader.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
| 10 #include "base/files/file_util.h" | 10 #include "base/files/file_util.h" |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 104 return !base::CommandLine::ForCurrentProcess()->HasSwitch( | 104 return !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 105 chromeos::switches::kGuestSession) && | 105 chromeos::switches::kGuestSession) && |
| 106 user_manager::UserManager::IsInitialized() && | 106 user_manager::UserManager::IsInitialized() && |
| 107 user_manager::UserManager::Get()->IsUserLoggedIn(); | 107 user_manager::UserManager::Get()->IsUserLoggedIn(); |
| 108 } | 108 } |
| 109 #endif // defined(OS_CHROMEOS) | 109 #endif // defined(OS_CHROMEOS) |
| 110 | 110 |
| 111 } // namespace | 111 } // namespace |
| 112 | 112 |
| 113 ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( | 113 ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( |
| 114 const base::DictionaryValue* manifest, const base::FilePath& directory) | 114 std::unique_ptr<base::DictionaryValue> manifest_param, |
|
Devlin
2016/11/17 16:02:54
optional nit: I'd prefer to just call this manifes
lazyboy
2016/11/17 22:50:38
The issue why I did this is when we use |manifest|
Devlin
2016/11/17 23:34:48
ah, makes sense, I'm good with this.
| |
| 115 : manifest(manifest), | 115 const base::FilePath& directory) |
| 116 root_directory(directory) { | 116 : manifest(std::move(manifest_param)), root_directory(directory) { |
| 117 if (!root_directory.IsAbsolute()) { | 117 if (!root_directory.IsAbsolute()) { |
| 118 CHECK(PathService::Get(chrome::DIR_RESOURCES, &root_directory)); | 118 CHECK(PathService::Get(chrome::DIR_RESOURCES, &root_directory)); |
| 119 root_directory = root_directory.Append(directory); | 119 root_directory = root_directory.Append(directory); |
| 120 } | 120 } |
| 121 extension_id = GenerateId(manifest, root_directory); | 121 extension_id = GenerateId(manifest.get(), root_directory); |
| 122 } | 122 } |
| 123 | 123 |
| 124 ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( | |
| 125 ComponentExtensionInfo&& other) | |
| 126 : manifest(std::move(other.manifest)), | |
| 127 root_directory(std::move(other.root_directory)), | |
| 128 extension_id(std::move(other.extension_id)) {} | |
| 129 | |
| 130 ComponentLoader::ComponentExtensionInfo& | |
| 131 ComponentLoader::ComponentExtensionInfo::operator=( | |
| 132 ComponentExtensionInfo&& other) { | |
| 133 manifest = std::move(other.manifest); | |
| 134 root_directory = std::move(other.root_directory); | |
| 135 extension_id = std::move(other.extension_id); | |
| 136 return *this; | |
| 137 } | |
| 138 | |
| 139 ComponentLoader::ComponentExtensionInfo::~ComponentExtensionInfo() {} | |
| 140 | |
| 124 ComponentLoader::ComponentLoader(ExtensionServiceInterface* extension_service, | 141 ComponentLoader::ComponentLoader(ExtensionServiceInterface* extension_service, |
| 125 PrefService* profile_prefs, | 142 PrefService* profile_prefs, |
| 126 PrefService* local_state, | 143 PrefService* local_state, |
| 127 Profile* profile) | 144 Profile* profile) |
| 128 : profile_prefs_(profile_prefs), | 145 : profile_prefs_(profile_prefs), |
| 129 local_state_(local_state), | 146 local_state_(local_state), |
| 130 profile_(profile), | 147 profile_(profile), |
| 131 extension_service_(extension_service), | 148 extension_service_(extension_service), |
| 132 ignore_whitelist_for_testing_(false), | 149 ignore_whitelist_for_testing_(false), |
| 133 weak_factory_(this) {} | 150 weak_factory_(this) {} |
| 134 | 151 |
| 135 ComponentLoader::~ComponentLoader() { | 152 ComponentLoader::~ComponentLoader() { |
| 136 ClearAllRegistered(); | 153 ClearAllRegistered(); |
| 137 } | 154 } |
| 138 | 155 |
| 139 void ComponentLoader::LoadAll() { | 156 void ComponentLoader::LoadAll() { |
| 140 TRACE_EVENT0("browser,startup", "ComponentLoader::LoadAll"); | 157 TRACE_EVENT0("browser,startup", "ComponentLoader::LoadAll"); |
| 141 SCOPED_UMA_HISTOGRAM_TIMER("Extensions.LoadAllComponentTime"); | 158 SCOPED_UMA_HISTOGRAM_TIMER("Extensions.LoadAllComponentTime"); |
| 142 | 159 |
| 143 for (RegisteredComponentExtensions::iterator it = | 160 for (RegisteredComponentExtensions::iterator it = |
| 144 component_extensions_.begin(); | 161 component_extensions_.begin(); |
| 145 it != component_extensions_.end(); ++it) { | 162 it != component_extensions_.end(); ++it) { |
| 146 Load(*it); | 163 Load(*it); |
| 147 } | 164 } |
| 148 } | 165 } |
| 149 | 166 |
| 150 base::DictionaryValue* ComponentLoader::ParseManifest( | 167 std::unique_ptr<base::DictionaryValue> ComponentLoader::ParseManifest( |
| 151 base::StringPiece manifest_contents) const { | 168 base::StringPiece manifest_contents) const { |
| 152 JSONStringValueDeserializer deserializer(manifest_contents); | 169 JSONStringValueDeserializer deserializer(manifest_contents); |
| 153 std::unique_ptr<base::Value> manifest = deserializer.Deserialize(NULL, NULL); | 170 std::unique_ptr<base::Value> manifest = deserializer.Deserialize(NULL, NULL); |
| 154 | 171 |
| 155 if (!manifest.get() || !manifest->IsType(base::Value::TYPE_DICTIONARY)) { | 172 if (!manifest.get() || !manifest->IsType(base::Value::TYPE_DICTIONARY)) { |
| 156 LOG(ERROR) << "Failed to parse extension manifest."; | 173 LOG(ERROR) << "Failed to parse extension manifest."; |
| 157 return NULL; | 174 return std::unique_ptr<base::DictionaryValue>(); |
| 158 } | 175 } |
| 159 // Transfer ownership to the caller. | 176 return base::WrapUnique( |
| 160 return static_cast<base::DictionaryValue*>(manifest.release()); | 177 static_cast<base::DictionaryValue*>(manifest.release())); |
|
Devlin
2016/11/17 16:02:54
static casting base::Values isn't really preferred
lazyboy
2016/11/17 22:50:38
Done.
| |
| 161 } | 178 } |
| 162 | 179 |
| 163 void ComponentLoader::ClearAllRegistered() { | 180 void ComponentLoader::ClearAllRegistered() { |
|
Devlin
2016/11/17 16:02:54
This is only called from the dtor, and component_e
lazyboy
2016/11/17 22:50:37
Correct.
Done.
| |
| 164 for (RegisteredComponentExtensions::iterator it = | |
| 165 component_extensions_.begin(); | |
| 166 it != component_extensions_.end(); ++it) { | |
| 167 delete it->manifest; | |
| 168 } | |
| 169 | |
| 170 component_extensions_.clear(); | 181 component_extensions_.clear(); |
| 171 } | 182 } |
| 172 | 183 |
| 173 std::string ComponentLoader::GetExtensionID( | |
| 174 int manifest_resource_id, | |
| 175 const base::FilePath& root_directory) { | |
| 176 base::DictionaryValue* manifest = | |
| 177 ParseManifest(ResourceBundle::GetSharedInstance().GetRawDataResource( | |
| 178 manifest_resource_id)); | |
| 179 if (!manifest) | |
| 180 return std::string(); | |
| 181 | |
| 182 ComponentExtensionInfo info(manifest, root_directory); | |
| 183 return info.extension_id; | |
| 184 } | |
| 185 | |
| 186 std::string ComponentLoader::Add(int manifest_resource_id, | 184 std::string ComponentLoader::Add(int manifest_resource_id, |
| 187 const base::FilePath& root_directory) { | 185 const base::FilePath& root_directory) { |
| 188 if (!ignore_whitelist_for_testing_ && | 186 if (!ignore_whitelist_for_testing_ && |
| 189 !IsComponentExtensionWhitelisted(manifest_resource_id)) | 187 !IsComponentExtensionWhitelisted(manifest_resource_id)) |
| 190 return std::string(); | 188 return std::string(); |
| 191 | 189 |
| 192 base::StringPiece manifest_contents = | 190 base::StringPiece manifest_contents = |
| 193 ResourceBundle::GetSharedInstance().GetRawDataResource( | 191 ResourceBundle::GetSharedInstance().GetRawDataResource( |
| 194 manifest_resource_id); | 192 manifest_resource_id); |
| 195 return Add(manifest_contents, root_directory, true); | 193 return Add(manifest_contents, root_directory, true); |
| 196 } | 194 } |
| 197 | 195 |
| 198 std::string ComponentLoader::Add(const base::StringPiece& manifest_contents, | 196 std::string ComponentLoader::Add(const base::StringPiece& manifest_contents, |
| 199 const base::FilePath& root_directory) { | 197 const base::FilePath& root_directory) { |
| 200 return Add(manifest_contents, root_directory, false); | 198 return Add(manifest_contents, root_directory, false); |
| 201 } | 199 } |
| 202 | 200 |
| 203 std::string ComponentLoader::Add(const base::StringPiece& manifest_contents, | 201 std::string ComponentLoader::Add(const base::StringPiece& manifest_contents, |
| 204 const base::FilePath& root_directory, | 202 const base::FilePath& root_directory, |
| 205 bool skip_whitelist) { | 203 bool skip_whitelist) { |
| 206 // The Value is kept for the lifetime of the ComponentLoader. This is | 204 // The Value is kept for the lifetime of the ComponentLoader. This is |
| 207 // required in case LoadAll() is called again. | 205 // required in case LoadAll() is called again. |
| 208 base::DictionaryValue* manifest = ParseManifest(manifest_contents); | 206 std::unique_ptr<base::DictionaryValue> manifest = |
| 207 ParseManifest(manifest_contents); | |
| 209 if (manifest) | 208 if (manifest) |
| 210 return Add(manifest, root_directory, skip_whitelist); | 209 return Add(std::move(manifest), root_directory, skip_whitelist); |
| 211 return std::string(); | 210 return std::string(); |
| 212 } | 211 } |
| 213 | 212 |
| 214 std::string ComponentLoader::Add(const base::DictionaryValue* parsed_manifest, | 213 std::string ComponentLoader::Add( |
| 215 const base::FilePath& root_directory, | 214 std::unique_ptr<base::DictionaryValue> parsed_manifest, |
| 216 bool skip_whitelist) { | 215 const base::FilePath& root_directory, |
| 217 ComponentExtensionInfo info(parsed_manifest, root_directory); | 216 bool skip_whitelist) { |
| 217 ComponentExtensionInfo info(std::move(parsed_manifest), root_directory); | |
| 218 if (!ignore_whitelist_for_testing_ && | 218 if (!ignore_whitelist_for_testing_ && |
| 219 !skip_whitelist && | 219 !skip_whitelist && |
| 220 !IsComponentExtensionWhitelisted(info.extension_id)) | 220 !IsComponentExtensionWhitelisted(info.extension_id)) |
| 221 return std::string(); | 221 return std::string(); |
| 222 | 222 |
| 223 component_extensions_.push_back(info); | 223 component_extensions_.push_back(std::move(info)); |
| 224 ComponentExtensionInfo& added_info = component_extensions_.back(); | |
| 224 if (extension_service_->is_ready()) | 225 if (extension_service_->is_ready()) |
| 225 Load(info); | 226 Load(added_info); |
| 226 return info.extension_id; | 227 return added_info.extension_id; |
| 227 } | 228 } |
| 228 | 229 |
| 229 std::string ComponentLoader::AddOrReplace(const base::FilePath& path) { | 230 std::string ComponentLoader::AddOrReplace(const base::FilePath& path) { |
| 230 base::FilePath absolute_path = base::MakeAbsoluteFilePath(path); | 231 base::FilePath absolute_path = base::MakeAbsoluteFilePath(path); |
| 231 std::string error; | 232 std::string error; |
| 232 std::unique_ptr<base::DictionaryValue> manifest( | 233 std::unique_ptr<base::DictionaryValue> manifest( |
| 233 file_util::LoadManifest(absolute_path, &error)); | 234 file_util::LoadManifest(absolute_path, &error)); |
| 234 if (!manifest) { | 235 if (!manifest) { |
| 235 LOG(ERROR) << "Could not load extension from '" << | 236 LOG(ERROR) << "Could not load extension from '" << |
| 236 absolute_path.value() << "'. " << error; | 237 absolute_path.value() << "'. " << error; |
| 237 return std::string(); | 238 return std::string(); |
| 238 } | 239 } |
| 239 Remove(GenerateId(manifest.get(), absolute_path)); | 240 Remove(GenerateId(manifest.get(), absolute_path)); |
| 240 | 241 |
| 241 // We don't check component extensions loaded by path because this is only | 242 // We don't check component extensions loaded by path because this is only |
| 242 // used by developers for testing. | 243 // used by developers for testing. |
| 243 return Add(manifest.release(), absolute_path, true); | 244 return Add(std::move(manifest), absolute_path, true); |
| 244 } | 245 } |
| 245 | 246 |
| 246 void ComponentLoader::Reload(const std::string& extension_id) { | 247 void ComponentLoader::Reload(const std::string& extension_id) { |
| 247 for (RegisteredComponentExtensions::iterator it = | 248 for (RegisteredComponentExtensions::iterator it = |
| 248 component_extensions_.begin(); it != component_extensions_.end(); | 249 component_extensions_.begin(); it != component_extensions_.end(); |
| 249 ++it) { | 250 ++it) { |
| 250 if (it->extension_id == extension_id) { | 251 if (it->extension_id == extension_id) { |
| 251 Load(*it); | 252 Load(*it); |
| 252 break; | 253 break; |
| 253 } | 254 } |
| 254 } | 255 } |
| 255 } | 256 } |
| 256 | 257 |
| 257 void ComponentLoader::Load(const ComponentExtensionInfo& info) { | 258 void ComponentLoader::Load(const ComponentExtensionInfo& info) { |
| 258 std::string error; | 259 std::string error; |
| 259 scoped_refptr<const Extension> extension(CreateExtension(info, &error)); | 260 scoped_refptr<const Extension> extension(CreateExtension(info, &error)); |
| 260 if (!extension.get()) { | 261 if (!extension.get()) { |
| 261 LOG(ERROR) << error; | 262 LOG(ERROR) << error; |
| 262 return; | 263 return; |
| 263 } | 264 } |
| 264 | 265 |
| 265 CHECK_EQ(info.extension_id, extension->id()) << extension->name(); | 266 CHECK_EQ(info.extension_id, extension->id()) << extension->name(); |
| 266 extension_service_->AddComponentExtension(extension.get()); | 267 extension_service_->AddComponentExtension(extension.get()); |
| 267 } | 268 } |
| 268 | 269 |
| 269 void ComponentLoader::Remove(const base::FilePath& root_directory) { | 270 void ComponentLoader::Remove(const base::FilePath& root_directory) { |
| 270 // Find the ComponentExtensionInfo for the extension. | 271 // Find the ComponentExtensionInfo for the extension. |
| 271 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); | 272 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); |
|
Devlin
2016/11/17 16:02:54
drive-by - inline this in the loop
lazyboy
2016/11/17 22:50:38
Done.
| |
| 272 for (; it != component_extensions_.end(); ++it) { | 273 for (; it != component_extensions_.end(); ++it) { |
| 273 if (it->root_directory == root_directory) { | 274 if (it->root_directory == root_directory) { |
| 274 Remove(GenerateId(it->manifest, root_directory)); | 275 Remove(GenerateId(it->manifest.get(), root_directory)); |
| 275 break; | 276 break; |
| 276 } | 277 } |
| 277 } | 278 } |
| 278 } | 279 } |
| 279 | 280 |
| 280 void ComponentLoader::Remove(const std::string& id) { | 281 void ComponentLoader::Remove(const std::string& id) { |
| 281 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); | 282 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); |
|
Devlin
2016/11/17 16:02:54
ditto (and below)
lazyboy
2016/11/17 22:50:38
Done.
| |
| 282 for (; it != component_extensions_.end(); ++it) { | 283 for (; it != component_extensions_.end(); ++it) { |
| 283 if (it->extension_id == id) { | 284 if (it->extension_id == id) { |
| 284 UnloadComponent(&(*it)); | 285 UnloadComponent(&(*it)); |
| 285 it = component_extensions_.erase(it); | 286 it = component_extensions_.erase(it); |
|
Devlin
2016/11/17 16:02:54
Drive-by - No reason to assign |it| here
lazyboy
2016/11/17 22:50:38
Done.
| |
| 286 break; | 287 break; |
| 287 } | 288 } |
| 288 } | 289 } |
| 289 } | 290 } |
| 290 | 291 |
| 291 bool ComponentLoader::Exists(const std::string& id) const { | 292 bool ComponentLoader::Exists(const std::string& id) const { |
| 292 RegisteredComponentExtensions::const_iterator it = | 293 RegisteredComponentExtensions::const_iterator it = |
| 293 component_extensions_.begin(); | 294 component_extensions_.begin(); |
| 294 for (; it != component_extensions_.end(); ++it) | 295 for (; it != component_extensions_.end(); ++it) |
| 295 if (it->extension_id == id) | 296 if (it->extension_id == id) |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 393 if (!ignore_whitelist_for_testing_ && | 394 if (!ignore_whitelist_for_testing_ && |
| 394 !IsComponentExtensionWhitelisted(manifest_resource_id)) | 395 !IsComponentExtensionWhitelisted(manifest_resource_id)) |
| 395 return; | 396 return; |
| 396 | 397 |
| 397 base::StringPiece manifest_contents = | 398 base::StringPiece manifest_contents = |
| 398 ResourceBundle::GetSharedInstance().GetRawDataResource( | 399 ResourceBundle::GetSharedInstance().GetRawDataResource( |
| 399 manifest_resource_id); | 400 manifest_resource_id); |
| 400 | 401 |
| 401 // The Value is kept for the lifetime of the ComponentLoader. This is | 402 // The Value is kept for the lifetime of the ComponentLoader. This is |
| 402 // required in case LoadAll() is called again. | 403 // required in case LoadAll() is called again. |
| 403 base::DictionaryValue* manifest = ParseManifest(manifest_contents); | 404 std::unique_ptr<base::DictionaryValue> manifest = |
| 405 ParseManifest(manifest_contents); | |
| 404 | 406 |
| 405 if (manifest) { | 407 if (manifest) { |
| 406 manifest->SetString(manifest_keys::kName, name_string); | 408 manifest->SetString(manifest_keys::kName, name_string); |
| 407 manifest->SetString(manifest_keys::kDescription, description_string); | 409 manifest->SetString(manifest_keys::kDescription, description_string); |
| 408 Add(manifest, root_directory, true); | 410 Add(std::move(manifest), root_directory, true); |
| 409 } | 411 } |
| 410 } | 412 } |
| 411 | 413 |
| 412 void ComponentLoader::AddChromeApp() { | 414 void ComponentLoader::AddChromeApp() { |
| 413 #if BUILDFLAG(ENABLE_APP_LIST) | 415 #if BUILDFLAG(ENABLE_APP_LIST) |
| 414 AddWithNameAndDescription( | 416 AddWithNameAndDescription( |
| 415 IDR_CHROME_APP_MANIFEST, base::FilePath(FILE_PATH_LITERAL("chrome_app")), | 417 IDR_CHROME_APP_MANIFEST, base::FilePath(FILE_PATH_LITERAL("chrome_app")), |
| 416 l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME), | 418 l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME), |
| 417 l10n_util::GetStringUTF8(IDS_CHROME_SHORTCUT_DESCRIPTION)); | 419 l10n_util::GetStringUTF8(IDS_CHROME_SHORTCUT_DESCRIPTION)); |
| 418 #endif | 420 #endif |
| (...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 631 (command_line->HasSwitch(switches::kTestType) || | 633 (command_line->HasSwitch(switches::kTestType) || |
| 632 command_line->HasSwitch( | 634 command_line->HasSwitch( |
| 633 switches::kDisableComponentExtensionsWithBackgroundPages))) { | 635 switches::kDisableComponentExtensionsWithBackgroundPages))) { |
| 634 return; | 636 return; |
| 635 } | 637 } |
| 636 | 638 |
| 637 AddHangoutServicesExtension(); | 639 AddHangoutServicesExtension(); |
| 638 } | 640 } |
| 639 | 641 |
| 640 void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { | 642 void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { |
| 641 delete component->manifest; | |
| 642 if (extension_service_->is_ready()) { | 643 if (extension_service_->is_ready()) { |
| 643 extension_service_-> | 644 extension_service_-> |
| 644 RemoveComponentExtension(component->extension_id); | 645 RemoveComponentExtension(component->extension_id); |
| 645 } | 646 } |
| 646 } | 647 } |
| 647 | 648 |
| 648 void ComponentLoader::EnableFileSystemInGuestMode(const std::string& id) { | 649 void ComponentLoader::EnableFileSystemInGuestMode(const std::string& id) { |
| 649 #if defined(OS_CHROMEOS) | 650 #if defined(OS_CHROMEOS) |
| 650 if (!IsNormalSession()) { | 651 if (!IsNormalSession()) { |
| 651 // TODO(dpolukhin): Hack to enable HTML5 temporary file system for | 652 // TODO(dpolukhin): Hack to enable HTML5 temporary file system for |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 685 } | 686 } |
| 686 | 687 |
| 687 void ComponentLoader::FinishAddComponentFromDir( | 688 void ComponentLoader::FinishAddComponentFromDir( |
| 688 const base::FilePath& root_directory, | 689 const base::FilePath& root_directory, |
| 689 const char* extension_id, | 690 const char* extension_id, |
| 690 const base::Closure& done_cb, | 691 const base::Closure& done_cb, |
| 691 std::unique_ptr<base::DictionaryValue> manifest) { | 692 std::unique_ptr<base::DictionaryValue> manifest) { |
| 692 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 693 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 693 if (!manifest) | 694 if (!manifest) |
| 694 return; // Error already logged. | 695 return; // Error already logged. |
| 695 std::string actual_extension_id = Add( | 696 std::string actual_extension_id = |
| 696 manifest.release(), | 697 Add(std::move(manifest), root_directory, false); |
| 697 root_directory, | |
| 698 false); | |
| 699 CHECK_EQ(extension_id, actual_extension_id); | 698 CHECK_EQ(extension_id, actual_extension_id); |
| 700 if (!done_cb.is_null()) | 699 if (!done_cb.is_null()) |
| 701 done_cb.Run(); | 700 done_cb.Run(); |
| 702 } | 701 } |
| 703 #endif | 702 #endif |
| 704 | 703 |
| 705 } // namespace extensions | 704 } // namespace extensions |
| OLD | NEW |