1 // Copyright 2013 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.
5 #include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h"
9 #include "base/files/file_util.h"
10 #include "base/json/json_string_value_serializer.h"
11 #include "base/logging.h"
12 #include "base/path_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/sys_info.h"
15 #include "chrome/browser/extensions/component_loader.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "chrome/common/chrome_paths.h"
20 #include "chrome/common/extensions/extension_constants.h"
21 #include "chrome/grit/browser_resources.h"
22 #include "chrome/grit/generated_resources.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/extension_l10n_util.h"
27 #include "extensions/common/manifest_constants.h"
28 #include "ui/base/ime/chromeos/extension_ime_util.h"
29 #include "ui/base/resource/resource_bundle.h"
35 struct WhitelistedComponentExtensionIME
{
37 int manifest_resource_id
;
38 } whitelisted_component_extension
[] = {
39 {// ChromeOS Hangul Input.
40 extension_ime_util::kHangulExtensionId
, IDR_HANGUL_MANIFEST
,
42 #if defined(GOOGLE_CHROME_BUILD)
43 {// Official Google XKB Input.
44 extension_ime_util::kXkbExtensionId
, IDR_GOOGLE_XKB_MANIFEST
,
46 {// Google input tools.
47 extension_ime_util::kT13nExtensionId
, IDR_GOOGLE_INPUT_TOOLS_MANIFEST
,
50 {// Open-sourced ChromeOS xkb extension.
51 extension_ime_util::kXkbExtensionId
, IDR_XKB_MANIFEST
,
53 {// Open-sourced ChromeOS Keyboards extension.
54 extension_ime_util::kM17nExtensionId
, IDR_M17N_MANIFEST
,
56 {// Open-sourced Pinyin Chinese Input Method.
57 extension_ime_util::kChinesePinyinExtensionId
, IDR_PINYIN_MANIFEST
,
59 {// Open-sourced Zhuyin Chinese Input Method.
60 extension_ime_util::kChineseZhuyinExtensionId
, IDR_ZHUYIN_MANIFEST
,
62 {// Open-sourced Cangjie Chinese Input Method.
63 extension_ime_util::kChineseCangjieExtensionId
, IDR_CANGJIE_MANIFEST
,
65 {// Japanese Mozc Input.
66 extension_ime_util::kMozcExtensionId
, IDR_MOZC_MANIFEST
,
69 {// Braille hardware keyboard IME that works together with ChromeVox.
70 extension_misc::kBrailleImeExtensionId
, IDR_BRAILLE_MANIFEST
,
74 const char kImePathKeyName
[] = "ime_path";
76 extensions::ComponentLoader
* GetComponentLoader(Profile
* profile
) {
77 extensions::ExtensionSystem
* extension_system
=
78 extensions::ExtensionSystem::Get(profile
);
79 ExtensionService
* extension_service
= extension_system
->extension_service();
80 return extension_service
->component_loader();
83 void DoLoadExtension(Profile
* profile
,
84 const std::string
& extension_id
,
85 const std::string
& manifest
,
86 const base::FilePath
& file_path
) {
87 extensions::ExtensionSystem
* extension_system
=
88 extensions::ExtensionSystem::Get(profile
);
89 ExtensionService
* extension_service
= extension_system
->extension_service();
90 DCHECK(extension_service
);
91 if (extension_service
->GetExtensionById(extension_id
, false))
93 const std::string loaded_extension_id
=
94 GetComponentLoader(profile
)->Add(manifest
, file_path
);
95 DCHECK_EQ(loaded_extension_id
, extension_id
);
98 bool CheckFilePath(const base::FilePath
* file_path
) {
99 return base::PathExists(*file_path
);
102 void OnFilePathChecked(Profile
* profile
,
103 const std::string
* extension_id
,
104 const std::string
* manifest
,
105 const base::FilePath
* file_path
,
108 DoLoadExtension(profile
, *extension_id
, *manifest
, *file_path
);
110 LOG_IF(ERROR
, base::SysInfo::IsRunningOnChromeOS())
111 << "IME extension file path does not exist: " << file_path
->value();
117 ComponentExtensionIMEManagerImpl::ComponentExtensionIMEManagerImpl() {
118 ReadComponentExtensionsInfo(&component_extension_list_
);
121 ComponentExtensionIMEManagerImpl::~ComponentExtensionIMEManagerImpl() {
124 std::vector
<ComponentExtensionIME
> ComponentExtensionIMEManagerImpl::ListIME() {
125 return component_extension_list_
;
128 void ComponentExtensionIMEManagerImpl::Load(Profile
* profile
,
129 const std::string
& extension_id
,
130 const std::string
& manifest
,
131 const base::FilePath
& file_path
) {
132 // Check the existence of file path to avoid unnecessary extension loading
133 // and InputMethodEngine creation, so that the virtual keyboard web content
134 // url won't be override by IME component extensions.
135 base::FilePath
* copied_file_path
= new base::FilePath(file_path
);
136 content::BrowserThread::PostTaskAndReplyWithResult(
137 content::BrowserThread::FILE,
139 base::Bind(&CheckFilePath
,
140 base::Unretained(copied_file_path
)),
141 base::Bind(&OnFilePathChecked
,
142 base::Unretained(profile
),
143 base::Owned(new std::string(extension_id
)),
144 base::Owned(new std::string(manifest
)),
145 base::Owned(copied_file_path
)));
148 void ComponentExtensionIMEManagerImpl::Unload(Profile
* profile
,
149 const std::string
& extension_id
,
150 const base::FilePath
& file_path
) {
151 // Remove(extension_id) does nothing when the extension has already been
152 // removed or not been registered.
153 GetComponentLoader(profile
)->Remove(extension_id
);
156 scoped_ptr
<base::DictionaryValue
> ComponentExtensionIMEManagerImpl::GetManifest(
157 const std::string
& manifest_string
) {
159 JSONStringValueDeserializer
deserializer(manifest_string
);
160 scoped_ptr
<base::Value
> manifest(deserializer
.Deserialize(NULL
, &error
));
162 LOG(ERROR
) << "Failed at getting manifest";
164 return scoped_ptr
<base::DictionaryValue
>(
165 static_cast<base::DictionaryValue
*>(manifest
.release())).Pass();
169 bool ComponentExtensionIMEManagerImpl::IsIMEExtensionID(const std::string
& id
) {
170 for (size_t i
= 0; i
< arraysize(whitelisted_component_extension
); ++i
) {
171 if (base::LowerCaseEqualsASCII(id
, whitelisted_component_extension
[i
].id
))
178 bool ComponentExtensionIMEManagerImpl::ReadEngineComponent(
179 const ComponentExtensionIME
& component_extension
,
180 const base::DictionaryValue
& dict
,
181 ComponentExtensionEngine
* out
) {
184 if (!dict
.GetString(extensions::manifest_keys::kType
, &type
))
188 if (!dict
.GetString(extensions::manifest_keys::kId
, &out
->engine_id
))
190 if (!dict
.GetString(extensions::manifest_keys::kName
, &out
->display_name
))
192 if (!dict
.GetString(extensions::manifest_keys::kIndicator
, &out
->indicator
))
195 std::set
<std::string
> languages
;
196 const base::Value
* language_value
= NULL
;
197 if (dict
.Get(extensions::manifest_keys::kLanguage
, &language_value
)) {
198 if (language_value
->GetType() == base::Value::TYPE_STRING
) {
199 std::string language_str
;
200 language_value
->GetAsString(&language_str
);
201 languages
.insert(language_str
);
202 } else if (language_value
->GetType() == base::Value::TYPE_LIST
) {
203 const base::ListValue
* language_list
= NULL
;
204 language_value
->GetAsList(&language_list
);
205 for (size_t j
= 0; j
< language_list
->GetSize(); ++j
) {
206 std::string language_str
;
207 if (language_list
->GetString(j
, &language_str
))
208 languages
.insert(language_str
);
212 DCHECK(!languages
.empty());
213 out
->language_codes
.assign(languages
.begin(), languages
.end());
215 const base::ListValue
* layouts
= NULL
;
216 if (!dict
.GetList(extensions::manifest_keys::kLayouts
, &layouts
))
219 for (size_t i
= 0; i
< layouts
->GetSize(); ++i
) {
221 if (layouts
->GetString(i
, &buffer
))
222 out
->layouts
.push_back(buffer
);
225 std::string url_string
;
226 if (dict
.GetString(extensions::manifest_keys::kInputView
,
228 GURL url
= extensions::Extension::GetResourceURL(
229 extensions::Extension::GetBaseURLFromExtensionId(
230 component_extension
.id
),
234 out
->input_view_url
= url
;
237 if (dict
.GetString(extensions::manifest_keys::kOptionsPage
,
239 GURL url
= extensions::Extension::GetResourceURL(
240 extensions::Extension::GetBaseURLFromExtensionId(
241 component_extension
.id
),
245 out
->options_page_url
= url
;
247 // Fallback to extension level options page.
248 out
->options_page_url
= component_extension
.options_page_url
;
255 bool ComponentExtensionIMEManagerImpl::ReadExtensionInfo(
256 const base::DictionaryValue
& manifest
,
257 const std::string
& extension_id
,
258 ComponentExtensionIME
* out
) {
259 if (!manifest
.GetString(extensions::manifest_keys::kDescription
,
263 if (manifest
.GetString(kImePathKeyName
, &path
))
264 out
->path
= base::FilePath(path
);
265 std::string url_string
;
266 if (manifest
.GetString(extensions::manifest_keys::kOptionsPage
,
268 GURL url
= extensions::Extension::GetResourceURL(
269 extensions::Extension::GetBaseURLFromExtensionId(extension_id
),
273 out
->options_page_url
= url
;
275 // It's okay to return true on no option page and/or input view page case.
280 void ComponentExtensionIMEManagerImpl::ReadComponentExtensionsInfo(
281 std::vector
<ComponentExtensionIME
>* out_imes
) {
283 for (size_t i
= 0; i
< arraysize(whitelisted_component_extension
); ++i
) {
284 ComponentExtensionIME component_ime
;
285 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
286 component_ime
.manifest
=
287 rb
.GetRawDataResource(
288 whitelisted_component_extension
[i
].manifest_resource_id
)
290 if (component_ime
.manifest
.empty())
293 scoped_ptr
<base::DictionaryValue
> manifest
=
294 GetManifest(component_ime
.manifest
);
298 if (!ReadExtensionInfo(*manifest
.get(),
299 whitelisted_component_extension
[i
].id
,
302 component_ime
.id
= whitelisted_component_extension
[i
].id
;
304 if (!component_ime
.path
.IsAbsolute()) {
305 base::FilePath resources_path
;
306 if (!PathService::Get(chrome::DIR_RESOURCES
, &resources_path
))
308 component_ime
.path
= resources_path
.Append(component_ime
.path
);
311 const base::ListValue
* component_list
;
312 if (!manifest
->GetList(extensions::manifest_keys::kInputComponents
,
316 for (size_t i
= 0; i
< component_list
->GetSize(); ++i
) {
317 const base::DictionaryValue
* dictionary
;
318 if (!component_list
->GetDictionary(i
, &dictionary
))
321 ComponentExtensionEngine engine
;
322 ReadEngineComponent(component_ime
, *dictionary
, &engine
);
323 component_ime
.engines
.push_back(engine
);
325 out_imes
->push_back(component_ime
);
329 } // namespace chromeos