• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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