blob: 5966e8a17d28c943a6719dff3fb79ad797b41d15 [file] [log] [blame]
// 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/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/event_router.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/lazy_background_task_queue.h"
#include "chrome/browser/extensions/updater/extension_updater.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 "chrome/common/extensions/background_info.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/omaha_query_params/omaha_query_params.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/common/error_utils.h"
#include "url/gurl.h"
#include "webkit/browser/fileapi/isolated_context.h"
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";
static void DispatchOnStartupEventImpl(
Profile* profile,
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 profiles.
if (profile->IsOffTheRecord())
return;
if (g_browser_process->IsShuttingDown() ||
!g_browser_process->profile_manager()->IsValidProfile(profile))
return;
ExtensionSystem* system = ExtensionSystem::Get(profile);
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 =
system->extension_service()->extensions()->GetByID(extension_id);
if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) &&
first_call &&
system->lazy_background_task_queue()->
ShouldEnqueueTask(profile, extension)) {
system->lazy_background_task_queue()->AddPendingTask(
profile, extension_id,
base::Bind(&DispatchOnStartupEventImpl,
profile, 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));
}
std::string GetUninstallUrl(ExtensionPrefs* prefs,
const std::string& extension_id) {
std::string url_string;
prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string);
return url_string;
}
} // namespace
// static
void RuntimeEventRouter::DispatchOnStartupEvent(
Profile* profile, const std::string& extension_id) {
DispatchOnStartupEventImpl(profile, extension_id, true, NULL);
}
// static
void RuntimeEventRouter::DispatchOnInstalledEvent(
Profile* profile,
const std::string& extension_id,
const Version& old_version,
bool chrome_updated) {
ExtensionSystem* system = ExtensionSystem::Get(profile);
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(&params);
#endif // defined(ENABLE_EXTENSIONS)
}
bool RuntimeGetBackgroundPageFunction::RunImpl() {
ExtensionSystem* system = ExtensionSystem::Get(profile());
ExtensionHost* host = system->process_manager()->
GetBackgroundHostForExtension(extension_id());
if (system->lazy_background_task_queue()->ShouldEnqueueTask(
profile(), GetExtension())) {
system->lazy_background_task_queue()->AddPendingTask(
profile(), 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(profile()), 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,
profile()->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(profile());
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(profile());
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 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