1 // Copyright 2014 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/api/runtime/chrome_runtime_api_delegate.h" 6 7 #include "base/message_loop/message_loop.h" 8 #include "base/metrics/histogram.h" 9 #include "base/time/time.h" 10 #include "chrome/browser/chrome_notification_types.h" 11 #include "chrome/browser/extensions/extension_service.h" 12 #include "chrome/browser/extensions/extension_warning_service.h" 13 #include "chrome/browser/extensions/extension_warning_set.h" 14 #include "chrome/browser/extensions/updater/extension_updater.h" 15 #include "chrome/browser/omaha_query_params/omaha_query_params.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/ui/browser_finder.h" 18 #include "chrome/browser/ui/browser_navigator.h" 19 #include "chrome/browser/ui/browser_window.h" 20 #include "content/public/browser/notification_service.h" 21 #include "extensions/browser/extension_system.h" 22 #include "extensions/common/api/runtime.h" 23 24 #if defined(OS_CHROMEOS) 25 #include "chrome/browser/chromeos/login/users/user_manager.h" 26 #include "chromeos/dbus/dbus_thread_manager.h" 27 #include "chromeos/dbus/power_manager_client.h" 28 #endif 29 30 using extensions::Extension; 31 using extensions::ExtensionSystem; 32 using extensions::ExtensionUpdater; 33 34 using extensions::core_api::runtime::PlatformInfo; 35 36 namespace { 37 38 const char kUpdateThrottled[] = "throttled"; 39 const char kUpdateNotFound[] = "no_update"; 40 const char kUpdateFound[] = "update_available"; 41 42 // If an extension reloads itself within this many miliseconds of reloading 43 // itself, the reload is considered suspiciously fast. 44 const int kFastReloadTime = 10000; 45 46 // After this many suspiciously fast consecutive reloads, an extension will get 47 // disabled. 48 const int kFastReloadCount = 5; 49 50 } // namespace 51 52 ChromeRuntimeAPIDelegate::ChromeRuntimeAPIDelegate( 53 content::BrowserContext* context) 54 : browser_context_(context), registered_for_updates_(false) { 55 registrar_.Add(this, 56 chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND, 57 content::NotificationService::AllSources()); 58 } 59 60 ChromeRuntimeAPIDelegate::~ChromeRuntimeAPIDelegate() { 61 } 62 63 void ChromeRuntimeAPIDelegate::AddUpdateObserver( 64 extensions::UpdateObserver* observer) { 65 registered_for_updates_ = true; 66 ExtensionSystem::Get(browser_context_) 67 ->extension_service() 68 ->AddUpdateObserver(observer); 69 } 70 71 void ChromeRuntimeAPIDelegate::RemoveUpdateObserver( 72 extensions::UpdateObserver* observer) { 73 if (registered_for_updates_) { 74 ExtensionSystem::Get(browser_context_) 75 ->extension_service() 76 ->RemoveUpdateObserver(observer); 77 } 78 } 79 80 base::Version ChromeRuntimeAPIDelegate::GetPreviousExtensionVersion( 81 const Extension* extension) { 82 // Get the previous version to check if this is an upgrade. 83 ExtensionService* service = 84 ExtensionSystem::Get(browser_context_)->extension_service(); 85 const Extension* old = service->GetExtensionById(extension->id(), true); 86 if (old) 87 return *old->version(); 88 return base::Version(); 89 } 90 91 void ChromeRuntimeAPIDelegate::ReloadExtension( 92 const std::string& extension_id) { 93 std::pair<base::TimeTicks, int>& reload_info = 94 last_reload_time_[extension_id]; 95 base::TimeTicks now = base::TimeTicks::Now(); 96 if (reload_info.first.is_null() || 97 (now - reload_info.first).InMilliseconds() > kFastReloadTime) { 98 reload_info.second = 0; 99 } else { 100 reload_info.second++; 101 } 102 if (!reload_info.first.is_null()) { 103 UMA_HISTOGRAM_LONG_TIMES("Extensions.RuntimeReloadTime", 104 now - reload_info.first); 105 } 106 UMA_HISTOGRAM_COUNTS_100("Extensions.RuntimeReloadFastCount", 107 reload_info.second); 108 reload_info.first = now; 109 110 ExtensionService* service = 111 ExtensionSystem::Get(browser_context_)->extension_service(); 112 if (reload_info.second >= kFastReloadCount) { 113 // Unloading an extension clears all warnings, so first terminate the 114 // extension, and then add the warning. Since this is called from an 115 // extension function unloading the extension has to be done 116 // asynchronously. Fortunately PostTask guarentees FIFO order so just 117 // post both tasks. 118 base::MessageLoop::current()->PostTask( 119 FROM_HERE, 120 base::Bind(&ExtensionService::TerminateExtension, 121 service->AsWeakPtr(), 122 extension_id)); 123 extensions::ExtensionWarningSet warnings; 124 warnings.insert( 125 extensions::ExtensionWarning::CreateReloadTooFrequentWarning( 126 extension_id)); 127 base::MessageLoop::current()->PostTask( 128 FROM_HERE, 129 base::Bind(&extensions::ExtensionWarningService::NotifyWarningsOnUI, 130 browser_context_, 131 warnings)); 132 } else { 133 // We can't call ReloadExtension directly, since when this method finishes 134 // it tries to decrease the reference count for the extension, which fails 135 // if the extension has already been reloaded; so instead we post a task. 136 base::MessageLoop::current()->PostTask( 137 FROM_HERE, 138 base::Bind(&ExtensionService::ReloadExtension, 139 service->AsWeakPtr(), 140 extension_id)); 141 } 142 } 143 144 bool ChromeRuntimeAPIDelegate::CheckForUpdates( 145 const std::string& extension_id, 146 const UpdateCheckCallback& callback) { 147 ExtensionSystem* system = ExtensionSystem::Get(browser_context_); 148 ExtensionService* service = system->extension_service(); 149 ExtensionUpdater* updater = service->updater(); 150 if (!updater) { 151 return false; 152 } 153 if (!updater->CheckExtensionSoon( 154 extension_id, 155 base::Bind(&ChromeRuntimeAPIDelegate::UpdateCheckComplete, 156 base::Unretained(this), 157 extension_id))) { 158 base::MessageLoop::current()->PostTask( 159 FROM_HERE, 160 base::Bind(callback, UpdateCheckResult(true, kUpdateThrottled, ""))); 161 } else { 162 UpdateCallbackList& callbacks = pending_update_checks_[extension_id]; 163 callbacks.push_back(callback); 164 } 165 return true; 166 } 167 168 void ChromeRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) { 169 #if defined(ENABLE_EXTENSIONS) 170 Profile* profile = Profile::FromBrowserContext(browser_context_); 171 Browser* browser = 172 chrome::FindLastActiveWithProfile(profile, chrome::GetActiveDesktop()); 173 if (!browser) 174 browser = 175 new Browser(Browser::CreateParams(profile, chrome::GetActiveDesktop())); 176 177 chrome::NavigateParams params( 178 browser, uninstall_url, content::PAGE_TRANSITION_CLIENT_REDIRECT); 179 params.disposition = NEW_FOREGROUND_TAB; 180 params.user_gesture = false; 181 chrome::Navigate(¶ms); 182 #endif 183 } 184 185 bool ChromeRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) { 186 const char* os = chrome::OmahaQueryParams::GetOS(); 187 if (strcmp(os, "mac") == 0) { 188 info->os = PlatformInfo::OS_MAC_; 189 } else if (strcmp(os, "win") == 0) { 190 info->os = PlatformInfo::OS_WIN_; 191 } else if (strcmp(os, "android") == 0) { 192 info->os = PlatformInfo::OS_ANDROID_; 193 } else if (strcmp(os, "cros") == 0) { 194 info->os = PlatformInfo::OS_CROS_; 195 } else if (strcmp(os, "linux") == 0) { 196 info->os = PlatformInfo::OS_LINUX_; 197 } else if (strcmp(os, "openbsd") == 0) { 198 info->os = PlatformInfo::OS_OPENBSD_; 199 } else { 200 NOTREACHED(); 201 return false; 202 } 203 204 const char* arch = chrome::OmahaQueryParams::GetArch(); 205 if (strcmp(arch, "arm") == 0) { 206 info->arch = PlatformInfo::ARCH_ARM; 207 } else if (strcmp(arch, "x86") == 0) { 208 info->arch = PlatformInfo::ARCH_X86_32; 209 } else if (strcmp(arch, "x64") == 0) { 210 info->arch = PlatformInfo::ARCH_X86_64; 211 } else { 212 NOTREACHED(); 213 return false; 214 } 215 216 const char* nacl_arch = chrome::OmahaQueryParams::GetNaclArch(); 217 if (strcmp(nacl_arch, "arm") == 0) { 218 info->nacl_arch = PlatformInfo::NACL_ARCH_ARM; 219 } else if (strcmp(nacl_arch, "x86-32") == 0) { 220 info->nacl_arch = PlatformInfo::NACL_ARCH_X86_32; 221 } else if (strcmp(nacl_arch, "x86-64") == 0) { 222 info->nacl_arch = PlatformInfo::NACL_ARCH_X86_64; 223 } else { 224 NOTREACHED(); 225 return false; 226 } 227 228 return true; 229 } 230 231 bool ChromeRuntimeAPIDelegate::RestartDevice(std::string* error_message) { 232 #if defined(OS_CHROMEOS) 233 if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) { 234 chromeos::DBusThreadManager::Get() 235 ->GetPowerManagerClient() 236 ->RequestRestart(); 237 return true; 238 } 239 #endif 240 *error_message = "Function available only for ChromeOS kiosk mode."; 241 return false; 242 } 243 244 void ChromeRuntimeAPIDelegate::Observe( 245 int type, 246 const content::NotificationSource& source, 247 const content::NotificationDetails& details) { 248 DCHECK(type == chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND); 249 typedef const std::pair<std::string, Version> UpdateDetails; 250 const std::string& id = content::Details<UpdateDetails>(details)->first; 251 const Version& version = content::Details<UpdateDetails>(details)->second; 252 if (version.IsValid()) { 253 CallUpdateCallbacks( 254 id, UpdateCheckResult(true, kUpdateFound, version.GetString())); 255 } 256 } 257 258 void ChromeRuntimeAPIDelegate::UpdateCheckComplete( 259 const std::string& extension_id) { 260 ExtensionSystem* system = ExtensionSystem::Get(browser_context_); 261 ExtensionService* service = system->extension_service(); 262 const Extension* update = service->GetPendingExtensionUpdate(extension_id); 263 if (update) { 264 CallUpdateCallbacks( 265 extension_id, 266 UpdateCheckResult(true, kUpdateFound, update->VersionString())); 267 } else { 268 CallUpdateCallbacks(extension_id, 269 UpdateCheckResult(true, kUpdateNotFound, "")); 270 } 271 } 272 273 void ChromeRuntimeAPIDelegate::CallUpdateCallbacks( 274 const std::string& extension_id, 275 const UpdateCheckResult& result) { 276 UpdateCallbackList callbacks = pending_update_checks_[extension_id]; 277 pending_update_checks_.erase(extension_id); 278 for (UpdateCallbackList::const_iterator iter = callbacks.begin(); 279 iter != callbacks.end(); 280 ++iter) { 281 const UpdateCheckCallback& callback = *iter; 282 callback.Run(result); 283 } 284 } 285