blob: 35c42bd86b3ea91fff07fb4bd61b058f33a65466 [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 "base/file_util.h"
#include "base/logging.h"
#include "base/path_service.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/common/extensions/extension_file_util.h"
#include "chromeos/ime/extension_ime_util.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest_constants.h"
#include "ui/base/l10n/l10n_util.h"
namespace chromeos {
namespace {
struct WhitelistedComponentExtensionIME {
const char* id;
const char* path;
} whitelisted_component_extension[] = {
{
// ChromeOS Hangul Input.
extension_ime_util::kHangulExtensionId,
"/usr/share/chromeos-assets/input_methods/hangul",
},
#if defined(OFFICIAL_BUILD)
{
// Official Google XKB Input.
extension_ime_util::kXkbExtensionId,
"/usr/share/chromeos-assets/input_methods/google_xkb",
},
{
// Google input tools.
extension_ime_util::kT13nExtensionId,
"/usr/share/chromeos-assets/input_methods/input_tools",
},
#else
{
// Open-sourced ChromeOS xkb extension.
extension_ime_util::kXkbExtensionId,
"/usr/share/chromeos-assets/input_methods/xkb",
},
{
// Open-sourced ChromeOS Keyboards extension.
extension_ime_util::kM17nExtensionId,
"/usr/share/chromeos-assets/input_methods/keyboard_layouts",
},
{
// Open-sourced Pinyin Chinese Input Method.
extension_ime_util::kChinesePinyinExtensionId,
"/usr/share/chromeos-assets/input_methods/pinyin",
},
{
// Open-sourced Zhuyin Chinese Input Method.
extension_ime_util::kChineseZhuyinExtensionId,
"/usr/share/chromeos-assets/input_methods/zhuyin",
},
{
// Open-sourced Cangjie Chinese Input Method.
extension_ime_util::kChineseCangjieExtensionId,
"/usr/share/chromeos-assets/input_methods/cangjie",
},
{
// Japanese Mozc Input.
extension_ime_util::kMozcExtensionId,
"/usr/share/chromeos-assets/input_methods/nacl_mozc",
},
#endif
{
// Braille hardware keyboard IME that works together with ChromeVox.
extension_misc::kBrailleImeExtensionId,
extension_misc::kBrailleImeExtensionPath,
},
};
extensions::ComponentLoader* GetComponentLoader() {
// TODO(skuhne, nkostylev): At this time the only thing which makes sense here
// is to use the active profile. Nkostylev is working on getting IME settings
// to work for multi user by collecting all settings from all users. Once that
// is done we might have to re-visit this decision.
Profile* profile = ProfileManager::GetActiveUserProfile();
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile);
ExtensionService* extension_service = extension_system->extension_service();
return extension_service->component_loader();
}
} // namespace
ComponentExtensionIMEManagerImpl::ComponentExtensionIMEManagerImpl()
: is_initialized_(false),
weak_ptr_factory_(this) {
}
ComponentExtensionIMEManagerImpl::~ComponentExtensionIMEManagerImpl() {
}
std::vector<ComponentExtensionIME> ComponentExtensionIMEManagerImpl::ListIME() {
DCHECK(thread_checker_.CalledOnValidThread());
return component_extension_list_;
}
bool ComponentExtensionIMEManagerImpl::Load(const std::string& extension_id,
const std::string& manifest,
const base::FilePath& file_path) {
DCHECK(thread_checker_.CalledOnValidThread());
Profile* profile = ProfileManager::GetActiveUserProfile();
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile);
ExtensionService* extension_service = extension_system->extension_service();
if (extension_service->GetExtensionById(extension_id, false))
return false;
const std::string loaded_extension_id =
GetComponentLoader()->Add(manifest, file_path);
DCHECK_EQ(loaded_extension_id, extension_id);
return true;
}
void ComponentExtensionIMEManagerImpl::Unload(const std::string& extension_id,
const base::FilePath& file_path) {
DCHECK(thread_checker_.CalledOnValidThread());
// Remove(extension_id) does nothing when the extension has already been
// removed or not been registered.
GetComponentLoader()->Remove(extension_id);
}
scoped_ptr<base::DictionaryValue> ComponentExtensionIMEManagerImpl::GetManifest(
const base::FilePath& file_path) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
std::string error;
scoped_ptr<base::DictionaryValue> manifest(
extensions::file_util::LoadManifest(file_path, &error));
if (!manifest.get())
LOG(ERROR) << "Failed at getting manifest";
if (!extension_l10n_util::LocalizeExtension(file_path,
manifest.get(),
&error)) {
LOG(ERROR) << "Localization failed for: " << file_path.value()
<< " Error: " << error;
}
return manifest.Pass();
}
void ComponentExtensionIMEManagerImpl::InitializeAsync(
const base::Closure& callback) {
DCHECK(!is_initialized_);
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<ComponentExtensionIME>* component_extension_ime_list
= new std::vector<ComponentExtensionIME>;
content::BrowserThread::PostTaskAndReply(
content::BrowserThread::FILE,
FROM_HERE,
base::Bind(&ComponentExtensionIMEManagerImpl::ReadComponentExtensionsInfo,
base::Unretained(component_extension_ime_list)),
base::Bind(
&ComponentExtensionIMEManagerImpl::OnReadComponentExtensionsInfo,
weak_ptr_factory_.GetWeakPtr(),
base::Owned(component_extension_ime_list),
callback));
}
bool ComponentExtensionIMEManagerImpl::IsInitialized() {
return is_initialized_;
}
// static
bool ComponentExtensionIMEManagerImpl::ReadEngineComponent(
const ComponentExtensionIME& component_extension,
const base::DictionaryValue& dict,
ComponentExtensionEngine* out) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
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;
std::set<std::string> languages;
const base::Value* language_value = NULL;
if (dict.Get(extensions::manifest_keys::kLanguage, &language_value)) {
if (language_value->GetType() == base::Value::TYPE_STRING) {
std::string language_str;
language_value->GetAsString(&language_str);
languages.insert(language_str);
} else if (language_value->GetType() == base::Value::TYPE_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) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
if (!manifest.GetString(extensions::manifest_keys::kDescription,
&out->description))
return false;
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(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
DCHECK(out_imes);
for (size_t i = 0; i < arraysize(whitelisted_component_extension); ++i) {
ComponentExtensionIME component_ime;
component_ime.path = base::FilePath(
whitelisted_component_extension[i].path);
if (!component_ime.path.IsAbsolute()) {
base::FilePath resources_path;
if (!PathService::Get(chrome::DIR_RESOURCES, &resources_path))
NOTREACHED();
component_ime.path = resources_path.Append(component_ime.path);
}
const base::FilePath manifest_path =
component_ime.path.Append("manifest.json");
if (!base::PathExists(component_ime.path) ||
!base::PathExists(manifest_path))
continue;
if (!base::ReadFileToString(manifest_path, &component_ime.manifest))
continue;
scoped_ptr<base::DictionaryValue> manifest =
GetManifest(component_ime.path);
if (!manifest.get())
continue;
if (!ReadExtensionInfo(*manifest.get(),
whitelisted_component_extension[i].id,
&component_ime))
continue;
component_ime.id = whitelisted_component_extension[i].id;
const base::ListValue* component_list;
if (!manifest->GetList(extensions::manifest_keys::kInputComponents,
&component_list))
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);
}
}
void ComponentExtensionIMEManagerImpl::OnReadComponentExtensionsInfo(
std::vector<ComponentExtensionIME>* result,
const base::Closure& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(result);
component_extension_list_ = *result;
is_initialized_ = true;
callback.Run();
}
} // namespace chromeos