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/extensions/extension_service.h" 11#include "chrome/browser/extensions/updater/extension_updater.h" 12#include "chrome/browser/profiles/profile.h" 13#include "chrome/browser/ui/browser_finder.h" 14#include "chrome/browser/ui/browser_navigator.h" 15#include "chrome/browser/ui/browser_window.h" 16#include "components/omaha_query_params/omaha_query_params.h" 17#include "content/public/browser/notification_service.h" 18#include "extensions/browser/extension_system.h" 19#include "extensions/browser/notification_types.h" 20#include "extensions/browser/warning_service.h" 21#include "extensions/browser/warning_set.h" 22#include "extensions/common/api/runtime.h" 23 24#if defined(OS_CHROMEOS) 25#include "chromeos/dbus/dbus_thread_manager.h" 26#include "chromeos/dbus/power_manager_client.h" 27#include "components/user_manager/user_manager.h" 28#endif 29 30using extensions::Extension; 31using extensions::ExtensionSystem; 32using extensions::ExtensionUpdater; 33 34using extensions::core_api::runtime::PlatformInfo; 35 36namespace { 37 38const char kUpdateThrottled[] = "throttled"; 39const char kUpdateNotFound[] = "no_update"; 40const 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. 44const int kFastReloadTime = 10000; 45 46// After this many suspiciously fast consecutive reloads, an extension will get 47// disabled. 48const int kFastReloadCount = 5; 49 50} // namespace 51 52ChromeRuntimeAPIDelegate::ChromeRuntimeAPIDelegate( 53 content::BrowserContext* context) 54 : browser_context_(context), registered_for_updates_(false) { 55 registrar_.Add(this, 56 extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND, 57 content::NotificationService::AllSources()); 58} 59 60ChromeRuntimeAPIDelegate::~ChromeRuntimeAPIDelegate() { 61} 62 63void ChromeRuntimeAPIDelegate::AddUpdateObserver( 64 extensions::UpdateObserver* observer) { 65 registered_for_updates_ = true; 66 ExtensionSystem::Get(browser_context_) 67 ->extension_service() 68 ->AddUpdateObserver(observer); 69} 70 71void 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 80base::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 91void 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::WarningSet warnings; 124 warnings.insert( 125 extensions::Warning::CreateReloadTooFrequentWarning( 126 extension_id)); 127 base::MessageLoop::current()->PostTask( 128 FROM_HERE, 129 base::Bind(&extensions::WarningService::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 144bool 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 168void ChromeRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) { 169 Profile* profile = Profile::FromBrowserContext(browser_context_); 170 Browser* browser = 171 chrome::FindLastActiveWithProfile(profile, chrome::GetActiveDesktop()); 172 if (!browser) 173 browser = 174 new Browser(Browser::CreateParams(profile, chrome::GetActiveDesktop())); 175 176 chrome::NavigateParams params( 177 browser, uninstall_url, ui::PAGE_TRANSITION_CLIENT_REDIRECT); 178 params.disposition = NEW_FOREGROUND_TAB; 179 params.user_gesture = false; 180 chrome::Navigate(¶ms); 181} 182 183bool ChromeRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) { 184 const char* os = omaha_query_params::OmahaQueryParams::GetOS(); 185 if (strcmp(os, "mac") == 0) { 186 info->os = PlatformInfo::OS_MAC_; 187 } else if (strcmp(os, "win") == 0) { 188 info->os = PlatformInfo::OS_WIN_; 189 } else if (strcmp(os, "cros") == 0) { 190 info->os = PlatformInfo::OS_CROS_; 191 } else if (strcmp(os, "linux") == 0) { 192 info->os = PlatformInfo::OS_LINUX_; 193 } else if (strcmp(os, "openbsd") == 0) { 194 info->os = PlatformInfo::OS_OPENBSD_; 195 } else { 196 NOTREACHED(); 197 return false; 198 } 199 200 const char* arch = omaha_query_params::OmahaQueryParams::GetArch(); 201 if (strcmp(arch, "arm") == 0) { 202 info->arch = PlatformInfo::ARCH_ARM; 203 } else if (strcmp(arch, "x86") == 0) { 204 info->arch = PlatformInfo::ARCH_X86_32; 205 } else if (strcmp(arch, "x64") == 0) { 206 info->arch = PlatformInfo::ARCH_X86_64; 207 } else { 208 NOTREACHED(); 209 return false; 210 } 211 212 const char* nacl_arch = omaha_query_params::OmahaQueryParams::GetNaclArch(); 213 if (strcmp(nacl_arch, "arm") == 0) { 214 info->nacl_arch = PlatformInfo::NACL_ARCH_ARM; 215 } else if (strcmp(nacl_arch, "x86-32") == 0) { 216 info->nacl_arch = PlatformInfo::NACL_ARCH_X86_32; 217 } else if (strcmp(nacl_arch, "x86-64") == 0) { 218 info->nacl_arch = PlatformInfo::NACL_ARCH_X86_64; 219 } else { 220 NOTREACHED(); 221 return false; 222 } 223 224 return true; 225} 226 227bool ChromeRuntimeAPIDelegate::RestartDevice(std::string* error_message) { 228#if defined(OS_CHROMEOS) 229 if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) { 230 chromeos::DBusThreadManager::Get() 231 ->GetPowerManagerClient() 232 ->RequestRestart(); 233 return true; 234 } 235#endif 236 *error_message = "Function available only for ChromeOS kiosk mode."; 237 return false; 238} 239 240void ChromeRuntimeAPIDelegate::Observe( 241 int type, 242 const content::NotificationSource& source, 243 const content::NotificationDetails& details) { 244 DCHECK(type == extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND); 245 typedef const std::pair<std::string, Version> UpdateDetails; 246 const std::string& id = content::Details<UpdateDetails>(details)->first; 247 const Version& version = content::Details<UpdateDetails>(details)->second; 248 if (version.IsValid()) { 249 CallUpdateCallbacks( 250 id, UpdateCheckResult(true, kUpdateFound, version.GetString())); 251 } 252} 253 254void ChromeRuntimeAPIDelegate::UpdateCheckComplete( 255 const std::string& extension_id) { 256 ExtensionSystem* system = ExtensionSystem::Get(browser_context_); 257 ExtensionService* service = system->extension_service(); 258 const Extension* update = service->GetPendingExtensionUpdate(extension_id); 259 if (update) { 260 CallUpdateCallbacks( 261 extension_id, 262 UpdateCheckResult(true, kUpdateFound, update->VersionString())); 263 } else { 264 CallUpdateCallbacks(extension_id, 265 UpdateCheckResult(true, kUpdateNotFound, "")); 266 } 267} 268 269void ChromeRuntimeAPIDelegate::CallUpdateCallbacks( 270 const std::string& extension_id, 271 const UpdateCheckResult& result) { 272 UpdateCallbackList callbacks = pending_update_checks_[extension_id]; 273 pending_update_checks_.erase(extension_id); 274 for (UpdateCallbackList::const_iterator iter = callbacks.begin(); 275 iter != callbacks.end(); 276 ++iter) { 277 const UpdateCheckCallback& callback = *iter; 278 callback.Run(result); 279 } 280} 281