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