Home | History | Annotate | Download | only in input_method
      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 
     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 
    102 ComponentExtensionIMEManagerImpl::ComponentExtensionIMEManagerImpl()
    103     : is_initialized_(false),
    104       weak_ptr_factory_(this) {
    105 }
    106 
    107 ComponentExtensionIMEManagerImpl::~ComponentExtensionIMEManagerImpl() {
    108 }
    109 
    110 std::vector<ComponentExtensionIME> ComponentExtensionIMEManagerImpl::ListIME() {
    111   DCHECK(thread_checker_.CalledOnValidThread());
    112   return component_extension_list_;
    113 }
    114 
    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 
    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 
    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 
    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 
    174 bool ComponentExtensionIMEManagerImpl::IsInitialized() {
    175   return is_initialized_;
    176 }
    177 
    178 // static
    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
    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
    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 
    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