component_extension_ime_manager_impl.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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 "chrome/browser/extensions/component_loader.h"
10#include "chrome/browser/extensions/extension_service.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/profiles/profile_manager.h"
13#include "chrome/common/extensions/extension_file_util.h"
14#include "content/public/browser/browser_thread.h"
15#include "extensions/browser/extension_system.h"
16#include "extensions/common/extension.h"
17#include "extensions/common/extension_l10n_util.h"
18#include "extensions/common/manifest_constants.h"
19#include "ui/base/l10n/l10n_util.h"
20
21namespace chromeos {
22
23namespace {
24
25struct WhitelistedComponentExtensionIME {
26  const char* id;
27  const char* path;
28} whitelisted_component_extension[] = {
29  {
30    // ChromeOS Hangul Input.
31    "bdgdidmhaijohebebipajioienkglgfo",
32    "/usr/share/chromeos-assets/input_methods/hangul",
33  },
34#if defined(OFFICIAL_BUILD)
35  {
36    // Official Google XKB Input.
37    "jkghodnilhceideoidjikpgommlajknk",
38    "/usr/share/chromeos-assets/input_methods/google_xkb",
39  },
40  {
41    // Official Google Keyboards Input.
42    "habcdindjejkmepknlhkkloncjcpcnbf",
43    "/usr/share/chromeos-assets/input_methods/google_keyboards",
44  },
45  {
46    // Official Google Japanese Input.
47    "fpfbhcjppmaeaijcidgiibchfbnhbelj",
48    "/usr/share/chromeos-assets/input_methods/nacl_mozc",
49  },
50  {
51    // Google input tools.
52    "gjaehgfemfahhmlgpdfknkhdnemmolop",
53    "/usr/share/chromeos-assets/input_methods/input_tools",
54  },
55#else
56  {
57    // Open-sourced ChromeOS xkb extension.
58    "fgoepimhcoialccpbmpnnblemnepkkao",
59    "/usr/share/chromeos-assets/input_methods/xkb",
60  },
61  {
62    // Open-sourced ChromeOS Keyboards extension.
63    "jhffeifommiaekmbkkjlpmilogcfdohp",
64    "/usr/share/chromeos-assets/input_methods/keyboard_layouts",
65  },
66  {
67    // Open-sourced Pinyin Chinese Input Method.
68    "cpgalbafkoofkjmaeonnfijgpfennjjn",
69    "/usr/share/chromeos-assets/input_methods/pinyin",
70  },
71  {
72    // Open-sourced Zhuyin Chinese Input Method.
73    "ekbifjdfhkmdeeajnolmgdlmkllopefi",
74    "/usr/share/chromeos-assets/input_methods/zhuyin",
75  },
76  {
77    // Open-sourced Cangjie Chinese Input Method.
78    "aeebooiibjahgpgmhkeocbeekccfknbj",
79    "/usr/share/chromeos-assets/input_methods/cangjie",
80  },
81  {
82    // Open-sourced Mozc Japanese Input.
83    "bbaiamgfapehflhememkfglaehiobjnk",
84    "/usr/share/chromeos-assets/input_methods/nacl_mozc",
85  },
86#endif
87};
88
89extensions::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
102ComponentExtensionIMEManagerImpl::ComponentExtensionIMEManagerImpl()
103    : is_initialized_(false),
104      weak_ptr_factory_(this) {
105}
106
107ComponentExtensionIMEManagerImpl::~ComponentExtensionIMEManagerImpl() {
108}
109
110std::vector<ComponentExtensionIME> ComponentExtensionIMEManagerImpl::ListIME() {
111  DCHECK(thread_checker_.CalledOnValidThread());
112  return component_extension_list_;
113}
114
115bool 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
131void 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
139scoped_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      extension_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
155void 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
174bool ComponentExtensionIMEManagerImpl::IsInitialized() {
175  return is_initialized_;
176}
177
178// static
179bool 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
255bool 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
278void 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    const base::FilePath manifest_path =
288        component_ime.path.Append("manifest.json");
289
290    if (!base::PathExists(component_ime.path) ||
291        !base::PathExists(manifest_path))
292      continue;
293
294    if (!base::ReadFileToString(manifest_path, &component_ime.manifest))
295      continue;
296
297    scoped_ptr<base::DictionaryValue> manifest =
298        GetManifest(component_ime.path);
299    if (!manifest.get())
300      continue;
301
302    if (!ReadExtensionInfo(*manifest.get(),
303                           whitelisted_component_extension[i].id,
304                           &component_ime))
305      continue;
306    component_ime.id = whitelisted_component_extension[i].id;
307
308    const base::ListValue* component_list;
309    if (!manifest->GetList(extensions::manifest_keys::kInputComponents,
310                           &component_list))
311      continue;
312
313    for (size_t i = 0; i < component_list->GetSize(); ++i) {
314      const base::DictionaryValue* dictionary;
315      if (!component_list->GetDictionary(i, &dictionary))
316        continue;
317
318      ComponentExtensionEngine engine;
319      ReadEngineComponent(component_ime, *dictionary, &engine);
320      component_ime.engines.push_back(engine);
321    }
322    out_imes->push_back(component_ime);
323  }
324}
325
326void ComponentExtensionIMEManagerImpl::OnReadComponentExtensionsInfo(
327    std::vector<ComponentExtensionIME>* result,
328    const base::Closure& callback) {
329  DCHECK(thread_checker_.CalledOnValidThread());
330  DCHECK(result);
331  component_extension_list_ = *result;
332  is_initialized_ = true;
333  callback.Run();
334}
335
336}  // namespace chromeos
337