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 "extensions/browser/api/runtime/runtime_api.h"
      6 
      7 #include <utility>
      8 
      9 #include "base/lazy_instance.h"
     10 #include "base/logging.h"
     11 #include "base/memory/scoped_ptr.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/values.h"
     14 #include "base/version.h"
     15 #include "chrome/browser/chrome_notification_types.h"
     16 #include "content/public/browser/browser_context.h"
     17 #include "content/public/browser/child_process_security_policy.h"
     18 #include "content/public/browser/notification_service.h"
     19 #include "content/public/browser/render_process_host.h"
     20 #include "content/public/browser/render_view_host.h"
     21 #include "extensions/browser/api/runtime/runtime_api_delegate.h"
     22 #include "extensions/browser/event_router.h"
     23 #include "extensions/browser/extension_host.h"
     24 #include "extensions/browser/extension_prefs.h"
     25 #include "extensions/browser/extension_registry.h"
     26 #include "extensions/browser/extension_system.h"
     27 #include "extensions/browser/extension_util.h"
     28 #include "extensions/browser/extensions_browser_client.h"
     29 #include "extensions/browser/lazy_background_task_queue.h"
     30 #include "extensions/browser/process_manager.h"
     31 #include "extensions/common/api/runtime.h"
     32 #include "extensions/common/error_utils.h"
     33 #include "extensions/common/extension.h"
     34 #include "extensions/common/manifest_handlers/background_info.h"
     35 #include "extensions/common/manifest_handlers/shared_module_info.h"
     36 #include "url/gurl.h"
     37 #include "webkit/browser/fileapi/isolated_context.h"
     38 
     39 using content::BrowserContext;
     40 
     41 namespace extensions {
     42 
     43 namespace runtime = core_api::runtime;
     44 
     45 namespace {
     46 
     47 const char kNoBackgroundPageError[] = "You do not have a background page.";
     48 const char kPageLoadError[] = "Background page failed to load.";
     49 const char kInstallId[] = "id";
     50 const char kInstallReason[] = "reason";
     51 const char kInstallReasonChromeUpdate[] = "chrome_update";
     52 const char kInstallReasonUpdate[] = "update";
     53 const char kInstallReasonInstall[] = "install";
     54 const char kInstallReasonSharedModuleUpdate[] = "shared_module_update";
     55 const char kInstallPreviousVersion[] = "previousVersion";
     56 const char kInvalidUrlError[] = "Invalid URL.";
     57 const char kPlatformInfoUnavailable[] = "Platform information unavailable.";
     58 
     59 const char kUpdatesDisabledError[] = "Autoupdate is not enabled.";
     60 
     61 // A preference key storing the url loaded when an extension is uninstalled.
     62 const char kUninstallUrl[] = "uninstall_url";
     63 
     64 // The name of the directory to be returned by getPackageDirectoryEntry. This
     65 // particular value does not matter to user code, but is chosen for consistency
     66 // with the equivalent Pepper API.
     67 const char kPackageDirectoryPath[] = "crxfs";
     68 
     69 void DispatchOnStartupEventImpl(BrowserContext* browser_context,
     70                                 const std::string& extension_id,
     71                                 bool first_call,
     72                                 ExtensionHost* host) {
     73   // A NULL host from the LazyBackgroundTaskQueue means the page failed to
     74   // load. Give up.
     75   if (!host && !first_call)
     76     return;
     77 
     78   // Don't send onStartup events to incognito browser contexts.
     79   if (browser_context->IsOffTheRecord())
     80     return;
     81 
     82   if (ExtensionsBrowserClient::Get()->IsShuttingDown() ||
     83       !ExtensionsBrowserClient::Get()->IsValidContext(browser_context))
     84     return;
     85   ExtensionSystem* system = ExtensionSystem::Get(browser_context);
     86   if (!system)
     87     return;
     88 
     89   // If this is a persistent background page, we want to wait for it to load
     90   // (it might not be ready, since this is startup). But only enqueue once.
     91   // If it fails to load the first time, don't bother trying again.
     92   const Extension* extension =
     93       ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID(
     94           extension_id);
     95   if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) &&
     96       first_call &&
     97       system->lazy_background_task_queue()->ShouldEnqueueTask(browser_context,
     98                                                               extension)) {
     99     system->lazy_background_task_queue()->AddPendingTask(
    100         browser_context,
    101         extension_id,
    102         base::Bind(
    103             &DispatchOnStartupEventImpl, browser_context, extension_id, false));
    104     return;
    105   }
    106 
    107   scoped_ptr<base::ListValue> event_args(new base::ListValue());
    108   scoped_ptr<Event> event(
    109       new Event(runtime::OnStartup::kEventName, event_args.Pass()));
    110   system->event_router()->DispatchEventToExtension(extension_id, event.Pass());
    111 }
    112 
    113 void SetUninstallURL(ExtensionPrefs* prefs,
    114                      const std::string& extension_id,
    115                      const std::string& url_string) {
    116   prefs->UpdateExtensionPref(
    117       extension_id, kUninstallUrl, new base::StringValue(url_string));
    118 }
    119 
    120 #if defined(ENABLE_EXTENSIONS)
    121 std::string GetUninstallURL(ExtensionPrefs* prefs,
    122                             const std::string& extension_id) {
    123   std::string url_string;
    124   prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string);
    125   return url_string;
    126 }
    127 #endif  // defined(ENABLE_EXTENSIONS)
    128 
    129 }  // namespace
    130 
    131 ///////////////////////////////////////////////////////////////////////////////
    132 
    133 static base::LazyInstance<BrowserContextKeyedAPIFactory<RuntimeAPI> >
    134     g_factory = LAZY_INSTANCE_INITIALIZER;
    135 
    136 // static
    137 BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() {
    138   return g_factory.Pointer();
    139 }
    140 
    141 RuntimeAPI::RuntimeAPI(content::BrowserContext* context)
    142     : browser_context_(context), dispatch_chrome_updated_event_(false) {
    143   registrar_.Add(this,
    144                  chrome::NOTIFICATION_EXTENSIONS_READY,
    145                  content::Source<BrowserContext>(context));
    146   registrar_.Add(this,
    147                  chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
    148                  content::Source<BrowserContext>(context));
    149   registrar_.Add(this,
    150                  chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED,
    151                  content::Source<BrowserContext>(context));
    152   registrar_.Add(this,
    153                  chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED,
    154                  content::Source<BrowserContext>(context));
    155 
    156   delegate_ = ExtensionsBrowserClient::Get()->CreateRuntimeAPIDelegate(
    157       browser_context_);
    158 
    159   // Check if registered events are up-to-date. We can only do this once
    160   // per browser context, since it updates internal state when called.
    161   dispatch_chrome_updated_event_ =
    162       ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_);
    163 }
    164 
    165 RuntimeAPI::~RuntimeAPI() {
    166   delegate_->RemoveUpdateObserver(this);
    167 }
    168 
    169 void RuntimeAPI::Observe(int type,
    170                          const content::NotificationSource& source,
    171                          const content::NotificationDetails& details) {
    172   switch (type) {
    173     case chrome::NOTIFICATION_EXTENSIONS_READY: {
    174       OnExtensionsReady();
    175       break;
    176     }
    177     case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
    178       const Extension* extension =
    179           content::Details<const Extension>(details).ptr();
    180       OnExtensionLoaded(extension);
    181       break;
    182     }
    183     case chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED: {
    184       const Extension* extension =
    185           content::Details<const InstalledExtensionInfo>(details)->extension;
    186       OnExtensionInstalled(extension);
    187       break;
    188     }
    189     case chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED: {
    190       const Extension* extension =
    191           content::Details<const Extension>(details).ptr();
    192       OnExtensionUninstalled(extension);
    193       break;
    194     }
    195     default:
    196       NOTREACHED();
    197       break;
    198   }
    199 }
    200 
    201 void RuntimeAPI::OnExtensionsReady() {
    202   // We're done restarting Chrome after an update.
    203   dispatch_chrome_updated_event_ = false;
    204 
    205   delegate_->AddUpdateObserver(this);
    206 
    207   // RuntimeAPI is redirected in incognito, so |browser_context_| is never
    208   // incognito. We don't observe incognito ProcessManagers but that is OK
    209   // because we don't send onStartup events to incognito browser contexts.
    210   DCHECK(!browser_context_->IsOffTheRecord());
    211   // Some tests use partially constructed Profiles without a process manager.
    212   ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_);
    213   if (extension_system->process_manager())
    214     extension_system->process_manager()->AddObserver(this);
    215 }
    216 
    217 void RuntimeAPI::OnExtensionLoaded(const Extension* extension) {
    218   if (!dispatch_chrome_updated_event_)
    219     return;
    220 
    221   // Dispatch the onInstalled event with reason "chrome_update".
    222   base::MessageLoop::current()->PostTask(
    223       FROM_HERE,
    224       base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
    225                  browser_context_,
    226                  extension->id(),
    227                  Version(),
    228                  true));
    229 }
    230 
    231 void RuntimeAPI::OnExtensionInstalled(const Extension* extension) {
    232   // Ephemeral apps are not considered to be installed and do not receive
    233   // the onInstalled() event.
    234   if (util::IsEphemeralApp(extension->id(), browser_context_))
    235     return;
    236 
    237   Version old_version = delegate_->GetPreviousExtensionVersion(extension);
    238 
    239   // Dispatch the onInstalled event.
    240   base::MessageLoop::current()->PostTask(
    241       FROM_HERE,
    242       base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
    243                  browser_context_,
    244                  extension->id(),
    245                  old_version,
    246                  false));
    247 }
    248 
    249 void RuntimeAPI::OnExtensionUninstalled(const Extension* extension) {
    250   // Ephemeral apps are not considered to be installed, so the uninstall URL
    251   // is not invoked when they are removed.
    252   if (util::IsEphemeralApp(extension->id(), browser_context_))
    253     return;
    254 
    255   RuntimeEventRouter::OnExtensionUninstalled(browser_context_, extension->id());
    256 }
    257 
    258 void RuntimeAPI::Shutdown() {
    259   // ExtensionSystem deletes its ProcessManager during the Shutdown() phase, so
    260   // the observer must be removed here and not in the RuntimeAPI destructor.
    261   ProcessManager* process_manager =
    262       ExtensionSystem::Get(browser_context_)->process_manager();
    263   // Some tests use partially constructed Profiles without a process manager.
    264   if (process_manager)
    265     process_manager->RemoveObserver(this);
    266 }
    267 
    268 void RuntimeAPI::OnAppUpdateAvailable(const Extension* extension) {
    269   RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
    270       browser_context_, extension->id(), extension->manifest()->value());
    271 }
    272 
    273 void RuntimeAPI::OnChromeUpdateAvailable() {
    274   RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(browser_context_);
    275 }
    276 
    277 void RuntimeAPI::OnBackgroundHostStartup(const Extension* extension) {
    278   RuntimeEventRouter::DispatchOnStartupEvent(browser_context_, extension->id());
    279 }
    280 
    281 void RuntimeAPI::ReloadExtension(const std::string& extension_id) {
    282   delegate_->ReloadExtension(extension_id);
    283 }
    284 
    285 bool RuntimeAPI::CheckForUpdates(
    286     const std::string& extension_id,
    287     const RuntimeAPIDelegate::UpdateCheckCallback& callback) {
    288   return delegate_->CheckForUpdates(extension_id, callback);
    289 }
    290 
    291 void RuntimeAPI::OpenURL(const GURL& update_url) {
    292   delegate_->OpenURL(update_url);
    293 }
    294 
    295 bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo* info) {
    296   return delegate_->GetPlatformInfo(info);
    297 }
    298 
    299 bool RuntimeAPI::RestartDevice(std::string* error_message) {
    300   return delegate_->RestartDevice(error_message);
    301 }
    302 
    303 ///////////////////////////////////////////////////////////////////////////////
    304 
    305 // static
    306 void RuntimeEventRouter::DispatchOnStartupEvent(
    307     content::BrowserContext* context,
    308     const std::string& extension_id) {
    309   DispatchOnStartupEventImpl(context, extension_id, true, NULL);
    310 }
    311 
    312 // static
    313 void RuntimeEventRouter::DispatchOnInstalledEvent(
    314     content::BrowserContext* context,
    315     const std::string& extension_id,
    316     const Version& old_version,
    317     bool chrome_updated) {
    318   if (!ExtensionsBrowserClient::Get()->IsValidContext(context))
    319     return;
    320   ExtensionSystem* system = ExtensionSystem::Get(context);
    321   if (!system)
    322     return;
    323 
    324   scoped_ptr<base::ListValue> event_args(new base::ListValue());
    325   base::DictionaryValue* info = new base::DictionaryValue();
    326   event_args->Append(info);
    327   if (old_version.IsValid()) {
    328     info->SetString(kInstallReason, kInstallReasonUpdate);
    329     info->SetString(kInstallPreviousVersion, old_version.GetString());
    330   } else if (chrome_updated) {
    331     info->SetString(kInstallReason, kInstallReasonChromeUpdate);
    332   } else {
    333     info->SetString(kInstallReason, kInstallReasonInstall);
    334   }
    335   DCHECK(system->event_router());
    336   scoped_ptr<Event> event(
    337       new Event(runtime::OnInstalled::kEventName, event_args.Pass()));
    338   system->event_router()->DispatchEventWithLazyListener(extension_id,
    339                                                         event.Pass());
    340 
    341   if (old_version.IsValid()) {
    342     const Extension* extension =
    343         ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
    344             extension_id);
    345     if (extension && SharedModuleInfo::IsSharedModule(extension)) {
    346       scoped_ptr<ExtensionSet> dependents =
    347           system->GetDependentExtensions(extension);
    348       for (ExtensionSet::const_iterator i = dependents->begin();
    349            i != dependents->end();
    350            i++) {
    351         scoped_ptr<base::ListValue> sm_event_args(new base::ListValue());
    352         base::DictionaryValue* sm_info = new base::DictionaryValue();
    353         sm_event_args->Append(sm_info);
    354         sm_info->SetString(kInstallReason, kInstallReasonSharedModuleUpdate);
    355         sm_info->SetString(kInstallPreviousVersion, old_version.GetString());
    356         sm_info->SetString(kInstallId, extension_id);
    357         scoped_ptr<Event> sm_event(
    358             new Event(runtime::OnInstalled::kEventName, sm_event_args.Pass()));
    359         system->event_router()->DispatchEventWithLazyListener((*i)->id(),
    360                                                               sm_event.Pass());
    361       }
    362     }
    363   }
    364 }
    365 
    366 // static
    367 void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
    368     content::BrowserContext* context,
    369     const std::string& extension_id,
    370     const base::DictionaryValue* manifest) {
    371   ExtensionSystem* system = ExtensionSystem::Get(context);
    372   if (!system)
    373     return;
    374 
    375   scoped_ptr<base::ListValue> args(new base::ListValue);
    376   args->Append(manifest->DeepCopy());
    377   DCHECK(system->event_router());
    378   scoped_ptr<Event> event(
    379       new Event(runtime::OnUpdateAvailable::kEventName, args.Pass()));
    380   system->event_router()->DispatchEventToExtension(extension_id, event.Pass());
    381 }
    382 
    383 // static
    384 void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(
    385     content::BrowserContext* context) {
    386   ExtensionSystem* system = ExtensionSystem::Get(context);
    387   if (!system)
    388     return;
    389 
    390   scoped_ptr<base::ListValue> args(new base::ListValue);
    391   DCHECK(system->event_router());
    392   scoped_ptr<Event> event(
    393       new Event(runtime::OnBrowserUpdateAvailable::kEventName, args.Pass()));
    394   system->event_router()->BroadcastEvent(event.Pass());
    395 }
    396 
    397 // static
    398 void RuntimeEventRouter::DispatchOnRestartRequiredEvent(
    399     content::BrowserContext* context,
    400     const std::string& app_id,
    401     core_api::runtime::OnRestartRequired::Reason reason) {
    402   ExtensionSystem* system = ExtensionSystem::Get(context);
    403   if (!system)
    404     return;
    405 
    406   scoped_ptr<Event> event(
    407       new Event(runtime::OnRestartRequired::kEventName,
    408                 core_api::runtime::OnRestartRequired::Create(reason)));
    409 
    410   DCHECK(system->event_router());
    411   system->event_router()->DispatchEventToExtension(app_id, event.Pass());
    412 }
    413 
    414 // static
    415 void RuntimeEventRouter::OnExtensionUninstalled(
    416     content::BrowserContext* context,
    417     const std::string& extension_id) {
    418 #if defined(ENABLE_EXTENSIONS)
    419   GURL uninstall_url(
    420       GetUninstallURL(ExtensionPrefs::Get(context), extension_id));
    421 
    422   if (uninstall_url.is_empty())
    423     return;
    424 
    425   RuntimeAPI::GetFactoryInstance()->Get(context)->OpenURL(uninstall_url);
    426 #endif  // defined(ENABLE_EXTENSIONS)
    427 }
    428 
    429 ExtensionFunction::ResponseAction RuntimeGetBackgroundPageFunction::Run() {
    430   ExtensionSystem* system = ExtensionSystem::Get(browser_context());
    431   ExtensionHost* host =
    432       system->process_manager()->GetBackgroundHostForExtension(extension_id());
    433   if (system->lazy_background_task_queue()->ShouldEnqueueTask(browser_context(),
    434                                                               GetExtension())) {
    435     system->lazy_background_task_queue()->AddPendingTask(
    436         browser_context(),
    437         extension_id(),
    438         base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded, this));
    439   } else if (host) {
    440     OnPageLoaded(host);
    441   } else {
    442     return RespondNow(Error(kNoBackgroundPageError));
    443   }
    444 
    445   return RespondLater();
    446 }
    447 
    448 void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost* host) {
    449   if (host) {
    450     Respond(NoArguments());
    451   } else {
    452     Respond(Error(kPageLoadError));
    453   }
    454 }
    455 
    456 ExtensionFunction::ResponseAction RuntimeSetUninstallURLFunction::Run() {
    457   std::string url_string;
    458   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &url_string));
    459 
    460   GURL url(url_string);
    461   if (!url.is_valid()) {
    462     return RespondNow(
    463         Error(ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string)));
    464   }
    465   SetUninstallURL(
    466       ExtensionPrefs::Get(browser_context()), extension_id(), url_string);
    467   return RespondNow(NoArguments());
    468 }
    469 
    470 ExtensionFunction::ResponseAction RuntimeReloadFunction::Run() {
    471   RuntimeAPI::GetFactoryInstance()->Get(browser_context())->ReloadExtension(
    472       extension_id());
    473   return RespondNow(NoArguments());
    474 }
    475 
    476 ExtensionFunction::ResponseAction RuntimeRequestUpdateCheckFunction::Run() {
    477   if (!RuntimeAPI::GetFactoryInstance()
    478            ->Get(browser_context())
    479            ->CheckForUpdates(
    480                extension_id(),
    481                base::Bind(&RuntimeRequestUpdateCheckFunction::CheckComplete,
    482                           this))) {
    483     return RespondNow(Error(kUpdatesDisabledError));
    484   }
    485   return RespondLater();
    486 }
    487 
    488 void RuntimeRequestUpdateCheckFunction::CheckComplete(
    489     const RuntimeAPIDelegate::UpdateCheckResult& result) {
    490   if (result.success) {
    491     base::DictionaryValue* details = new base::DictionaryValue;
    492     details->SetString("version", result.version);
    493     Respond(TwoArguments(new base::StringValue(result.response), details));
    494   } else {
    495     // HMM(kalman): Why does !success not imply Error()?
    496     Respond(OneArgument(new base::StringValue(result.response)));
    497   }
    498 }
    499 
    500 ExtensionFunction::ResponseAction RuntimeRestartFunction::Run() {
    501   std::string message;
    502   bool result =
    503       RuntimeAPI::GetFactoryInstance()->Get(browser_context())->RestartDevice(
    504           &message);
    505   if (!result) {
    506     return RespondNow(Error(message));
    507   }
    508   return RespondNow(NoArguments());
    509 }
    510 
    511 ExtensionFunction::ResponseAction RuntimeGetPlatformInfoFunction::Run() {
    512   runtime::PlatformInfo info;
    513   if (!RuntimeAPI::GetFactoryInstance()
    514            ->Get(browser_context())
    515            ->GetPlatformInfo(&info)) {
    516     return RespondNow(Error(kPlatformInfoUnavailable));
    517   }
    518   return RespondNow(
    519       ArgumentList(runtime::GetPlatformInfo::Results::Create(info)));
    520 }
    521 
    522 ExtensionFunction::ResponseAction
    523 RuntimeGetPackageDirectoryEntryFunction::Run() {
    524   fileapi::IsolatedContext* isolated_context =
    525       fileapi::IsolatedContext::GetInstance();
    526   DCHECK(isolated_context);
    527 
    528   std::string relative_path = kPackageDirectoryPath;
    529   base::FilePath path = extension_->path();
    530   std::string filesystem_id = isolated_context->RegisterFileSystemForPath(
    531       fileapi::kFileSystemTypeNativeLocal, std::string(), path, &relative_path);
    532 
    533   int renderer_id = render_view_host_->GetProcess()->GetID();
    534   content::ChildProcessSecurityPolicy* policy =
    535       content::ChildProcessSecurityPolicy::GetInstance();
    536   policy->GrantReadFileSystem(renderer_id, filesystem_id);
    537   base::DictionaryValue* dict = new base::DictionaryValue();
    538   dict->SetString("fileSystemId", filesystem_id);
    539   dict->SetString("baseName", relative_path);
    540   return RespondNow(OneArgument(dict));
    541 }
    542 
    543 }  // namespace extensions
    544