Home | History | Annotate | Download | only in runtime
      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(&params);
    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