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(ERROR
) << "IME extension file path not exists: " << file_path
->value();
115 ComponentExtensionIMEManagerImpl::ComponentExtensionIMEManagerImpl() {
116 ReadComponentExtensionsInfo(&component_extension_list_
);
119 ComponentExtensionIMEManagerImpl::~ComponentExtensionIMEManagerImpl() {
122 std::vector
<ComponentExtensionIME
> ComponentExtensionIMEManagerImpl::ListIME() {
123 return component_extension_list_
;
126 void ComponentExtensionIMEManagerImpl::Load(Profile
* profile
,
127 const std::string
& extension_id
,
128 const std::string
& manifest
,
129 const base::FilePath
& file_path
) {
130 // Check the existence of file path to avoid unnecessary extension loading
131 // and InputMethodEngine creation, so that the virtual keyboard web content
132 // url won't be override by IME component extensions.
133 base::FilePath
* copied_file_path
= new base::FilePath(file_path
);
134 content::BrowserThread::PostTaskAndReplyWithResult(
135 content::BrowserThread::FILE,
137 base::Bind(&CheckFilePath
,
138 base::Unretained(copied_file_path
)),
139 base::Bind(&OnFilePathChecked
,
140 base::Unretained(profile
),
141 base::Owned(new std::string(extension_id
)),
142 base::Owned(new std::string(manifest
)),
143 base::Owned(copied_file_path
)));
146 void ComponentExtensionIMEManagerImpl::Unload(Profile
* profile
,
147 const std::string
& extension_id
,
148 const base::FilePath
& file_path
) {
149 // Remove(extension_id) does nothing when the extension has already been
150 // removed or not been registered.
151 GetComponentLoader(profile
)->Remove(extension_id
);
154 scoped_ptr
<base::DictionaryValue
> ComponentExtensionIMEManagerImpl::GetManifest(
155 const std::string
& manifest_string
) {
157 JSONStringValueDeserializer
deserializer(manifest_string
);
158 scoped_ptr
<base::Value
> manifest(deserializer
.Deserialize(NULL
, &error
));
160 LOG(ERROR
) << "Failed at getting manifest";
162 return scoped_ptr
<base::DictionaryValue
>(
163 static_cast<base::DictionaryValue
*>(manifest
.release())).Pass();
167 bool ComponentExtensionIMEManagerImpl::IsIMEExtensionID(const std::string
& id
) {
168 for (size_t i
= 0; i
< arraysize(whitelisted_component_extension
); ++i
) {
169 if (base::LowerCaseEqualsASCII(id
, whitelisted_component_extension
[i
].id
))
176 bool ComponentExtensionIMEManagerImpl::ReadEngineComponent(
177 const ComponentExtensionIME
& component_extension
,
178 const base::DictionaryValue
& dict
,
179 ComponentExtensionEngine
* out
) {
182 if (!dict
.GetString(extensions::manifest_keys::kType
, &type
))
186 if (!dict
.GetString(extensions::manifest_keys::kId
, &out
->engine_id
))
188 if (!dict
.GetString(extensions::manifest_keys::kName
, &out
->display_name
))
190 if (!dict
.GetString(extensions::manifest_keys::kIndicator
, &out
->indicator
))
193 std::set
<std::string
> languages
;
194 const base::Value
* language_value
= NULL
;
195 if (dict
.Get(extensions::manifest_keys::kLanguage
, &language_value
)) {
196 if (language_value
->GetType() == base::Value::TYPE_STRING
) {
197 std::string language_str
;
198 language_value
->GetAsString(&language_str
);
199 languages
.insert(language_str
);
200 } else if (language_value
->GetType() == base::Value::TYPE_LIST
) {
201 const base::ListValue
* language_list
= NULL
;
202 language_value
->GetAsList(&language_list
);
203 for (size_t j
= 0; j
< language_list
->GetSize(); ++j
) {
204 std::string language_str
;
205 if (language_list
->GetString(j
, &language_str
))
206 languages
.insert(language_str
);
210 DCHECK(!languages
.empty());
211 out
->language_codes
.assign(languages
.begin(), languages
.end());
213 const base::ListValue
* layouts
= NULL
;
214 if (!dict
.GetList(extensions::manifest_keys::kLayouts
, &layouts
))
217 for (size_t i
= 0; i
< layouts
->GetSize(); ++i
) {
219 if (layouts
->GetString(i
, &buffer
))
220 out
->layouts
.push_back(buffer
);
223 std::string url_string
;
224 if (dict
.GetString(extensions::manifest_keys::kInputView
,
226 GURL url
= extensions::Extension::GetResourceURL(
227 extensions::Extension::GetBaseURLFromExtensionId(
228 component_extension
.id
),
232 out
->input_view_url
= url
;
235 if (dict
.GetString(extensions::manifest_keys::kOptionsPage
,
237 GURL url
= extensions::Extension::GetResourceURL(
238 extensions::Extension::GetBaseURLFromExtensionId(
239 component_extension
.id
),
243 out
->options_page_url
= url
;
245 // Fallback to extension level options page.
246 out
->options_page_url
= component_extension
.options_page_url
;
253 bool ComponentExtensionIMEManagerImpl::ReadExtensionInfo(
254 const base::DictionaryValue
& manifest
,
255 const std::string
& extension_id
,
256 ComponentExtensionIME
* out
) {
257 if (!manifest
.GetString(extensions::manifest_keys::kDescription
,
261 if (manifest
.GetString(kImePathKeyName
, &path
))
262 out
->path
= base::FilePath(path
);
263 std::string url_string
;
264 if (manifest
.GetString(extensions::manifest_keys::kOptionsPage
,
266 GURL url
= extensions::Extension::GetResourceURL(
267 extensions::Extension::GetBaseURLFromExtensionId(extension_id
),
271 out
->options_page_url
= url
;
273 // It's okay to return true on no option page and/or input view page case.
278 void ComponentExtensionIMEManagerImpl::ReadComponentExtensionsInfo(
279 std::vector
<ComponentExtensionIME
>* out_imes
) {
281 for (size_t i
= 0; i
< arraysize(whitelisted_component_extension
); ++i
) {
282 ComponentExtensionIME component_ime
;
283 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
284 component_ime
.manifest
=
285 rb
.GetRawDataResource(
286 whitelisted_component_extension
[i
].manifest_resource_id
)
288 if (component_ime
.manifest
.empty())
291 scoped_ptr
<base::DictionaryValue
> manifest
=
292 GetManifest(component_ime
.manifest
);
296 if (!ReadExtensionInfo(*manifest
.get(),
297 whitelisted_component_extension
[i
].id
,
300 component_ime
.id
= whitelisted_component_extension
[i
].id
;
302 if (!component_ime
.path
.IsAbsolute()) {
303 base::FilePath resources_path
;
304 if (!PathService::Get(chrome::DIR_RESOURCES
, &resources_path
))
306 component_ime
.path
= resources_path
.Append(component_ime
.path
);
309 const base::ListValue
* component_list
;
310 if (!manifest
->GetList(extensions::manifest_keys::kInputComponents
,
314 for (size_t i
= 0; i
< component_list
->GetSize(); ++i
) {
315 const base::DictionaryValue
* dictionary
;
316 if (!component_list
->GetDictionary(i
, &dictionary
))
319 ComponentExtensionEngine engine
;
320 ReadEngineComponent(component_ime
, *dictionary
, &engine
);
321 component_ime
.engines
.push_back(engine
);
323 out_imes
->push_back(component_ime
);
327 } // namespace chromeos