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/extension_service.h" | 5 #include "chrome/browser/extensions/extension_service.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <iterator> | |
| 8 #include <set> | 9 #include <set> |
| 9 | 10 |
| 10 #include "base/basictypes.h" | 11 #include "base/basictypes.h" |
| 11 #include "base/bind.h" | 12 #include "base/bind.h" |
| 12 #include "base/callback.h" | 13 #include "base/callback.h" |
| 13 #include "base/command_line.h" | 14 #include "base/command_line.h" |
| 14 #include "base/file_util.h" | 15 #include "base/file_util.h" |
| 15 #include "base/logging.h" | 16 #include "base/logging.h" |
| 16 #include "base/metrics/histogram.h" | 17 #include "base/metrics/histogram.h" |
| 17 #include "base/path_service.h" | 18 #include "base/path_service.h" |
| (...skipping 339 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 357 const CommandLine* command_line, | 358 const CommandLine* command_line, |
| 358 const FilePath& install_directory, | 359 const FilePath& install_directory, |
| 359 extensions::ExtensionPrefs* extension_prefs, | 360 extensions::ExtensionPrefs* extension_prefs, |
| 360 extensions::Blacklist* blacklist, | 361 extensions::Blacklist* blacklist, |
| 361 bool autoupdate_enabled, | 362 bool autoupdate_enabled, |
| 362 bool extensions_enabled) | 363 bool extensions_enabled) |
| 363 : extensions::Blacklist::Observer(blacklist), | 364 : extensions::Blacklist::Observer(blacklist), |
| 364 profile_(profile), | 365 profile_(profile), |
| 365 system_(extensions::ExtensionSystem::Get(profile)), | 366 system_(extensions::ExtensionSystem::Get(profile)), |
| 366 extension_prefs_(extension_prefs), | 367 extension_prefs_(extension_prefs), |
| 368 blacklist_(blacklist), | |
| 367 settings_frontend_(extensions::SettingsFrontend::Create(profile)), | 369 settings_frontend_(extensions::SettingsFrontend::Create(profile)), |
| 368 pending_extension_manager_(*ALLOW_THIS_IN_INITIALIZER_LIST(this)), | 370 pending_extension_manager_(*ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| 369 install_directory_(install_directory), | 371 install_directory_(install_directory), |
| 370 extensions_enabled_(extensions_enabled), | 372 extensions_enabled_(extensions_enabled), |
| 371 show_extensions_prompts_(true), | 373 show_extensions_prompts_(true), |
| 372 install_updates_when_idle_(true), | 374 install_updates_when_idle_(true), |
| 373 ready_(false), | 375 ready_(false), |
| 374 toolbar_model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), | 376 toolbar_model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| 375 menu_manager_(profile), | 377 menu_manager_(profile), |
| 376 app_notification_manager_( | 378 app_notification_manager_( |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 462 } | 464 } |
| 463 | 465 |
| 464 const ExtensionSet* ExtensionService::disabled_extensions() const { | 466 const ExtensionSet* ExtensionService::disabled_extensions() const { |
| 465 return &disabled_extensions_; | 467 return &disabled_extensions_; |
| 466 } | 468 } |
| 467 | 469 |
| 468 const ExtensionSet* ExtensionService::terminated_extensions() const { | 470 const ExtensionSet* ExtensionService::terminated_extensions() const { |
| 469 return &terminated_extensions_; | 471 return &terminated_extensions_; |
| 470 } | 472 } |
| 471 | 473 |
| 472 const ExtensionSet* ExtensionService::GenerateInstalledExtensionsSet() const { | 474 const ExtensionSet* ExtensionService::blacklisted_extensions() const { |
| 473 ExtensionSet* installed_extensions = new ExtensionSet(); | 475 return &blacklisted_extensions_; |
| 476 } | |
| 477 | |
| 478 scoped_ptr<const ExtensionSet> | |
| 479 ExtensionService::GenerateInstalledExtensionsSet() const { | |
| 480 scoped_ptr<ExtensionSet> installed_extensions(new ExtensionSet()); | |
| 474 installed_extensions->InsertAll(extensions_); | 481 installed_extensions->InsertAll(extensions_); |
| 475 installed_extensions->InsertAll(disabled_extensions_); | 482 installed_extensions->InsertAll(disabled_extensions_); |
| 476 installed_extensions->InsertAll(terminated_extensions_); | 483 installed_extensions->InsertAll(terminated_extensions_); |
| 477 return installed_extensions; | 484 return installed_extensions.PassAs<const ExtensionSet>(); |
| 478 } | 485 } |
| 479 | 486 |
| 480 const ExtensionSet* ExtensionService::GetWipedOutExtensions() const { | 487 scoped_ptr<const ExtensionSet> ExtensionService::GetWipedOutExtensions() const { |
| 481 ExtensionSet* extension_set = new ExtensionSet(); | 488 scoped_ptr<ExtensionSet> extension_set(new ExtensionSet()); |
| 482 for (ExtensionSet::const_iterator iter = disabled_extensions_.begin(); | 489 for (ExtensionSet::const_iterator iter = disabled_extensions_.begin(); |
| 483 iter != disabled_extensions_.end(); ++iter) { | 490 iter != disabled_extensions_.end(); ++iter) { |
| 484 int disabled_reason = extension_prefs_->GetDisableReasons((*iter)->id()); | 491 int disabled_reason = extension_prefs_->GetDisableReasons((*iter)->id()); |
| 485 if ((disabled_reason & Extension::DISABLE_SIDELOAD_WIPEOUT) != 0) | 492 if ((disabled_reason & Extension::DISABLE_SIDELOAD_WIPEOUT) != 0) |
| 486 extension_set->Insert(*iter); | 493 extension_set->Insert(*iter); |
| 487 } | 494 } |
| 488 return extension_set; | 495 return extension_set.PassAs<const ExtensionSet>(); |
| 489 } | 496 } |
| 490 | 497 |
| 491 extensions::PendingExtensionManager* | 498 extensions::PendingExtensionManager* |
| 492 ExtensionService::pending_extension_manager() { | 499 ExtensionService::pending_extension_manager() { |
| 493 return &pending_extension_manager_; | 500 return &pending_extension_manager_; |
| 494 } | 501 } |
| 495 | 502 |
| 496 ExtensionService::~ExtensionService() { | 503 ExtensionService::~ExtensionService() { |
| 497 // No need to unload extensions here because they are profile-scoped, and the | 504 // No need to unload extensions here because they are profile-scoped, and the |
| 498 // profile is in the process of being deleted. | 505 // profile is in the process of being deleted. |
| (...skipping 384 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 883 return true; | 890 return true; |
| 884 } | 891 } |
| 885 | 892 |
| 886 bool ExtensionService::IsExtensionEnabled( | 893 bool ExtensionService::IsExtensionEnabled( |
| 887 const std::string& extension_id) const { | 894 const std::string& extension_id) const { |
| 888 if (extensions_.Contains(extension_id) || | 895 if (extensions_.Contains(extension_id) || |
| 889 terminated_extensions_.Contains(extension_id)) { | 896 terminated_extensions_.Contains(extension_id)) { |
| 890 return true; | 897 return true; |
| 891 } | 898 } |
| 892 | 899 |
| 893 if (disabled_extensions_.Contains(extension_id)) | 900 if (disabled_extensions_.Contains(extension_id) || |
| 901 blacklisted_extensions_.Contains(extension_id)) { | |
| 894 return false; | 902 return false; |
| 903 } | |
| 895 | 904 |
| 896 // If the extension hasn't been loaded yet, check the prefs for it. Assume | 905 // If the extension hasn't been loaded yet, check the prefs for it. Assume |
| 897 // enabled unless otherwise noted. | 906 // enabled unless otherwise noted. |
| 898 return !extension_prefs_->IsExtensionDisabled(extension_id) && | 907 return !extension_prefs_->IsExtensionDisabled(extension_id) && |
| 899 !extension_prefs_->IsExternalExtensionUninstalled(extension_id); | 908 !extension_prefs_->IsExternalExtensionUninstalled(extension_id); |
| 900 } | 909 } |
| 901 | 910 |
| 902 bool ExtensionService::IsExternalExtensionUninstalled( | 911 bool ExtensionService::IsExternalExtensionUninstalled( |
| 903 const std::string& extension_id) const { | 912 const std::string& extension_id) const { |
| 904 return extension_prefs_->IsExternalExtensionUninstalled(extension_id); | 913 return extension_prefs_->IsExternalExtensionUninstalled(extension_id); |
| (...skipping 355 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1260 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); | 1269 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); |
| 1261 return file_task_runner_; | 1270 return file_task_runner_; |
| 1262 } | 1271 } |
| 1263 | 1272 |
| 1264 extensions::ExtensionUpdater* ExtensionService::updater() { | 1273 extensions::ExtensionUpdater* ExtensionService::updater() { |
| 1265 return updater_.get(); | 1274 return updater_.get(); |
| 1266 } | 1275 } |
| 1267 | 1276 |
| 1268 void ExtensionService::CheckManagementPolicy() { | 1277 void ExtensionService::CheckManagementPolicy() { |
| 1269 std::vector<std::string> to_be_removed; | 1278 std::vector<std::string> to_be_removed; |
| 1279 | |
| 1270 // Loop through extensions list, unload installed extensions. | 1280 // Loop through extensions list, unload installed extensions. |
| 1271 for (ExtensionSet::const_iterator iter = extensions_.begin(); | 1281 for (ExtensionSet::const_iterator iter = extensions_.begin(); |
| 1272 iter != extensions_.end(); ++iter) { | 1282 iter != extensions_.end(); ++iter) { |
| 1273 const Extension* extension = (*iter); | 1283 const Extension* extension = (*iter); |
| 1274 if (!system_->management_policy()->UserMayLoad(extension, NULL)) | 1284 if (!system_->management_policy()->UserMayLoad(extension, NULL)) |
| 1275 to_be_removed.push_back(extension->id()); | 1285 to_be_removed.push_back(extension->id()); |
| 1276 } | 1286 } |
| 1277 | 1287 |
| 1278 // UnloadExtension will change the extensions_ list. So, we should | 1288 // UnloadExtension will change the extensions_ list. So, we should |
| 1279 // call it outside the iterator loop. | 1289 // call it outside the iterator loop. |
| (...skipping 543 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1823 | 1833 |
| 1824 UpdateExternalExtensionAlert(); | 1834 UpdateExternalExtensionAlert(); |
| 1825 | 1835 |
| 1826 if (!did_show_alert) | 1836 if (!did_show_alert) |
| 1827 extension_error_ui_.reset(); | 1837 extension_error_ui_.reset(); |
| 1828 } | 1838 } |
| 1829 | 1839 |
| 1830 bool ExtensionService::PopulateExtensionErrorUI( | 1840 bool ExtensionService::PopulateExtensionErrorUI( |
| 1831 ExtensionErrorUI* extension_error_ui) { | 1841 ExtensionErrorUI* extension_error_ui) { |
| 1832 bool needs_alert = false; | 1842 bool needs_alert = false; |
| 1843 | |
| 1833 for (ExtensionSet::const_iterator iter = extensions_.begin(); | 1844 for (ExtensionSet::const_iterator iter = extensions_.begin(); |
| 1834 iter != extensions_.end(); ++iter) { | 1845 iter != extensions_.end(); ++iter) { |
| 1835 const Extension* e = *iter; | 1846 const Extension* e = *iter; |
| 1847 | |
| 1848 // Extensions disabled by policy. Note: this no longer includes blacklisted | |
| 1849 // extensions, though we still show the same UI. | |
| 1836 if (!system_->management_policy()->UserMayLoad(e, NULL)) { | 1850 if (!system_->management_policy()->UserMayLoad(e, NULL)) { |
| 1837 if (!extension_prefs_->IsBlacklistedExtensionAcknowledged(e->id())) { | 1851 if (!extension_prefs_->IsBlacklistedExtensionAcknowledged(e->id())) { |
| 1838 extension_error_ui->AddBlacklistedExtension(e->id()); | 1852 extension_error_ui->AddBlacklistedExtension(e->id()); |
| 1839 needs_alert = true; | 1853 needs_alert = true; |
| 1840 } | 1854 } |
| 1841 } | 1855 } |
| 1856 | |
| 1857 // Orphaned extensions. | |
| 1842 if (extension_prefs_->IsExtensionOrphaned(e->id())) { | 1858 if (extension_prefs_->IsExtensionOrphaned(e->id())) { |
| 1843 if (!extension_prefs_->IsOrphanedExtensionAcknowledged(e->id())) { | 1859 if (!extension_prefs_->IsOrphanedExtensionAcknowledged(e->id())) { |
| 1844 extension_error_ui->AddOrphanedExtension(e->id()); | 1860 extension_error_ui->AddOrphanedExtension(e->id()); |
| 1845 needs_alert = true; | 1861 needs_alert = true; |
| 1846 } | 1862 } |
| 1847 } | 1863 } |
| 1848 } | 1864 } |
| 1865 | |
| 1866 // Extensions that really are blacklisted. | |
|
Yoyo Zhou
2012/11/30 23:44:07
I was confused reading the "disabled by policy" bl
not at google - send to devlin
2012/12/01 02:51:30
Done.
| |
| 1867 for (ExtensionSet::const_iterator it = blacklisted_extensions_.begin(); | |
| 1868 it != blacklisted_extensions_.end(); ++it) { | |
| 1869 std::string id = (*it)->id(); | |
| 1870 if (!extension_prefs_->IsBlacklistedExtensionAcknowledged(id)) { | |
| 1871 extension_error_ui->AddBlacklistedExtension(id); | |
| 1872 needs_alert = true; | |
| 1873 } | |
| 1874 } | |
| 1875 | |
| 1849 return needs_alert; | 1876 return needs_alert; |
| 1850 } | 1877 } |
| 1851 | 1878 |
| 1852 void ExtensionService::HandleExtensionAlertClosed() { | 1879 void ExtensionService::HandleExtensionAlertClosed() { |
| 1853 extension_error_ui_.reset(); | 1880 extension_error_ui_.reset(); |
| 1854 } | 1881 } |
| 1855 | 1882 |
| 1856 void ExtensionService::HandleExtensionAlertAccept() { | 1883 void ExtensionService::HandleExtensionAlertAccept() { |
| 1857 const ExtensionIdSet* extension_ids = | 1884 const ExtensionIdSet* extension_ids = |
| 1858 extension_error_ui_->get_blacklisted_extension_ids(); | 1885 extension_error_ui_->get_blacklisted_extension_ids(); |
| (...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2078 EnableExtension(extension->id()); | 2105 EnableExtension(extension->id()); |
| 2079 | 2106 |
| 2080 // Check if the extension's privileges have changed and disable the | 2107 // Check if the extension's privileges have changed and disable the |
| 2081 // extension if necessary. | 2108 // extension if necessary. |
| 2082 InitializePermissions(extension); | 2109 InitializePermissions(extension); |
| 2083 | 2110 |
| 2084 // If this extension is a sideloaded extension and we've not performed a | 2111 // If this extension is a sideloaded extension and we've not performed a |
| 2085 // wipeout before, we might disable this extension here. | 2112 // wipeout before, we might disable this extension here. |
| 2086 MaybeWipeout(extension); | 2113 MaybeWipeout(extension); |
| 2087 | 2114 |
| 2088 if (extension_prefs_->IsExtensionDisabled(extension->id())) { | 2115 // Communicated to the Blacklist. |
| 2116 std::set<std::string> already_in_blacklist; | |
| 2117 | |
| 2118 if (extension_prefs_->IsExtensionBlacklisted(extension->id())) { | |
| 2119 // Don't check the Blacklist here because it's asynchronous (we do it at | |
|
Yoyo Zhou
2012/11/30 23:44:07
nit: s/here/yet/
not at google - send to devlin
2012/12/01 02:51:30
Done.
| |
| 2120 // the end). This pre-emptive check is because we will always store the | |
| 2121 // blacklisted state of *installed* extensions in prefs, and it's important | |
| 2122 // not to re-enable blacklisted extensions. | |
| 2123 blacklisted_extensions_.Insert(extension); | |
| 2124 already_in_blacklist.insert(extension->id()); | |
| 2125 } else if (extension_prefs_->IsExtensionDisabled(extension->id())) { | |
| 2089 disabled_extensions_.Insert(extension); | 2126 disabled_extensions_.Insert(extension); |
| 2090 SyncExtensionChangeIfNeeded(*extension); | 2127 SyncExtensionChangeIfNeeded(*extension); |
| 2091 content::NotificationService::current()->Notify( | 2128 content::NotificationService::current()->Notify( |
| 2092 chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED, | 2129 chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED, |
| 2093 content::Source<Profile>(profile_), | 2130 content::Source<Profile>(profile_), |
| 2094 content::Details<const Extension>(extension)); | 2131 content::Details<const Extension>(extension)); |
| 2095 | 2132 |
| 2096 if (extension_prefs_->GetDisableReasons(extension->id()) & | 2133 if (extension_prefs_->GetDisableReasons(extension->id()) & |
| 2097 Extension::DISABLE_PERMISSIONS_INCREASE) { | 2134 Extension::DISABLE_PERMISSIONS_INCREASE) { |
| 2098 extensions::AddExtensionDisabledError(this, extension); | 2135 extensions::AddExtensionDisabledError(this, extension); |
| 2099 } | 2136 } |
| 2100 return; | 2137 } else { |
| 2138 // All apps that are displayed in the launcher are ordered by their ordinals | |
| 2139 // so we must ensure they have valid ordinals. | |
| 2140 if (extension->RequiresSortOrdinal()) { | |
| 2141 extension_prefs_->extension_sorting()->EnsureValidOrdinals( | |
| 2142 extension->id(), syncer::StringOrdinal()); | |
| 2143 } | |
| 2144 | |
| 2145 extensions_.Insert(extension); | |
| 2146 SyncExtensionChangeIfNeeded(*extension); | |
| 2147 NotifyExtensionLoaded(extension); | |
| 2148 DoPostLoadTasks(extension); | |
| 2101 } | 2149 } |
| 2102 | 2150 |
| 2103 // All apps that are displayed in the launcher are ordered by their ordinals | 2151 // Lastly, begin the process for checking the blacklist status of extensions. |
| 2104 // so we must ensure they have valid ordinals. | 2152 // This may need to go to other threads so is asynchronous. |
| 2105 if (extension->RequiresSortOrdinal()) { | 2153 blacklist_->IsBlacklisted( |
| 2106 extension_prefs_->extension_sorting()->EnsureValidOrdinals( | 2154 extension->id(), |
| 2107 extension->id(), syncer::StringOrdinal()); | 2155 base::Bind(&ExtensionService::ManageBlacklist, |
| 2108 } | 2156 AsWeakPtr(), |
| 2109 | 2157 already_in_blacklist)); |
| 2110 extensions_.Insert(extension); | |
| 2111 SyncExtensionChangeIfNeeded(*extension); | |
| 2112 NotifyExtensionLoaded(extension); | |
| 2113 DoPostLoadTasks(extension); | |
| 2114 } | 2158 } |
| 2115 | 2159 |
| 2116 void ExtensionService::AddComponentExtension(const Extension* extension) { | 2160 void ExtensionService::AddComponentExtension(const Extension* extension) { |
| 2117 const std::string old_version_string( | 2161 const std::string old_version_string( |
| 2118 extension_prefs_->GetVersionString(extension->id())); | 2162 extension_prefs_->GetVersionString(extension->id())); |
| 2119 const Version old_version(old_version_string); | 2163 const Version old_version(old_version_string); |
| 2120 | 2164 |
| 2121 if (!old_version.IsValid() || !old_version.Equals(*extension->version())) { | 2165 if (!old_version.IsValid() || !old_version.Equals(*extension->version())) { |
| 2122 VLOG(1) << "Component extension " << extension->name() << " (" | 2166 VLOG(1) << "Component extension " << extension->name() << " (" |
| 2123 << extension->id() << ") installing/upgrading from '" | 2167 << extension->id() << ") installing/upgrading from '" |
| (...skipping 900 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3024 // event. | 3068 // event. |
| 3025 return system_->event_router()->ExtensionHasEventListener( | 3069 return system_->event_router()->ExtensionHasEventListener( |
| 3026 extension_id, kOnUpdateAvailableEvent); | 3070 extension_id, kOnUpdateAvailableEvent); |
| 3027 } else { | 3071 } else { |
| 3028 // Delay installation if the extension is not idle. | 3072 // Delay installation if the extension is not idle. |
| 3029 return !IsExtensionIdle(extension_id); | 3073 return !IsExtensionIdle(extension_id); |
| 3030 } | 3074 } |
| 3031 } | 3075 } |
| 3032 | 3076 |
| 3033 void ExtensionService::OnBlacklistUpdated() { | 3077 void ExtensionService::OnBlacklistUpdated() { |
| 3034 CheckManagementPolicy(); | 3078 blacklist_->IsBlacklisted( |
| 3079 GenerateInstalledExtensionsSet()->GetIDs(), | |
| 3080 base::Bind(&ExtensionService::ManageBlacklist, | |
| 3081 AsWeakPtr(), | |
| 3082 blacklisted_extensions_.GetIDs())); | |
| 3035 } | 3083 } |
| 3084 | |
| 3085 void ExtensionService::ManageBlacklist( | |
| 3086 const std::set<std::string>& old_blacklisted_ids, | |
| 3087 const std::set<std::string>& new_blacklisted_ids) { | |
| 3088 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 3089 | |
| 3090 std::set<std::string> no_longer_blacklisted; | |
| 3091 std::set_difference(old_blacklisted_ids.begin(), old_blacklisted_ids.end(), | |
| 3092 new_blacklisted_ids.begin(), new_blacklisted_ids.end(), | |
| 3093 std::inserter(no_longer_blacklisted, | |
| 3094 no_longer_blacklisted.begin())); | |
| 3095 std::set<std::string> not_yet_blacklisted; | |
| 3096 std::set_difference(new_blacklisted_ids.begin(), new_blacklisted_ids.end(), | |
| 3097 old_blacklisted_ids.begin(), old_blacklisted_ids.end(), | |
| 3098 std::inserter(not_yet_blacklisted, | |
| 3099 not_yet_blacklisted.begin())); | |
| 3100 | |
| 3101 for (std::set<std::string>::iterator it = no_longer_blacklisted.begin(); | |
| 3102 it != no_longer_blacklisted.end(); ++it) { | |
| 3103 scoped_refptr<const Extension> extension = | |
| 3104 blacklisted_extensions_.GetByID(*it); | |
| 3105 if (!extension) | |
| 3106 continue; | |
| 3107 blacklisted_extensions_.Remove(*it); | |
| 3108 AddExtension(extension); | |
| 3109 } | |
| 3110 | |
| 3111 for (std::set<std::string>::iterator it = not_yet_blacklisted.begin(); | |
| 3112 it != not_yet_blacklisted.end(); ++it) { | |
| 3113 scoped_refptr<const Extension> extension = GetInstalledExtension(*it); | |
| 3114 if (!extension) | |
| 3115 continue; | |
| 3116 blacklisted_extensions_.Insert(extension); | |
| 3117 UnloadExtension(*it, extension_misc::UNLOAD_REASON_BLACKLIST); | |
| 3118 } | |
| 3119 | |
| 3120 IdentifyAlertableExtensions(); | |
| 3121 } | |
| OLD | NEW |