// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/runtime/runtime_api.h" #include <utility> #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/updater/extension_updater.h" #include "chrome/browser/omaha_query_params/omaha_query_params.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/common/extensions/api/runtime.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/lazy_background_task_queue.h" #include "extensions/browser/process_manager.h" #include "extensions/common/error_utils.h" #include "extensions/common/extension.h" #include "extensions/common/manifest_handlers/background_info.h" #include "url/gurl.h" #include "webkit/browser/fileapi/isolated_context.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/login/user_manager.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/power_manager_client.h" #endif using content::BrowserContext; namespace GetPlatformInfo = extensions::api::runtime::GetPlatformInfo; namespace extensions { namespace runtime = api::runtime; namespace { const char kNoBackgroundPageError[] = "You do not have a background page."; const char kPageLoadError[] = "Background page failed to load."; const char kInstallReason[] = "reason"; const char kInstallReasonChromeUpdate[] = "chrome_update"; const char kInstallReasonUpdate[] = "update"; const char kInstallReasonInstall[] = "install"; const char kInstallPreviousVersion[] = "previousVersion"; const char kInvalidUrlError[] = "Invalid URL."; const char kUpdatesDisabledError[] = "Autoupdate is not enabled."; const char kUpdateFound[] = "update_available"; const char kUpdateNotFound[] = "no_update"; const char kUpdateThrottled[] = "throttled"; // A preference key storing the url loaded when an extension is uninstalled. const char kUninstallUrl[] = "uninstall_url"; // The name of the directory to be returned by getPackageDirectoryEntry. This // particular value does not matter to user code, but is chosen for consistency // with the equivalent Pepper API. const char kPackageDirectoryPath[] = "crxfs"; void DispatchOnStartupEventImpl(BrowserContext* browser_context, const std::string& extension_id, bool first_call, ExtensionHost* host) { // A NULL host from the LazyBackgroundTaskQueue means the page failed to // load. Give up. if (!host && !first_call) return; // Don't send onStartup events to incognito browser contexts. if (browser_context->IsOffTheRecord()) return; if (ExtensionsBrowserClient::Get()->IsShuttingDown() || !ExtensionsBrowserClient::Get()->IsValidContext(browser_context)) return; ExtensionSystem* system = ExtensionSystem::Get(browser_context); if (!system) return; // If this is a persistent background page, we want to wait for it to load // (it might not be ready, since this is startup). But only enqueue once. // If it fails to load the first time, don't bother trying again. const Extension* extension = ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID( extension_id); if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) && first_call && system->lazy_background_task_queue()-> ShouldEnqueueTask(browser_context, extension)) { system->lazy_background_task_queue()->AddPendingTask( browser_context, extension_id, base::Bind(&DispatchOnStartupEventImpl, browser_context, extension_id, false)); return; } scoped_ptr<base::ListValue> event_args(new base::ListValue()); scoped_ptr<Event> event(new Event(runtime::OnStartup::kEventName, event_args.Pass())); system->event_router()->DispatchEventToExtension(extension_id, event.Pass()); } void SetUninstallURL(ExtensionPrefs* prefs, const std::string& extension_id, const std::string& url_string) { prefs->UpdateExtensionPref(extension_id, kUninstallUrl, new base::StringValue(url_string)); } #if defined(ENABLE_EXTENSIONS) std::string GetUninstallURL(ExtensionPrefs* prefs, const std::string& extension_id) { std::string url_string; prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string); return url_string; } #endif // defined(ENABLE_EXTENSIONS) } // namespace /////////////////////////////////////////////////////////////////////////////// static base::LazyInstance<BrowserContextKeyedAPIFactory<RuntimeAPI> > g_factory = LAZY_INSTANCE_INITIALIZER; // static BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() { return g_factory.Pointer(); } RuntimeAPI::RuntimeAPI(content::BrowserContext* context) : browser_context_(context), dispatch_chrome_updated_event_(false), registered_for_updates_(false) { registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, content::Source<BrowserContext>(context)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, content::Source<BrowserContext>(context)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, content::Source<BrowserContext>(context)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, content::Source<BrowserContext>(context)); // Check if registered events are up-to-date. We can only do this once // per browser context, since it updates internal state when called. dispatch_chrome_updated_event_ = ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_); } RuntimeAPI::~RuntimeAPI() { if (registered_for_updates_) { ExtensionSystem::Get(browser_context_)-> extension_service()->RemoveUpdateObserver(this); } } void RuntimeAPI::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case chrome::NOTIFICATION_EXTENSIONS_READY: { OnExtensionsReady(); break; } case chrome::NOTIFICATION_EXTENSION_LOADED: { const Extension* extension = content::Details<const Extension>(details).ptr(); OnExtensionLoaded(extension); break; } case chrome::NOTIFICATION_EXTENSION_INSTALLED: { const Extension* extension = content::Details<const InstalledExtensionInfo>(details)->extension; OnExtensionInstalled(extension); break; } case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: { const Extension* extension = content::Details<const Extension>(details).ptr(); OnExtensionUninstalled(extension); break; } default: NOTREACHED(); break; } } void RuntimeAPI::OnExtensionsReady() { // We're done restarting Chrome after an update. dispatch_chrome_updated_event_ = false; registered_for_updates_ = true; ExtensionSystem::Get(browser_context_)->extension_service()-> AddUpdateObserver(this); } void RuntimeAPI::OnExtensionLoaded(const Extension* extension) { if (!dispatch_chrome_updated_event_) return; // Dispatch the onInstalled event with reason "chrome_update". base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent, browser_context_, extension->id(), Version(), true)); } void RuntimeAPI::OnExtensionInstalled(const Extension* extension) { // Ephemeral apps are not considered to be installed and do not receive // the onInstalled() event. if (extension->is_ephemeral()) return; // Get the previous version to check if this is an upgrade. ExtensionService* service = ExtensionSystem::Get( browser_context_)->extension_service(); const Extension* old = service->GetExtensionById(extension->id(), true); Version old_version; if (old) old_version = *old->version(); // Dispatch the onInstalled event. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent, browser_context_, extension->id(), old_version, false)); } void RuntimeAPI::OnExtensionUninstalled(const Extension* extension) { // Ephemeral apps are not considered to be installed, so the uninstall URL // is not invoked when they are removed. if (extension->is_ephemeral()) return; Profile* profile = Profile::FromBrowserContext(browser_context_); RuntimeEventRouter::OnExtensionUninstalled(profile, extension->id()); } void RuntimeAPI::OnAppUpdateAvailable(const Extension* extension) { Profile* profile = Profile::FromBrowserContext(browser_context_); RuntimeEventRouter::DispatchOnUpdateAvailableEvent( profile, extension->id(), extension->manifest()->value()); } void RuntimeAPI::OnChromeUpdateAvailable() { Profile* profile = Profile::FromBrowserContext(browser_context_); RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(profile); } /////////////////////////////////////////////////////////////////////////////// // static void RuntimeEventRouter::DispatchOnStartupEvent( content::BrowserContext* context, const std::string& extension_id) { DispatchOnStartupEventImpl(context, extension_id, true, NULL); } // static void RuntimeEventRouter::DispatchOnInstalledEvent( content::BrowserContext* context, const std::string& extension_id, const Version& old_version, bool chrome_updated) { if (!ExtensionsBrowserClient::Get()->IsValidContext(context)) return; ExtensionSystem* system = ExtensionSystem::Get(context); if (!system) return; scoped_ptr<base::ListValue> event_args(new base::ListValue()); base::DictionaryValue* info = new base::DictionaryValue(); event_args->Append(info); if (old_version.IsValid()) { info->SetString(kInstallReason, kInstallReasonUpdate); info->SetString(kInstallPreviousVersion, old_version.GetString()); } else if (chrome_updated) { info->SetString(kInstallReason, kInstallReasonChromeUpdate); } else { info->SetString(kInstallReason, kInstallReasonInstall); } DCHECK(system->event_router()); scoped_ptr<Event> event(new Event(runtime::OnInstalled::kEventName, event_args.Pass())); system->event_router()->DispatchEventWithLazyListener(extension_id, event.Pass()); } // static void RuntimeEventRouter::DispatchOnUpdateAvailableEvent( Profile* profile, const std::string& extension_id, const base::DictionaryValue* manifest) { ExtensionSystem* system = ExtensionSystem::Get(profile); if (!system) return; scoped_ptr<base::ListValue> args(new base::ListValue); args->Append(manifest->DeepCopy()); DCHECK(system->event_router()); scoped_ptr<Event> event(new Event(runtime::OnUpdateAvailable::kEventName, args.Pass())); system->event_router()->DispatchEventToExtension(extension_id, event.Pass()); } // static void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent( Profile* profile) { ExtensionSystem* system = ExtensionSystem::Get(profile); if (!system) return; scoped_ptr<base::ListValue> args(new base::ListValue); DCHECK(system->event_router()); scoped_ptr<Event> event(new Event( runtime::OnBrowserUpdateAvailable::kEventName, args.Pass())); system->event_router()->BroadcastEvent(event.Pass()); } // static void RuntimeEventRouter::DispatchOnRestartRequiredEvent( Profile* profile, const std::string& app_id, api::runtime::OnRestartRequired::Reason reason) { ExtensionSystem* system = ExtensionSystem::Get(profile); if (!system) return; scoped_ptr<Event> event( new Event(runtime::OnRestartRequired::kEventName, api::runtime::OnRestartRequired::Create(reason))); DCHECK(system->event_router()); system->event_router()->DispatchEventToExtension(app_id, event.Pass()); } // static void RuntimeEventRouter::OnExtensionUninstalled( Profile* profile, const std::string& extension_id) { #if defined(ENABLE_EXTENSIONS) GURL uninstall_url(GetUninstallURL(ExtensionPrefs::Get(profile), extension_id)); if (uninstall_url.is_empty()) return; Browser* browser = chrome::FindLastActiveWithProfile(profile, chrome::GetActiveDesktop()); if (!browser) browser = new Browser(Browser::CreateParams(profile, chrome::GetActiveDesktop())); chrome::NavigateParams params(browser, uninstall_url, content::PAGE_TRANSITION_CLIENT_REDIRECT); params.disposition = NEW_FOREGROUND_TAB; params.user_gesture = false; chrome::Navigate(¶ms); #endif // defined(ENABLE_EXTENSIONS) } bool RuntimeGetBackgroundPageFunction::RunImpl() { ExtensionSystem* system = ExtensionSystem::Get(GetProfile()); ExtensionHost* host = system->process_manager()-> GetBackgroundHostForExtension(extension_id()); if (system->lazy_background_task_queue()->ShouldEnqueueTask(GetProfile(), GetExtension())) { system->lazy_background_task_queue()->AddPendingTask( GetProfile(), extension_id(), base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded, this)); } else if (host) { OnPageLoaded(host); } else { error_ = kNoBackgroundPageError; return false; } return true; } void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost* host) { if (host) { SendResponse(true); } else { error_ = kPageLoadError; SendResponse(false); } } bool RuntimeSetUninstallURLFunction::RunImpl() { std::string url_string; EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &url_string)); GURL url(url_string); if (!url.is_valid()) { error_ = ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string); return false; } SetUninstallURL( ExtensionPrefs::Get(GetProfile()), extension_id(), url_string); return true; } bool RuntimeReloadFunction::RunImpl() { // We can't call ReloadExtension directly, since when this method finishes // it tries to decrease the reference count for the extension, which fails // if the extension has already been reloaded; so instead we post a task. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&ExtensionService::ReloadExtension, GetProfile()->GetExtensionService()->AsWeakPtr(), extension_id())); return true; } RuntimeRequestUpdateCheckFunction::RuntimeRequestUpdateCheckFunction() { registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND, content::NotificationService::AllSources()); } bool RuntimeRequestUpdateCheckFunction::RunImpl() { ExtensionSystem* system = ExtensionSystem::Get(GetProfile()); ExtensionService* service = system->extension_service(); ExtensionUpdater* updater = service->updater(); if (!updater) { error_ = kUpdatesDisabledError; return false; } did_reply_ = false; if (!updater->CheckExtensionSoon(extension_id(), base::Bind( &RuntimeRequestUpdateCheckFunction::CheckComplete, this))) { did_reply_ = true; SetResult(new base::StringValue(kUpdateThrottled)); SendResponse(true); } return true; } void RuntimeRequestUpdateCheckFunction::CheckComplete() { if (did_reply_) return; did_reply_ = true; // Since no UPDATE_FOUND notification was seen, this generally would mean // that no update is found, but a previous update check might have already // queued up an update, so check for that here to make sure we return the // right value. ExtensionSystem* system = ExtensionSystem::Get(GetProfile()); ExtensionService* service = system->extension_service(); const Extension* update = service->GetPendingExtensionUpdate(extension_id()); if (update) { ReplyUpdateFound(update->VersionString()); } else { SetResult(new base::StringValue(kUpdateNotFound)); } SendResponse(true); } void RuntimeRequestUpdateCheckFunction::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { if (did_reply_) return; DCHECK(type == chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND); typedef const std::pair<std::string, Version> UpdateDetails; const std::string& id = content::Details<UpdateDetails>(details)->first; const Version& version = content::Details<UpdateDetails>(details)->second; if (id == extension_id()) { ReplyUpdateFound(version.GetString()); } } void RuntimeRequestUpdateCheckFunction::ReplyUpdateFound( const std::string& version) { did_reply_ = true; results_.reset(new base::ListValue); results_->AppendString(kUpdateFound); base::DictionaryValue* details = new base::DictionaryValue; results_->Append(details); details->SetString("version", version); SendResponse(true); } bool RuntimeRestartFunction::RunImpl() { #if defined(OS_CHROMEOS) if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) { chromeos::DBusThreadManager::Get() ->GetPowerManagerClient() ->RequestRestart(); return true; } #endif SetError("Function available only for ChromeOS kiosk mode."); return false; } bool RuntimeGetPlatformInfoFunction::RunImpl() { GetPlatformInfo::Results::PlatformInfo info; const char* os = chrome::OmahaQueryParams::GetOS(); if (strcmp(os, "mac") == 0) { info.os = GetPlatformInfo::Results::PlatformInfo::OS_MAC_; } else if (strcmp(os, "win") == 0) { info.os = GetPlatformInfo::Results::PlatformInfo::OS_WIN_; } else if (strcmp(os, "android") == 0) { info.os = GetPlatformInfo::Results::PlatformInfo::OS_ANDROID_; } else if (strcmp(os, "cros") == 0) { info.os = GetPlatformInfo::Results::PlatformInfo::OS_CROS_; } else if (strcmp(os, "linux") == 0) { info.os = GetPlatformInfo::Results::PlatformInfo::OS_LINUX_; } else if (strcmp(os, "openbsd") == 0) { info.os = GetPlatformInfo::Results::PlatformInfo::OS_OPENBSD_; } else { NOTREACHED(); return false; } const char* arch = chrome::OmahaQueryParams::GetArch(); if (strcmp(arch, "arm") == 0) { info.arch = GetPlatformInfo::Results::PlatformInfo::ARCH_ARM; } else if (strcmp(arch, "x86") == 0) { info.arch = GetPlatformInfo::Results::PlatformInfo::ARCH_X86_32; } else if (strcmp(arch, "x64") == 0) { info.arch = GetPlatformInfo::Results::PlatformInfo::ARCH_X86_64; } else { NOTREACHED(); return false; } const char* nacl_arch = chrome::OmahaQueryParams::GetNaclArch(); if (strcmp(nacl_arch, "arm") == 0) { info.nacl_arch = GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_ARM; } else if (strcmp(nacl_arch, "x86-32") == 0) { info.nacl_arch = GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_X86_32; } else if (strcmp(nacl_arch, "x86-64") == 0) { info.nacl_arch = GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_X86_64; } else { NOTREACHED(); return false; } results_ = GetPlatformInfo::Results::Create(info); return true; } bool RuntimeGetPackageDirectoryEntryFunction::RunImpl() { fileapi::IsolatedContext* isolated_context = fileapi::IsolatedContext::GetInstance(); DCHECK(isolated_context); std::string relative_path = kPackageDirectoryPath; base::FilePath path = extension_->path(); std::string filesystem_id = isolated_context->RegisterFileSystemForPath( fileapi::kFileSystemTypeNativeLocal, path, &relative_path); int renderer_id = render_view_host_->GetProcess()->GetID(); content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance(); policy->GrantReadFileSystem(renderer_id, filesystem_id); base::DictionaryValue* dict = new base::DictionaryValue(); SetResult(dict); dict->SetString("fileSystemId", filesystem_id); dict->SetString("baseName", relative_path); return true; } } // namespace extensions