blob: ed32eb40850f992d562cc8e84374fa9d53957ec1 [file] [log] [blame]
[email protected]31d8f5f22012-04-02 15:22:081// 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.
4
5#include "chrome/browser/extensions/extension_system.h"
6
[email protected]53603d22012-11-14 08:31:117#include "base/base_switches.h"
[email protected]31d8f5f22012-04-02 15:22:088#include "base/bind.h"
9#include "base/command_line.h"
[email protected]57999812013-02-24 05:40:5210#include "base/files/file_path.h"
[email protected]f4ebe772013-02-02 00:21:3911#include "base/strings/string_tokenizer.h"
[email protected]31d8f5f22012-04-02 15:22:0812#include "chrome/browser/browser_process.h"
[email protected]31d8f5f22012-04-02 15:22:0813#include "chrome/browser/content_settings/cookie_settings.h"
[email protected]4868bcc2013-05-29 03:27:2714#include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
[email protected]fdd679b2012-11-15 20:49:3915#include "chrome/browser/extensions/blacklist.h"
[email protected]931186e02012-07-20 01:22:0616#include "chrome/browser/extensions/component_loader.h"
[email protected]5a38dfd2012-07-23 23:22:1017#include "chrome/browser/extensions/event_router.h"
[email protected]31d8f5f22012-04-02 15:22:0818#include "chrome/browser/extensions/extension_error_reporter.h"
[email protected]31d8f5f22012-04-02 15:22:0819#include "chrome/browser/extensions/extension_info_map.h"
[email protected]31d8f5f22012-04-02 15:22:0820#include "chrome/browser/extensions/extension_pref_store.h"
21#include "chrome/browser/extensions/extension_pref_value_map.h"
[email protected]ef9bba12012-04-06 16:26:0922#include "chrome/browser/extensions/extension_pref_value_map_factory.h"
[email protected]836e2982013-05-16 08:07:4223#include "chrome/browser/extensions/extension_prefs.h"
[email protected]31d8f5f22012-04-02 15:22:0824#include "chrome/browser/extensions/extension_process_manager.h"
25#include "chrome/browser/extensions/extension_service.h"
26#include "chrome/browser/extensions/extension_system_factory.h"
[email protected]b4d3771d2012-11-14 14:44:1027#include "chrome/browser/extensions/extension_warning_badge_service.h"
28#include "chrome/browser/extensions/extension_warning_set.h"
[email protected]31d8f5f22012-04-02 15:22:0829#include "chrome/browser/extensions/lazy_background_task_queue.h"
[email protected]65187152012-06-02 13:14:1430#include "chrome/browser/extensions/management_policy.h"
[email protected]d9ede582012-08-14 19:21:3831#include "chrome/browser/extensions/navigation_observer.h"
[email protected]fdd679b2012-11-15 20:49:3932#include "chrome/browser/extensions/standard_management_policy_provider.h"
[email protected]90e800c2012-06-12 23:11:0033#include "chrome/browser/extensions/state_store.h"
[email protected]31d8f5f22012-04-02 15:22:0834#include "chrome/browser/extensions/unpacked_installer.h"
35#include "chrome/browser/extensions/user_script_master.h"
36#include "chrome/browser/profiles/profile.h"
37#include "chrome/browser/profiles/profile_manager.h"
[email protected]31d8f5f22012-04-02 15:22:0838#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
39#include "chrome/common/chrome_switches.h"
[email protected]d69d7b172012-08-09 04:17:2640#include "chrome/common/chrome_version_info.h"
[email protected]31d8f5f22012-04-02 15:22:0841#include "chrome/common/extensions/extension.h"
[email protected]5115ccc2012-06-11 19:47:0742#include "chrome/common/extensions/features/feature.h"
[email protected]1d5e58b2013-01-31 08:41:4043#include "chrome/common/extensions/manifest.h"
[email protected]31d8f5f22012-04-02 15:22:0844#include "content/public/browser/browser_thread.h"
[email protected]24ea7a12013-01-27 23:54:5345#include "content/public/browser/url_data_source.h"
[email protected]836e2982013-05-16 08:07:4246#include "extensions/common/constants.h"
[email protected]31d8f5f22012-04-02 15:22:0847
[email protected]bb121482012-12-08 06:49:3848#if defined(OS_CHROMEOS)
[email protected]6ed2e0c2013-04-09 04:48:1349#include "chrome/browser/app_mode/app_mode_utils.h"
[email protected]931d1042013-04-05 17:50:4450#include "chromeos/chromeos_switches.h"
[email protected]c57397a2013-04-18 19:41:1151#include "chromeos/login/login_state.h"
[email protected]bb121482012-12-08 06:49:3852#endif
53
[email protected]31d8f5f22012-04-02 15:22:0854using content::BrowserThread;
55
[email protected]bd306722012-07-11 20:43:5956namespace extensions {
57
[email protected]31d8f5f22012-04-02 15:22:0858//
59// ExtensionSystem
60//
61
62ExtensionSystem::ExtensionSystem() {
[email protected]d57a089c2012-08-16 00:26:4363 // Only set if it hasn't already been set (e.g. by a test).
64 if (Feature::GetCurrentChannel() == Feature::GetDefaultChannel())
65 Feature::SetCurrentChannel(chrome::VersionInfo::GetChannel());
[email protected]31d8f5f22012-04-02 15:22:0866}
67
68ExtensionSystem::~ExtensionSystem() {
69}
70
[email protected]749d59a2012-04-05 00:23:2471// static
72ExtensionSystem* ExtensionSystem::Get(Profile* profile) {
73 return ExtensionSystemFactory::GetForProfile(profile);
74}
75
[email protected]31d8f5f22012-04-02 15:22:0876//
77// ExtensionSystemImpl::Shared
78//
79
80ExtensionSystemImpl::Shared::Shared(Profile* profile)
81 : profile_(profile) {
82}
83
84ExtensionSystemImpl::Shared::~Shared() {
85}
86
87void ExtensionSystemImpl::Shared::InitPrefs() {
[email protected]c4dc5cc2012-11-09 08:48:3988 lazy_background_task_queue_.reset(new LazyBackgroundTaskQueue(profile_));
[email protected]836e2982013-05-16 08:07:4289 event_router_.reset(new EventRouter(profile_, ExtensionPrefs::Get(profile_)));
[email protected]90e800c2012-06-12 23:11:0090
[email protected]a690e292012-12-19 19:22:4991 // Two state stores. The latter, which contains declarative rules, must be
92 // loaded immediately so that the rules are ready before we issue network
93 // requests.
[email protected]bd306722012-07-11 20:43:5994 state_store_.reset(new StateStore(
[email protected]90e800c2012-06-12 23:11:0095 profile_,
[email protected]a690e292012-12-19 19:22:4996 profile_->GetPath().AppendASCII(ExtensionService::kStateStoreName),
97 true));
98 rules_store_.reset(new StateStore(
99 profile_,
100 profile_->GetPath().AppendASCII(ExtensionService::kRulesStoreName),
101 false));
[email protected]cb610dc2012-08-31 17:16:56102
[email protected]836e2982013-05-16 08:07:42103 blacklist_.reset(new Blacklist(ExtensionPrefs::Get(profile_)));
[email protected]fdd679b2012-11-15 20:49:39104
105 standard_management_policy_provider_.reset(
[email protected]836e2982013-05-16 08:07:42106 new StandardManagementPolicyProvider(ExtensionPrefs::Get(profile_)));
[email protected]31d8f5f22012-04-02 15:22:08107}
108
[email protected]65187152012-06-02 13:14:14109void ExtensionSystemImpl::Shared::RegisterManagementPolicyProviders() {
[email protected]fdd679b2012-11-15 20:49:39110 DCHECK(standard_management_policy_provider_.get());
111 management_policy_->RegisterProvider(
112 standard_management_policy_provider_.get());
[email protected]65187152012-06-02 13:14:14113}
114
[email protected]31d8f5f22012-04-02 15:22:08115void ExtensionSystemImpl::Shared::Init(bool extensions_enabled) {
116 const CommandLine* command_line = CommandLine::ForCurrentProcess();
117
[email protected]d9ede582012-08-14 19:21:38118 navigation_observer_.reset(new NavigationObserver(profile_));
[email protected]31d8f5f22012-04-02 15:22:08119
[email protected]53603d22012-11-14 08:31:11120 bool allow_noisy_errors = !command_line->HasSwitch(switches::kNoErrorDialogs);
121 ExtensionErrorReporter::Init(allow_noisy_errors);
[email protected]31d8f5f22012-04-02 15:22:08122
123 user_script_master_ = new UserScriptMaster(profile_);
124
125 bool autoupdate_enabled = true;
126#if defined(OS_CHROMEOS)
127 if (!extensions_enabled)
128 autoupdate_enabled = false;
129 else
[email protected]931d1042013-04-05 17:50:44130 autoupdate_enabled =
131 !command_line->HasSwitch(chromeos::switches::kGuestSession);
[email protected]31d8f5f22012-04-02 15:22:08132#endif
133 extension_service_.reset(new ExtensionService(
134 profile_,
135 CommandLine::ForCurrentProcess(),
[email protected]836e2982013-05-16 08:07:42136 profile_->GetPath().AppendASCII(extensions::kInstallDirectoryName),
137 ExtensionPrefs::Get(profile_),
[email protected]fdd679b2012-11-15 20:49:39138 blacklist_.get(),
[email protected]31d8f5f22012-04-02 15:22:08139 autoupdate_enabled,
[email protected]4a10006a2013-05-17 23:18:35140 extensions_enabled,
141 &ready_));
[email protected]31d8f5f22012-04-02 15:22:08142
[email protected]d7fbc092012-06-18 22:52:00143 // These services must be registered before the ExtensionService tries to
144 // load any extensions.
145 {
[email protected]bd306722012-07-11 20:43:59146 management_policy_.reset(new ManagementPolicy);
[email protected]d7fbc092012-06-18 22:52:00147 RegisterManagementPolicyProviders();
148 }
[email protected]65187152012-06-02 13:14:14149
[email protected]bb121482012-12-08 06:49:38150 bool skip_session_extensions = false;
151#if defined(OS_CHROMEOS)
152 // Skip loading session extensions if we are not in a user session.
[email protected]c57397a2013-04-18 19:41:11153 skip_session_extensions = !chromeos::LoginState::Get()->IsUserLoggedIn();
[email protected]6ed2e0c2013-04-09 04:48:13154 if (!chrome::IsRunningInForcedAppMode()) {
155 extension_service_->component_loader()->AddDefaultComponentExtensions(
156 skip_session_extensions);
157 }
158#else
[email protected]bb121482012-12-08 06:49:38159 extension_service_->component_loader()->AddDefaultComponentExtensions(
160 skip_session_extensions);
[email protected]6ed2e0c2013-04-09 04:48:13161#endif
[email protected]31d8f5f22012-04-02 15:22:08162 if (command_line->HasSwitch(switches::kLoadComponentExtension)) {
163 CommandLine::StringType path_list = command_line->GetSwitchValueNative(
164 switches::kLoadComponentExtension);
[email protected]f4ebe772013-02-02 00:21:39165 base::StringTokenizerT<CommandLine::StringType,
[email protected]31d8f5f22012-04-02 15:22:08166 CommandLine::StringType::const_iterator> t(path_list,
167 FILE_PATH_LITERAL(","));
168 while (t.GetNext()) {
169 // Load the component extension manifest synchronously.
170 // Blocking the UI thread is acceptable here since
171 // this flag designated for developers.
172 base::ThreadRestrictions::ScopedAllowIO allow_io;
173 extension_service_->component_loader()->AddOrReplace(
[email protected]650b2d52013-02-10 03:41:45174 base::FilePath(t.token()));
[email protected]31d8f5f22012-04-02 15:22:08175 }
176 }
177 extension_service_->Init();
178
179 if (extensions_enabled) {
180 // Load any extensions specified with --load-extension.
181 // TODO(yoz): Seems like this should move into ExtensionService::Init.
182 // But maybe it's no longer important.
183 if (command_line->HasSwitch(switches::kLoadExtension)) {
184 CommandLine::StringType path_list = command_line->GetSwitchValueNative(
185 switches::kLoadExtension);
[email protected]f4ebe772013-02-02 00:21:39186 base::StringTokenizerT<CommandLine::StringType,
[email protected]31d8f5f22012-04-02 15:22:08187 CommandLine::StringType::const_iterator> t(path_list,
[email protected]dd6d36672013-05-17 22:50:02188 FILE_PATH_LITERAL(","));
[email protected]31d8f5f22012-04-02 15:22:08189 while (t.GetNext()) {
[email protected]2a69b942013-05-31 09:37:53190 std::string extension_id;
[email protected]98270432012-09-11 20:51:24191 UnpackedInstaller::Create(extension_service_.get())->
[email protected]2a69b942013-05-31 09:37:53192 LoadFromCommandLine(base::FilePath(t.token()), &extension_id);
[email protected]31d8f5f22012-04-02 15:22:08193 }
194 }
195 }
196
197 // Make the chrome://extension-icon/ resource available.
[email protected]24ea7a12013-01-27 23:54:53198 content::URLDataSource::Add(profile_, new ExtensionIconSource(profile_));
[email protected]31d8f5f22012-04-02 15:22:08199
200 // Initialize extension event routers. Note that on Chrome OS, this will
201 // not succeed if the user has not logged in yet, in which case the
202 // event routers are initialized in LoginUtilsImpl::CompleteLogin instead.
203 // The InitEventRouters call used to be in BrowserMain, because when bookmark
204 // import happened on first run, the bookmark bar was not being correctly
205 // initialized (see issue 40144). Now that bookmarks aren't imported and
206 // the event routers need to be initialized for every profile individually,
207 // initialize them with the extension service.
[email protected]ae4c37e2012-12-21 01:16:25208 // If import is going to run in a separate process (the profile itself is on
209 // the main process), wait for import to finish before initializing the
210 // routers.
[email protected]ae4c37e2012-12-21 01:16:25211 if (g_browser_process->profile_manager()->will_import()) {
212 extension_service_->InitEventRoutersAfterImport();
213 } else {
214 extension_service_->InitEventRouters();
[email protected]31d8f5f22012-04-02 15:22:08215 }
[email protected]2def34cd2012-12-08 07:07:54216
217 extension_warning_service_.reset(new ExtensionWarningService(profile_));
218 extension_warning_badge_service_.reset(
219 new ExtensionWarningBadgeService(profile_));
220 extension_warning_service_->AddObserver(
221 extension_warning_badge_service_.get());
[email protected]31d8f5f22012-04-02 15:22:08222}
223
[email protected]47b896562012-08-22 23:55:15224void ExtensionSystemImpl::Shared::Shutdown() {
[email protected]3eeddd892013-04-17 17:00:11225 if (extension_warning_service_) {
[email protected]2def34cd2012-12-08 07:07:54226 extension_warning_service_->RemoveObserver(
227 extension_warning_badge_service_.get());
228 }
[email protected]3eeddd892013-04-17 17:00:11229 if (extension_service_)
[email protected]47b896562012-08-22 23:55:15230 extension_service_->Shutdown();
231}
232
[email protected]bd306722012-07-11 20:43:59233StateStore* ExtensionSystemImpl::Shared::state_store() {
[email protected]90e800c2012-06-12 23:11:00234 return state_store_.get();
235}
236
[email protected]a690e292012-12-19 19:22:49237StateStore* ExtensionSystemImpl::Shared::rules_store() {
238 return rules_store_.get();
239}
240
[email protected]31d8f5f22012-04-02 15:22:08241ExtensionService* ExtensionSystemImpl::Shared::extension_service() {
242 return extension_service_.get();
243}
244
[email protected]bd306722012-07-11 20:43:59245ManagementPolicy* ExtensionSystemImpl::Shared::management_policy() {
[email protected]65187152012-06-02 13:14:14246 return management_policy_.get();
247}
248
[email protected]31d8f5f22012-04-02 15:22:08249UserScriptMaster* ExtensionSystemImpl::Shared::user_script_master() {
250 return user_script_master_.get();
251}
252
253ExtensionInfoMap* ExtensionSystemImpl::Shared::info_map() {
[email protected]dc24976f2013-06-02 21:15:09254 if (!extension_info_map_.get())
[email protected]9656bc52012-08-13 17:05:33255 extension_info_map_ = new ExtensionInfoMap();
[email protected]31d8f5f22012-04-02 15:22:08256 return extension_info_map_.get();
257}
258
[email protected]bd306722012-07-11 20:43:59259LazyBackgroundTaskQueue*
260 ExtensionSystemImpl::Shared::lazy_background_task_queue() {
[email protected]31d8f5f22012-04-02 15:22:08261 return lazy_background_task_queue_.get();
262}
263
[email protected]5a38dfd2012-07-23 23:22:10264EventRouter* ExtensionSystemImpl::Shared::event_router() {
[email protected]c4dc5cc2012-11-09 08:48:39265 return event_router_.get();
[email protected]31d8f5f22012-04-02 15:22:08266}
267
[email protected]2def34cd2012-12-08 07:07:54268ExtensionWarningService* ExtensionSystemImpl::Shared::warning_service() {
269 return extension_warning_service_.get();
270}
271
[email protected]fdd679b2012-11-15 20:49:39272Blacklist* ExtensionSystemImpl::Shared::blacklist() {
273 return blacklist_.get();
274}
275
[email protected]31d8f5f22012-04-02 15:22:08276//
277// ExtensionSystemImpl
278//
279
280ExtensionSystemImpl::ExtensionSystemImpl(Profile* profile)
[email protected]98b673032012-12-11 10:26:02281 : profile_(profile) {
[email protected]31d8f5f22012-04-02 15:22:08282 shared_ = ExtensionSystemSharedFactory::GetForProfile(profile);
283
284 if (profile->IsOffTheRecord()) {
285 extension_process_manager_.reset(ExtensionProcessManager::Create(profile));
286 } else {
287 shared_->InitPrefs();
288 }
289}
290
291ExtensionSystemImpl::~ExtensionSystemImpl() {
[email protected]4868bcc2013-05-29 03:27:27292 if (rules_registry_service_)
293 rules_registry_service_->Shutdown();
[email protected]31d8f5f22012-04-02 15:22:08294}
295
296void ExtensionSystemImpl::Shutdown() {
297 extension_process_manager_.reset();
298}
299
[email protected]3dfa4c02012-07-30 17:21:41300void ExtensionSystemImpl::InitForRegularProfile(bool extensions_enabled) {
[email protected]31d8f5f22012-04-02 15:22:08301 DCHECK(!profile_->IsOffTheRecord());
302 if (user_script_master() || extension_service())
303 return; // Already initialized.
304
[email protected]9656bc52012-08-13 17:05:33305 // The ExtensionInfoMap needs to be created before the
306 // ExtensionProcessManager.
307 shared_->info_map();
[email protected]31d8f5f22012-04-02 15:22:08308
309 extension_process_manager_.reset(ExtensionProcessManager::Create(profile_));
[email protected]931186e02012-07-20 01:22:06310
311 serial_connection_manager_.reset(new ApiResourceManager<SerialConnection>(
312 BrowserThread::FILE));
313 socket_manager_.reset(new ApiResourceManager<Socket>(BrowserThread::IO));
314 usb_device_resource_manager_.reset(
315 new ApiResourceManager<UsbDeviceResource>(BrowserThread::IO));
[email protected]31d8f5f22012-04-02 15:22:08316
[email protected]4868bcc2013-05-29 03:27:27317 rules_registry_service_.reset(new RulesRegistryService(profile_));
318 rules_registry_service_->RegisterDefaultRulesRegistries();
319
[email protected]31d8f5f22012-04-02 15:22:08320 shared_->Init(extensions_enabled);
321}
322
[email protected]4868bcc2013-05-29 03:27:27323void ExtensionSystemImpl::InitForOTRProfile() {
324 // Only initialize the RulesRegistryService of the OTR ExtensionSystem if the
325 // regular ExtensionSystem has been initialized properly, as we depend on it.
326 // Some ChromeOS browser tests don't initialize the regular ExtensionSystem
327 // in login-tests.
328 if (extension_service()) {
329 rules_registry_service_.reset(new RulesRegistryService(profile_));
330 rules_registry_service_->RegisterDefaultRulesRegistries();
331 }
332}
333
[email protected]31d8f5f22012-04-02 15:22:08334ExtensionService* ExtensionSystemImpl::extension_service() {
335 return shared_->extension_service();
336}
337
[email protected]bd306722012-07-11 20:43:59338ManagementPolicy* ExtensionSystemImpl::management_policy() {
[email protected]65187152012-06-02 13:14:14339 return shared_->management_policy();
340}
341
[email protected]31d8f5f22012-04-02 15:22:08342UserScriptMaster* ExtensionSystemImpl::user_script_master() {
343 return shared_->user_script_master();
344}
345
[email protected]31d8f5f22012-04-02 15:22:08346ExtensionProcessManager* ExtensionSystemImpl::process_manager() {
347 return extension_process_manager_.get();
348}
349
[email protected]bd306722012-07-11 20:43:59350StateStore* ExtensionSystemImpl::state_store() {
[email protected]90e800c2012-06-12 23:11:00351 return shared_->state_store();
352}
353
[email protected]a690e292012-12-19 19:22:49354StateStore* ExtensionSystemImpl::rules_store() {
355 return shared_->rules_store();
356}
357
[email protected]31d8f5f22012-04-02 15:22:08358ExtensionInfoMap* ExtensionSystemImpl::info_map() {
359 return shared_->info_map();
360}
361
[email protected]bd306722012-07-11 20:43:59362LazyBackgroundTaskQueue* ExtensionSystemImpl::lazy_background_task_queue() {
[email protected]31d8f5f22012-04-02 15:22:08363 return shared_->lazy_background_task_queue();
364}
365
[email protected]5a38dfd2012-07-23 23:22:10366EventRouter* ExtensionSystemImpl::event_router() {
[email protected]31d8f5f22012-04-02 15:22:08367 return shared_->event_router();
368}
369
[email protected]4868bcc2013-05-29 03:27:27370RulesRegistryService* ExtensionSystemImpl::rules_registry_service() {
371 return rules_registry_service_.get();
372}
373
[email protected]931186e02012-07-20 01:22:06374ApiResourceManager<SerialConnection>*
375ExtensionSystemImpl::serial_connection_manager() {
376 return serial_connection_manager_.get();
377}
378
[email protected]3dfa4c02012-07-30 17:21:41379ApiResourceManager<Socket>* ExtensionSystemImpl::socket_manager() {
[email protected]931186e02012-07-20 01:22:06380 return socket_manager_.get();
381}
382
383ApiResourceManager<UsbDeviceResource>*
384ExtensionSystemImpl::usb_device_resource_manager() {
385 return usb_device_resource_manager_.get();
386}
387
[email protected]b4d3771d2012-11-14 14:44:10388ExtensionWarningService* ExtensionSystemImpl::warning_service() {
[email protected]2def34cd2012-12-08 07:07:54389 return shared_->warning_service();
[email protected]b4d3771d2012-11-14 14:44:10390}
391
[email protected]fdd679b2012-11-15 20:49:39392Blacklist* ExtensionSystemImpl::blacklist() {
393 return shared_->blacklist();
394}
395
[email protected]4a10006a2013-05-17 23:18:35396const OneShotEvent& ExtensionSystemImpl::ready() const {
397 return shared_->ready();
398}
399
[email protected]31d8f5f22012-04-02 15:22:08400void ExtensionSystemImpl::RegisterExtensionWithRequestContexts(
[email protected]bd306722012-07-11 20:43:59401 const Extension* extension) {
[email protected]31d8f5f22012-04-02 15:22:08402 base::Time install_time;
[email protected]1d5e58b2013-01-31 08:41:40403 if (extension->location() != Manifest::COMPONENT) {
[email protected]836e2982013-05-16 08:07:42404 install_time = ExtensionPrefs::Get(profile_)->
[email protected]31d8f5f22012-04-02 15:22:08405 GetInstallTime(extension->id());
406 }
407 bool incognito_enabled =
408 extension_service()->IsIncognitoEnabled(extension->id());
409 BrowserThread::PostTask(
410 BrowserThread::IO, FROM_HERE,
411 base::Bind(&ExtensionInfoMap::AddExtension, info_map(),
412 make_scoped_refptr(extension), install_time,
413 incognito_enabled));
414}
415
416void ExtensionSystemImpl::UnregisterExtensionWithRequestContexts(
417 const std::string& extension_id,
418 const extension_misc::UnloadedExtensionReason reason) {
419 BrowserThread::PostTask(
420 BrowserThread::IO, FROM_HERE,
421 base::Bind(&ExtensionInfoMap::RemoveExtension, info_map(),
422 extension_id, reason));
423}
[email protected]bd306722012-07-11 20:43:59424
425} // namespace extensions