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.
4
5 #include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h"
6
7 #include "base/file_util.h"
8 #include "base/logging.h"
9 #include "base/path_service.h"
10 #include "chrome/browser/extensions/component_loader.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/profiles/profile_manager.h"
14 #include "chrome/common/chrome_paths.h"
15 #include "chrome/common/extensions/extension_constants.h"
16 #include "chrome/common/extensions/extension_file_util.h"
17 #include "chromeos/ime/extension_ime_util.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "extensions/browser/extension_system.h"
20 #include "extensions/common/extension.h"
21 #include "extensions/common/extension_l10n_util.h"
22 #include "extensions/common/file_util.h"
23 #include "extensions/common/manifest_constants.h"
24 #include "ui/base/l10n/l10n_util.h"
25
26 namespace chromeos {
27
28 namespace {
29
30 struct WhitelistedComponentExtensionIME {
31 const char* id;
32 const char* path;
33 } whitelisted_component_extension[] = {
34 {
35 // ChromeOS Hangul Input.
36 extension_ime_util::kHangulExtensionId,
37 "/usr/share/chromeos-assets/input_methods/hangul",
38 },
39 #if defined(OFFICIAL_BUILD)
40 {
41 // Official Google XKB Input.
42 extension_ime_util::kXkbExtensionId,
43 "/usr/share/chromeos-assets/input_methods/google_xkb",
44 },
45 {
46 // Google input tools.
47 extension_ime_util::kT13nExtensionId,
48 "/usr/share/chromeos-assets/input_methods/input_tools",
49 },
50 #else
51 {
52 // Open-sourced ChromeOS xkb extension.
53 extension_ime_util::kXkbExtensionId,
54 "/usr/share/chromeos-assets/input_methods/xkb",
55 },
56 {
57 // Open-sourced ChromeOS Keyboards extension.
58 extension_ime_util::kM17nExtensionId,
59 "/usr/share/chromeos-assets/input_methods/keyboard_layouts",
60 },
61 {
62 // Open-sourced Pinyin Chinese Input Method.
63 extension_ime_util::kChinesePinyinExtensionId,
64 "/usr/share/chromeos-assets/input_methods/pinyin",
65 },
66 {
67 // Open-sourced Zhuyin Chinese Input Method.
68 extension_ime_util::kChineseZhuyinExtensionId,
69 "/usr/share/chromeos-assets/input_methods/zhuyin",
70 },
71 {
72 // Open-sourced Cangjie Chinese Input Method.
73 extension_ime_util::kChineseCangjieExtensionId,
74 "/usr/share/chromeos-assets/input_methods/cangjie",
75 },
76 {
77 // Japanese Mozc Input.
78 extension_ime_util::kMozcExtensionId,
79 "/usr/share/chromeos-assets/input_methods/nacl_mozc",
80 },
81 #endif
82 {
83 // Braille hardware keyboard IME that works together with ChromeVox.
84 extension_misc::kBrailleImeExtensionId,
85 extension_misc::kBrailleImeExtensionPath,
86 },
87 };
88
GetComponentLoader()89 extensions::ComponentLoader* GetComponentLoader() {
90 // TODO(skuhne, nkostylev): At this time the only thing which makes sense here
91 // is to use the active profile. Nkostylev is working on getting IME settings
92 // to work for multi user by collecting all settings from all users. Once that
93 // is done we might have to re-visit this decision.
94 Profile* profile = ProfileManager::GetActiveUserProfile();
95 extensions::ExtensionSystem* extension_system =
96 extensions::ExtensionSystem::Get(profile);
97 ExtensionService* extension_service = extension_system->extension_service();
98 return extension_service->component_loader();
99 }
100 } // namespace
101
ComponentExtensionIMEManagerImpl()102 ComponentExtensionIMEManagerImpl::ComponentExtensionIMEManagerImpl()
103 : is_initialized_(false),
104 weak_ptr_factory_(this) {
105 }
106
~ComponentExtensionIMEManagerImpl()107 ComponentExtensionIMEManagerImpl::~ComponentExtensionIMEManagerImpl() {
108 }
109
ListIME()110 std::vector<ComponentExtensionIME> ComponentExtensionIMEManagerImpl::ListIME() {
111 DCHECK(thread_checker_.CalledOnValidThread());
112 return component_extension_list_;
113 }
114
Load(const std::string & extension_id,const std::string & manifest,const base::FilePath & file_path)115 bool ComponentExtensionIMEManagerImpl::Load(const std::string& extension_id,
116 const std::string& manifest,
117 const base::FilePath& file_path) {
118 DCHECK(thread_checker_.CalledOnValidThread());
119 Profile* profile = ProfileManager::GetActiveUserProfile();
120 extensions::ExtensionSystem* extension_system =
121 extensions::ExtensionSystem::Get(profile);
122 ExtensionService* extension_service = extension_system->extension_service();
123 if (extension_service->GetExtensionById(extension_id, false))
124 return false;
125 const std::string loaded_extension_id =
126 GetComponentLoader()->Add(manifest, file_path);
127 DCHECK_EQ(loaded_extension_id, extension_id);
128 return true;
129 }
130
Unload(const std::string & extension_id,const base::FilePath & file_path)131 void ComponentExtensionIMEManagerImpl::Unload(const std::string& extension_id,
132 const base::FilePath& file_path) {
133 DCHECK(thread_checker_.CalledOnValidThread());
134 // Remove(extension_id) does nothing when the extension has already been
135 // removed or not been registered.
136 GetComponentLoader()->Remove(extension_id);
137 }
138
GetManifest(const base::FilePath & file_path)139 scoped_ptr<base::DictionaryValue> ComponentExtensionIMEManagerImpl::GetManifest(
140 const base::FilePath& file_path) {
141 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
142 std::string error;
143 scoped_ptr<base::DictionaryValue> manifest(
144 extensions::file_util::LoadManifest(file_path, &error));
145 if (!manifest.get())
146 LOG(ERROR) << "Failed at getting manifest";
147 if (!extension_l10n_util::LocalizeExtension(file_path,
148 manifest.get(),
149 &error))
150 LOG(ERROR) << "Localization failed";
151
152 return manifest.Pass();
153 }
154
InitializeAsync(const base::Closure & callback)155 void ComponentExtensionIMEManagerImpl::InitializeAsync(
156 const base::Closure& callback) {
157 DCHECK(!is_initialized_);
158 DCHECK(thread_checker_.CalledOnValidThread());
159
160 std::vector<ComponentExtensionIME>* component_extension_ime_list
161 = new std::vector<ComponentExtensionIME>;
162 content::BrowserThread::PostTaskAndReply(
163 content::BrowserThread::FILE,
164 FROM_HERE,
165 base::Bind(&ComponentExtensionIMEManagerImpl::ReadComponentExtensionsInfo,
166 base::Unretained(component_extension_ime_list)),
167 base::Bind(
168 &ComponentExtensionIMEManagerImpl::OnReadComponentExtensionsInfo,
169 weak_ptr_factory_.GetWeakPtr(),
170 base::Owned(component_extension_ime_list),
171 callback));
172 }
173
IsInitialized()174 bool ComponentExtensionIMEManagerImpl::IsInitialized() {
175 return is_initialized_;
176 }
177
178 // static
ReadEngineComponent(const ComponentExtensionIME & component_extension,const base::DictionaryValue & dict,ComponentExtensionEngine * out)179 bool ComponentExtensionIMEManagerImpl::ReadEngineComponent(
180 const ComponentExtensionIME& component_extension,
181 const base::DictionaryValue& dict,
182 ComponentExtensionEngine* out) {
183 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
184 DCHECK(out);
185 std::string type;
186 if (!dict.GetString(extensions::manifest_keys::kType, &type))
187 return false;
188 if (type != "ime")
189 return false;
190 if (!dict.GetString(extensions::manifest_keys::kId, &out->engine_id))
191 return false;
192 if (!dict.GetString(extensions::manifest_keys::kName, &out->display_name))
193 return false;
194
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);
209 }
210 }
211 }
212 DCHECK(!languages.empty());
213 out->language_codes.assign(languages.begin(), languages.end());
214
215 const base::ListValue* layouts = NULL;
216 if (!dict.GetList(extensions::manifest_keys::kLayouts, &layouts))
217 return false;
218
219 for (size_t i = 0; i < layouts->GetSize(); ++i) {
220 std::string buffer;
221 if (layouts->GetString(i, &buffer))
222 out->layouts.push_back(buffer);
223 }
224
225 std::string url_string;
226 if (dict.GetString(extensions::manifest_keys::kInputView,
227 &url_string)) {
228 GURL url = extensions::Extension::GetResourceURL(
229 extensions::Extension::GetBaseURLFromExtensionId(
230 component_extension.id),
231 url_string);
232 if (!url.is_valid())
233 return false;
234 out->input_view_url = url;
235 }
236
237 if (dict.GetString(extensions::manifest_keys::kOptionsPage,
238 &url_string)) {
239 GURL url = extensions::Extension::GetResourceURL(
240 extensions::Extension::GetBaseURLFromExtensionId(
241 component_extension.id),
242 url_string);
243 if (!url.is_valid())
244 return false;
245 out->options_page_url = url;
246 } else {
247 // Fallback to extension level options page.
248 out->options_page_url = component_extension.options_page_url;
249 }
250
251 return true;
252 }
253
254 // static
ReadExtensionInfo(const base::DictionaryValue & manifest,const std::string & extension_id,ComponentExtensionIME * out)255 bool ComponentExtensionIMEManagerImpl::ReadExtensionInfo(
256 const base::DictionaryValue& manifest,
257 const std::string& extension_id,
258 ComponentExtensionIME* out) {
259 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
260 if (!manifest.GetString(extensions::manifest_keys::kDescription,
261 &out->description))
262 return false;
263 std::string url_string;
264 if (manifest.GetString(extensions::manifest_keys::kOptionsPage,
265 &url_string)) {
266 GURL url = extensions::Extension::GetResourceURL(
267 extensions::Extension::GetBaseURLFromExtensionId(extension_id),
268 url_string);
269 if (!url.is_valid())
270 return false;
271 out->options_page_url = url;
272 }
273 // It's okay to return true on no option page and/or input view page case.
274 return true;
275 }
276
277 // static
ReadComponentExtensionsInfo(std::vector<ComponentExtensionIME> * out_imes)278 void ComponentExtensionIMEManagerImpl::ReadComponentExtensionsInfo(
279 std::vector<ComponentExtensionIME>* out_imes) {
280 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
281 DCHECK(out_imes);
282 for (size_t i = 0; i < arraysize(whitelisted_component_extension); ++i) {
283 ComponentExtensionIME component_ime;
284 component_ime.path = base::FilePath(
285 whitelisted_component_extension[i].path);
286
287 if (!component_ime.path.IsAbsolute()) {
288 base::FilePath resources_path;
289 if (!PathService::Get(chrome::DIR_RESOURCES, &resources_path))
290 NOTREACHED();
291 component_ime.path = resources_path.Append(component_ime.path);
292 }
293 const base::FilePath manifest_path =
294 component_ime.path.Append("manifest.json");
295
296 if (!base::PathExists(component_ime.path) ||
297 !base::PathExists(manifest_path))
298 continue;
299
300 if (!base::ReadFileToString(manifest_path, &component_ime.manifest))
301 continue;
302
303 scoped_ptr<base::DictionaryValue> manifest =
304 GetManifest(component_ime.path);
305 if (!manifest.get())
306 continue;
307
308 if (!ReadExtensionInfo(*manifest.get(),
309 whitelisted_component_extension[i].id,
310 &component_ime))
311 continue;
312 component_ime.id = whitelisted_component_extension[i].id;
313
314 const base::ListValue* component_list;
315 if (!manifest->GetList(extensions::manifest_keys::kInputComponents,
316 &component_list))
317 continue;
318
319 for (size_t i = 0; i < component_list->GetSize(); ++i) {
320 const base::DictionaryValue* dictionary;
321 if (!component_list->GetDictionary(i, &dictionary))
322 continue;
323
324 ComponentExtensionEngine engine;
325 ReadEngineComponent(component_ime, *dictionary, &engine);
326 component_ime.engines.push_back(engine);
327 }
328 out_imes->push_back(component_ime);
329 }
330 }
331
OnReadComponentExtensionsInfo(std::vector<ComponentExtensionIME> * result,const base::Closure & callback)332 void ComponentExtensionIMEManagerImpl::OnReadComponentExtensionsInfo(
333 std::vector<ComponentExtensionIME>* result,
334 const base::Closure& callback) {
335 DCHECK(thread_checker_.CalledOnValidThread());
336 DCHECK(result);
337 component_extension_list_ = *result;
338 is_initialized_ = true;
339 callback.Run();
340 }
341
342 } // namespace chromeos
343