blob: 55db4a68793534f2bc693d5f301e56396950892c [file] [log] [blame]
// Copyright 2013 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/chromeos/input_method/component_extension_ime_manager_impl.h"
#include <stddef.h>
#include <algorithm>
#include "base/files/file_util.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/grit/browser_resources.h"
#include "extensions/browser/extension_pref_value_map.h"
#include "extensions/browser/extension_pref_value_map_factory.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "ui/base/ime/chromeos/extension_ime_util.h"
#include "ui/base/resource/resource_bundle.h"
namespace chromeos {
namespace {
struct WhitelistedComponentExtensionIME {
const char* id;
int manifest_resource_id;
} whitelisted_component_extension[] = {
#if defined(GOOGLE_CHROME_BUILD)
{
// Official Google XKB Input.
extension_ime_util::kXkbExtensionId, IDR_GOOGLE_XKB_MANIFEST,
},
#else
{
// Open-sourced ChromeOS xkb extension.
extension_ime_util::kXkbExtensionId, IDR_XKB_MANIFEST,
},
{
// Open-sourced ChromeOS Keyboards extension.
extension_ime_util::kM17nExtensionId, IDR_M17N_MANIFEST,
},
{
// Open-sourced Pinyin Chinese Input Method.
extension_ime_util::kChinesePinyinExtensionId, IDR_PINYIN_MANIFEST,
},
{
// Open-sourced Zhuyin Chinese Input Method.
extension_ime_util::kChineseZhuyinExtensionId, IDR_ZHUYIN_MANIFEST,
},
{
// Open-sourced Cangjie Chinese Input Method.
extension_ime_util::kChineseCangjieExtensionId, IDR_CANGJIE_MANIFEST,
},
{
// Open-sourced Japanese Mozc Input.
extension_ime_util::kMozcExtensionId, IDR_MOZC_MANIFEST,
},
{
// Open-sourced Hangul Input.
extension_ime_util::kHangulExtensionId, IDR_HANGUL_MANIFEST,
},
#endif
{
// Braille hardware keyboard IME that works together with ChromeVox.
extension_ime_util::kBrailleImeExtensionId, IDR_BRAILLE_MANIFEST,
},
};
const char kImePathKeyName[] = "ime_path";
extensions::ComponentLoader* GetComponentLoader(Profile* profile) {
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile);
extensions::ExtensionService* extension_service =
extension_system->extension_service();
return extension_service->component_loader();
}
void DoLoadExtension(Profile* profile,
const std::string& extension_id,
const std::string& manifest,
const base::FilePath& file_path) {
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile);
extensions::ExtensionService* extension_service =
extension_system->extension_service();
DCHECK(extension_service);
if (extension_service->GetExtensionById(extension_id, false)) {
VLOG(1) << "the IME extension(id=\"" << extension_id
<< "\") is already enabled";
return;
}
const std::string loaded_extension_id =
GetComponentLoader(profile)->Add(manifest, file_path);
if (loaded_extension_id.empty()) {
LOG(ERROR) << "Failed to add an IME extension(id=\"" << extension_id
<< ", path=\"" << file_path.LossyDisplayName()
<< "\") to ComponentLoader";
return;
}
// Register IME extension with ExtensionPrefValueMap.
ExtensionPrefValueMapFactory::GetForBrowserContext(profile)
->RegisterExtension(extension_id,
base::Time(), // install_time.
true, // is_enabled.
true); // is_incognito_enabled.
DCHECK_EQ(loaded_extension_id, extension_id);
if (!extension_service->IsExtensionEnabled(loaded_extension_id)) {
LOG(ERROR) << "An IME extension(id=\"" << loaded_extension_id
<< "\") is not enabled after loading";
}
}
bool CheckFilePath(const base::FilePath* file_path) {
return base::PathExists(*file_path);
}
void OnFilePathChecked(Profile* profile,
const std::string* extension_id,
const std::string* manifest,
const base::FilePath* file_path,
bool result) {
if (result) {
DoLoadExtension(profile, *extension_id, *manifest, *file_path);
} else {
LOG_IF(ERROR, base::SysInfo::IsRunningOnChromeOS())
<< "IME extension file path does not exist: " << file_path->value();
}
}
} // namespace
ComponentExtensionIMEManagerImpl::ComponentExtensionIMEManagerImpl() {
ReadComponentExtensionsInfo(&component_extension_list_);
}
ComponentExtensionIMEManagerImpl::~ComponentExtensionIMEManagerImpl() {
}
std::vector<ComponentExtensionIME> ComponentExtensionIMEManagerImpl::ListIME() {
return component_extension_list_;
}
void ComponentExtensionIMEManagerImpl::Load(Profile* profile,
const std::string& extension_id,
const std::string& manifest,
const base::FilePath& file_path) {
// Check the existence of file path to avoid unnecessary extension loading
// and InputMethodEngine creation, so that the virtual keyboard web content
// url won't be override by IME component extensions.
base::FilePath* copied_file_path = new base::FilePath(file_path);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::Bind(&CheckFilePath, base::Unretained(copied_file_path)),
base::Bind(&OnFilePathChecked, base::Unretained(profile),
base::Owned(new std::string(extension_id)),
base::Owned(new std::string(manifest)),
base::Owned(copied_file_path)));
}
void ComponentExtensionIMEManagerImpl::Unload(Profile* profile,
const std::string& extension_id,
const base::FilePath& file_path) {
// Remove(extension_id) does nothing when the extension has already been
// removed or not been registered.
GetComponentLoader(profile)->Remove(extension_id);
}
std::unique_ptr<base::DictionaryValue>
ComponentExtensionIMEManagerImpl::GetManifest(
const std::string& manifest_string) {
std::string error;
JSONStringValueDeserializer deserializer(manifest_string);
std::unique_ptr<base::Value> manifest =
deserializer.Deserialize(NULL, &error);
if (!manifest.get())
LOG(ERROR) << "Failed at getting manifest";
return std::unique_ptr<base::DictionaryValue>(
static_cast<base::DictionaryValue*>(manifest.release()));
}
// static
bool ComponentExtensionIMEManagerImpl::IsIMEExtensionID(const std::string& id) {
for (size_t i = 0; i < base::size(whitelisted_component_extension); ++i) {
if (base::LowerCaseEqualsASCII(id, whitelisted_component_extension[i].id))
return true;
}
return false;
}
// static
bool ComponentExtensionIMEManagerImpl::ReadEngineComponent(
const ComponentExtensionIME& component_extension,
const base::DictionaryValue& dict,
ComponentExtensionEngine* out) {
DCHECK(out);
std::string type;
if (!dict.GetString(extensions::manifest_keys::kType, &type))
return false;
if (type != "ime")
return false;
if (!dict.GetString(extensions::manifest_keys::kId, &out->engine_id))
return false;
if (!dict.GetString(extensions::manifest_keys::kName, &out->display_name))
return false;
if (!dict.GetString(extensions::manifest_keys::kIndicator, &out->indicator))
out->indicator = "";
std::set<std::string> languages;
const base::Value* language_value = NULL;
if (dict.Get(extensions::manifest_keys::kLanguage, &language_value)) {
if (language_value->is_string()) {
std::string language_str;
language_value->GetAsString(&language_str);
languages.insert(language_str);
} else if (language_value->is_list()) {
const base::ListValue* language_list = NULL;
language_value->GetAsList(&language_list);
for (size_t j = 0; j < language_list->GetSize(); ++j) {
std::string language_str;
if (language_list->GetString(j, &language_str))
languages.insert(language_str);
}
}
}
DCHECK(!languages.empty());
out->language_codes.assign(languages.begin(), languages.end());
const base::ListValue* layouts = NULL;
if (!dict.GetList(extensions::manifest_keys::kLayouts, &layouts))
return false;
for (size_t i = 0; i < layouts->GetSize(); ++i) {
std::string buffer;
if (layouts->GetString(i, &buffer))
out->layouts.push_back(buffer);
}
std::string url_string;
if (dict.GetString(extensions::manifest_keys::kInputView,
&url_string)) {
GURL url = extensions::Extension::GetResourceURL(
extensions::Extension::GetBaseURLFromExtensionId(
component_extension.id),
url_string);
if (!url.is_valid())
return false;
out->input_view_url = url;
}
if (dict.GetString(extensions::manifest_keys::kOptionsPage,
&url_string)) {
GURL url = extensions::Extension::GetResourceURL(
extensions::Extension::GetBaseURLFromExtensionId(
component_extension.id),
url_string);
if (!url.is_valid())
return false;
out->options_page_url = url;
} else {
// Fallback to extension level options page.
out->options_page_url = component_extension.options_page_url;
}
return true;
}
// static
bool ComponentExtensionIMEManagerImpl::ReadExtensionInfo(
const base::DictionaryValue& manifest,
const std::string& extension_id,
ComponentExtensionIME* out) {
if (!manifest.GetString(extensions::manifest_keys::kDescription,
&out->description))
return false;
std::string path;
if (manifest.GetString(kImePathKeyName, &path))
out->path = base::FilePath(path);
std::string url_string;
if (manifest.GetString(extensions::manifest_keys::kOptionsPage,
&url_string)) {
GURL url = extensions::Extension::GetResourceURL(
extensions::Extension::GetBaseURLFromExtensionId(extension_id),
url_string);
if (!url.is_valid())
return false;
out->options_page_url = url;
}
// It's okay to return true on no option page and/or input view page case.
return true;
}
// static
void ComponentExtensionIMEManagerImpl::ReadComponentExtensionsInfo(
std::vector<ComponentExtensionIME>* out_imes) {
DCHECK(out_imes);
for (size_t i = 0; i < base::size(whitelisted_component_extension); ++i) {
ComponentExtensionIME component_ime;
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
component_ime.manifest =
rb.GetRawDataResource(
whitelisted_component_extension[i].manifest_resource_id)
.as_string();
if (component_ime.manifest.empty()) {
LOG(ERROR) << "Couldn't get manifest from resource_id("
<< whitelisted_component_extension[i].manifest_resource_id
<< ")";
continue;
}
std::unique_ptr<base::DictionaryValue> manifest =
GetManifest(component_ime.manifest);
if (!manifest.get()) {
LOG(ERROR) << "Failed to load invalid manifest: "
<< component_ime.manifest;
continue;
}
if (!ReadExtensionInfo(*manifest.get(),
whitelisted_component_extension[i].id,
&component_ime)) {
LOG(ERROR) << "manifest doesn't have needed information for IME.";
continue;
}
component_ime.id = whitelisted_component_extension[i].id;
if (!component_ime.path.IsAbsolute()) {
base::FilePath resources_path;
if (!base::PathService::Get(chrome::DIR_RESOURCES, &resources_path))
NOTREACHED();
component_ime.path = resources_path.Append(component_ime.path);
}
const base::ListValue* component_list;
if (!manifest->GetList(extensions::manifest_keys::kInputComponents,
&component_list)) {
LOG(ERROR) << "No input_components is found in manifest.";
continue;
}
for (size_t i = 0; i < component_list->GetSize(); ++i) {
const base::DictionaryValue* dictionary;
if (!component_list->GetDictionary(i, &dictionary))
continue;
ComponentExtensionEngine engine;
ReadEngineComponent(component_ime, *dictionary, &engine);
component_ime.engines.push_back(engine);
}
out_imes->push_back(component_ime);
}
}
} // namespace chromeos