L10nRegistry.cpp (14779B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "L10nRegistry.h" 6 #include "mozilla/RefPtr.h" 7 #include "mozilla/URLPreloader.h" 8 #include "nsIChannel.h" 9 #include "nsILoadInfo.h" 10 #include "nsNetUtil.h" 11 #include "nsString.h" 12 #include "nsContentUtils.h" 13 #include "FluentResource.h" 14 #include "FileSource.h" 15 #include "nsICategoryManager.h" 16 #include "mozilla/SimpleEnumerator.h" 17 #include "mozilla/dom/Promise.h" 18 #include "mozilla/dom/PContent.h" 19 #include "mozilla/dom/ContentParent.h" 20 #include "mozilla/Preferences.h" 21 22 using namespace mozilla; 23 using namespace mozilla::dom; 24 25 namespace mozilla::intl { 26 27 /* FluentBundleIterator */ 28 29 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundleIterator, mGlobal) 30 31 FluentBundleIterator::FluentBundleIterator( 32 nsIGlobalObject* aGlobal, UniquePtr<ffi::GeckoFluentBundleIterator> aRaw) 33 : mGlobal(aGlobal), mRaw(std::move(aRaw)) {} 34 35 JSObject* FluentBundleIterator::WrapObject(JSContext* aCx, 36 JS::Handle<JSObject*> aGivenProto) { 37 return FluentBundleIterator_Binding::Wrap(aCx, this, aGivenProto); 38 } 39 40 void FluentBundleIterator::Next(FluentBundleIteratorResult& aResult) { 41 UniquePtr<ffi::FluentBundleRc> raw( 42 ffi::fluent_bundle_iterator_next(mRaw.get())); 43 if (!raw) { 44 aResult.mDone = true; 45 return; 46 } 47 aResult.mDone = false; 48 aResult.mValue = new FluentBundle(mGlobal, std::move(raw)); 49 } 50 51 already_AddRefed<FluentBundleIterator> FluentBundleIterator::Values() { 52 return do_AddRef(this); 53 } 54 55 /* FluentBundleAsyncIterator */ 56 57 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundleAsyncIterator, mGlobal) 58 59 FluentBundleAsyncIterator::FluentBundleAsyncIterator( 60 nsIGlobalObject* aGlobal, 61 UniquePtr<ffi::GeckoFluentBundleAsyncIteratorWrapper> aRaw) 62 : mGlobal(aGlobal), mRaw(std::move(aRaw)) {} 63 64 JSObject* FluentBundleAsyncIterator::WrapObject( 65 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 66 return FluentBundleAsyncIterator_Binding::Wrap(aCx, this, aGivenProto); 67 } 68 69 already_AddRefed<Promise> FluentBundleAsyncIterator::Next(ErrorResult& aError) { 70 RefPtr<Promise> promise = Promise::Create(mGlobal, aError); 71 if (aError.Failed()) { 72 return nullptr; 73 } 74 75 ffi::fluent_bundle_async_iterator_next( 76 mRaw.get(), promise, 77 // callback function which will be invoked by the rust code, passing the 78 // promise back in. 79 [](auto* aPromise, ffi::FluentBundleRc* aBundle) { 80 Promise* promise = const_cast<Promise*>(aPromise); 81 82 FluentBundleIteratorResult res; 83 84 if (aBundle) { 85 // The Rust caller will transfer the ownership to us. 86 UniquePtr<ffi::FluentBundleRc> b(aBundle); 87 nsIGlobalObject* global = promise->GetGlobalObject(); 88 res.mValue = new FluentBundle(global, std::move(b)); 89 res.mDone = false; 90 } else { 91 res.mDone = true; 92 } 93 promise->MaybeResolve(res); 94 }); 95 96 return promise.forget(); 97 } 98 99 already_AddRefed<FluentBundleAsyncIterator> 100 FluentBundleAsyncIterator::Values() { 101 return do_AddRef(this); 102 } 103 104 /* L10nRegistry */ 105 106 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(L10nRegistry, mGlobal) 107 108 L10nRegistry::L10nRegistry(nsIGlobalObject* aGlobal, bool aUseIsolating) 109 : mGlobal(aGlobal), 110 mRaw(dont_AddRef(ffi::l10nregistry_new(aUseIsolating))) {} 111 112 L10nRegistry::L10nRegistry(nsIGlobalObject* aGlobal, 113 RefPtr<const ffi::GeckoL10nRegistry> aRaw) 114 : mGlobal(aGlobal), mRaw(std::move(aRaw)) {} 115 116 /* static */ 117 already_AddRefed<L10nRegistry> L10nRegistry::Constructor( 118 const GlobalObject& aGlobal, const L10nRegistryOptions& aOptions) { 119 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 120 return MakeAndAddRef<L10nRegistry>(global, 121 aOptions.mBundleOptions.mUseIsolating); 122 } 123 124 /* static */ 125 already_AddRefed<L10nRegistry> L10nRegistry::GetInstance( 126 const GlobalObject& aGlobal) { 127 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 128 return MakeAndAddRef<L10nRegistry>( 129 global, dont_AddRef(ffi::l10nregistry_instance_get())); 130 } 131 132 JSObject* L10nRegistry::WrapObject(JSContext* aCx, 133 JS::Handle<JSObject*> aGivenProto) { 134 return L10nRegistry_Binding::Wrap(aCx, this, aGivenProto); 135 } 136 137 void L10nRegistry::GetAvailableLocales(nsTArray<nsCString>& aRetVal) { 138 ffi::l10nregistry_get_available_locales(mRaw.get(), &aRetVal); 139 } 140 141 void L10nRegistry::RegisterSources( 142 const Sequence<OwningNonNull<L10nFileSource>>& aSources) { 143 nsTArray<const ffi::FileSource*> sources(aSources.Length()); 144 for (const auto& source : aSources) { 145 sources.AppendElement(source->Raw()); 146 } 147 148 ffi::l10nregistry_register_sources(mRaw.get(), &sources); 149 } 150 151 void L10nRegistry::UpdateSources( 152 const Sequence<OwningNonNull<L10nFileSource>>& aSources) { 153 nsTArray<const ffi::FileSource*> sources(aSources.Length()); 154 for (const auto& source : aSources) { 155 sources.AppendElement(source->Raw()); 156 } 157 158 ffi::l10nregistry_update_sources(mRaw.get(), &sources); 159 } 160 161 void L10nRegistry::RemoveSources(const Sequence<nsCString>& aSources) { 162 ffi::l10nregistry_remove_sources(mRaw.get(), aSources.Elements(), 163 aSources.Length()); 164 } 165 166 bool L10nRegistry::HasSource(const nsACString& aName, ErrorResult& aRv) { 167 ffi::L10nRegistryStatus status; 168 169 bool result = ffi::l10nregistry_has_source(mRaw.get(), &aName, &status); 170 PopulateError(aRv, status); 171 return result; 172 } 173 174 already_AddRefed<L10nFileSource> L10nRegistry::GetSource( 175 const nsACString& aName, ErrorResult& aRv) { 176 ffi::L10nRegistryStatus status; 177 178 RefPtr<const ffi::FileSource> raw( 179 dont_AddRef(ffi::l10nregistry_get_source(mRaw.get(), &aName, &status))); 180 if (PopulateError(aRv, status)) { 181 return nullptr; 182 } 183 184 return MakeAndAddRef<L10nFileSource>(std::move(raw)); 185 } 186 187 void L10nRegistry::GetSourceNames(nsTArray<nsCString>& aRetVal) { 188 ffi::l10nregistry_get_source_names(mRaw.get(), &aRetVal); 189 } 190 191 void L10nRegistry::ClearSources() { 192 ffi::l10nregistry_clear_sources(mRaw.get()); 193 } 194 195 /* static */ 196 ffi::GeckoResourceId L10nRegistry::ResourceIdToFFI( 197 const nsCString& aResourceId) { 198 return ffi::GeckoResourceId{ 199 aResourceId, 200 ffi::GeckoResourceType::Required, 201 }; 202 } 203 204 /* static */ 205 ffi::GeckoResourceId L10nRegistry::ResourceIdToFFI( 206 const dom::OwningUTF8StringOrResourceId& aResourceId) { 207 if (aResourceId.IsUTF8String()) { 208 return ffi::GeckoResourceId{ 209 aResourceId.GetAsUTF8String(), 210 ffi::GeckoResourceType::Required, 211 }; 212 } 213 return ffi::GeckoResourceId{ 214 aResourceId.GetAsResourceId().mPath, 215 aResourceId.GetAsResourceId().mOptional 216 ? ffi::GeckoResourceType::Optional 217 : ffi::GeckoResourceType::Required, 218 }; 219 } 220 221 /* static */ 222 nsTArray<ffi::GeckoResourceId> L10nRegistry::ResourceIdsToFFI( 223 const nsTArray<nsCString>& aResourceIds) { 224 nsTArray<ffi::GeckoResourceId> ffiResourceIds; 225 for (const auto& resourceId : aResourceIds) { 226 ffiResourceIds.EmplaceBack(ResourceIdToFFI(resourceId)); 227 } 228 return ffiResourceIds; 229 } 230 231 /* static */ 232 nsTArray<ffi::GeckoResourceId> L10nRegistry::ResourceIdsToFFI( 233 const nsTArray<dom::OwningUTF8StringOrResourceId>& aResourceIds) { 234 nsTArray<ffi::GeckoResourceId> ffiResourceIds; 235 for (const auto& resourceId : aResourceIds) { 236 ffiResourceIds.EmplaceBack(ResourceIdToFFI(resourceId)); 237 } 238 return ffiResourceIds; 239 } 240 241 already_AddRefed<FluentBundleIterator> L10nRegistry::GenerateBundlesSync( 242 const nsTArray<nsCString>& aLocales, 243 const nsTArray<ffi::GeckoResourceId>& aResourceIds, ErrorResult& aRv) { 244 ffi::L10nRegistryStatus status; 245 UniquePtr<ffi::GeckoFluentBundleIterator> iter( 246 ffi::l10nregistry_generate_bundles_sync( 247 mRaw, aLocales.Elements(), aLocales.Length(), aResourceIds.Elements(), 248 aResourceIds.Length(), &status)); 249 250 if (PopulateError(aRv, status) || !iter) { 251 return nullptr; 252 } 253 254 return do_AddRef(new FluentBundleIterator(mGlobal, std::move(iter))); 255 } 256 257 already_AddRefed<FluentBundleIterator> L10nRegistry::GenerateBundlesSync( 258 const dom::Sequence<nsCString>& aLocales, 259 const dom::Sequence<dom::OwningUTF8StringOrResourceId>& aResourceIds, 260 ErrorResult& aRv) { 261 auto ffiResourceIds{ResourceIdsToFFI(aResourceIds)}; 262 return GenerateBundlesSync(aLocales, ffiResourceIds, aRv); 263 } 264 265 already_AddRefed<FluentBundleAsyncIterator> L10nRegistry::GenerateBundles( 266 const nsTArray<nsCString>& aLocales, 267 const nsTArray<ffi::GeckoResourceId>& aResourceIds, ErrorResult& aRv) { 268 ffi::L10nRegistryStatus status; 269 UniquePtr<ffi::GeckoFluentBundleAsyncIteratorWrapper> iter( 270 ffi::l10nregistry_generate_bundles( 271 mRaw, aLocales.Elements(), aLocales.Length(), aResourceIds.Elements(), 272 aResourceIds.Length(), &status)); 273 if (PopulateError(aRv, status) || !iter) { 274 return nullptr; 275 } 276 277 return do_AddRef(new FluentBundleAsyncIterator(mGlobal, std::move(iter))); 278 } 279 280 already_AddRefed<FluentBundleAsyncIterator> L10nRegistry::GenerateBundles( 281 const dom::Sequence<nsCString>& aLocales, 282 const dom::Sequence<dom::OwningUTF8StringOrResourceId>& aResourceIds, 283 ErrorResult& aRv) { 284 nsTArray<ffi::GeckoResourceId> resourceIds; 285 for (const auto& resourceId : aResourceIds) { 286 resourceIds.EmplaceBack(ResourceIdToFFI(resourceId)); 287 } 288 return GenerateBundles(aLocales, resourceIds, aRv); 289 } 290 291 /* static */ 292 void L10nRegistry::GetParentProcessFileSourceDescriptors( 293 nsTArray<L10nFileSourceDescriptor>& aRetVal) { 294 MOZ_ASSERT(XRE_IsParentProcess()); 295 nsTArray<ffi::L10nFileSourceDescriptor> sources; 296 ffi::l10nregistry_get_parent_process_sources(&sources); 297 for (const auto& source : sources) { 298 auto descriptor = aRetVal.AppendElement(); 299 descriptor->name() = source.name; 300 descriptor->metasource() = source.metasource; 301 descriptor->locales().AppendElements(std::move(source.locales)); 302 descriptor->prePath() = source.pre_path; 303 descriptor->index().AppendElements(std::move(source.index)); 304 } 305 } 306 307 /* static */ 308 void L10nRegistry::RegisterFileSourcesFromParentProcess( 309 const nsTArray<L10nFileSourceDescriptor>& aDescriptors) { 310 // This means that in content processes the L10nRegistry 311 // service instance is created eagerly, not lazily. 312 // It is necessary so that the instance can store the sources 313 // provided in the IPC init, which, in turn, is necessary 314 // for the service to be avialable for sync bundle generation. 315 // 316 // L10nRegistry is lightweight and performs no operations, so 317 // we believe this behavior to be acceptable. 318 MOZ_ASSERT(XRE_IsContentProcess()); 319 nsTArray<ffi::L10nFileSourceDescriptor> sources; 320 for (const auto& desc : aDescriptors) { 321 auto source = sources.AppendElement(); 322 source->name = desc.name(); 323 source->metasource = desc.metasource(); 324 source->locales.AppendElements(desc.locales()); 325 source->pre_path = desc.prePath(); 326 source->index.AppendElements(desc.index()); 327 } 328 ffi::l10nregistry_register_parent_process_sources(&sources); 329 } 330 331 /* static */ 332 nsresult L10nRegistry::Load(const nsACString& aPath, 333 nsIStreamLoaderObserver* aObserver) { 334 nsCOMPtr<nsIURI> uri; 335 nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath); 336 NS_ENSURE_SUCCESS(rv, rv); 337 NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG); 338 339 RefPtr<nsIStreamLoader> loader; 340 rv = NS_NewStreamLoader( 341 getter_AddRefs(loader), uri, aObserver, 342 nsContentUtils::GetSystemPrincipal(), 343 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 344 nsIContentPolicy::TYPE_OTHER); 345 346 return rv; 347 } 348 349 /* static */ 350 nsresult L10nRegistry::LoadSync(const nsACString& aPath, void** aData, 351 uint64_t* aSize) { 352 nsCOMPtr<nsIURI> uri; 353 354 nsresult rv = NS_NewURI(getter_AddRefs(uri), aPath); 355 NS_ENSURE_SUCCESS(rv, rv); 356 357 NS_ENSURE_TRUE(uri, NS_ERROR_INVALID_ARG); 358 359 auto result = URLPreloader::ReadURI(uri); 360 if (result.isOk()) { 361 auto uri = result.unwrap(); 362 *aData = ToNewCString(uri); 363 *aSize = uri.Length(); 364 return NS_OK; 365 } 366 367 auto err = result.unwrapErr(); 368 if (err != NS_ERROR_INVALID_ARG && err != NS_ERROR_NOT_INITIALIZED) { 369 return err; 370 } 371 372 nsCOMPtr<nsIChannel> channel; 373 rv = NS_NewChannel( 374 getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), 375 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 376 nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */ 377 nullptr, /* aPerformanceStorage */ 378 nullptr, /* aLoadGroup */ 379 nullptr, /* aCallbacks */ 380 nsIRequest::LOAD_NORMAL); 381 NS_ENSURE_SUCCESS(rv, rv); 382 383 // Don't warn on failure here, because it is triggered very frequently for 384 // necko.ftl which first tries and fails loading a resource://app/ URI before 385 // succeeding with a resource://gre/ URI. 386 nsCOMPtr<nsIInputStream> input; 387 if (NS_FAILED(channel->Open(getter_AddRefs(input)))) { 388 return NS_ERROR_INVALID_ARG; 389 } 390 391 return NS_ReadInputStreamToBuffer(input, aData, -1, aSize); 392 } 393 394 /* static */ 395 bool L10nRegistry::PopulateError(ErrorResult& aError, 396 ffi::L10nRegistryStatus& aStatus) { 397 switch (aStatus) { 398 case ffi::L10nRegistryStatus::InvalidLocaleCode: 399 aError.ThrowTypeError("Invalid locale code"); 400 return true; 401 case ffi::L10nRegistryStatus::EmptyName: 402 aError.ThrowTypeError("Name cannot be empty."); 403 return true; 404 405 case ffi::L10nRegistryStatus::None: 406 return false; 407 } 408 MOZ_ASSERT_UNREACHABLE("Unknown status"); 409 return false; 410 } 411 412 extern "C" { 413 nsresult L10nRegistryLoad(const nsACString* aPath, 414 const nsIStreamLoaderObserver* aObserver) { 415 if (!aPath || !aObserver) { 416 return NS_ERROR_INVALID_ARG; 417 } 418 419 return mozilla::intl::L10nRegistry::Load( 420 *aPath, const_cast<nsIStreamLoaderObserver*>(aObserver)); 421 } 422 423 nsresult L10nRegistryLoadSync(const nsACString* aPath, void** aData, 424 uint64_t* aSize) { 425 if (!aPath || !aData || !aSize) { 426 return NS_ERROR_INVALID_ARG; 427 } 428 429 return mozilla::intl::L10nRegistry::LoadSync(*aPath, aData, aSize); 430 } 431 432 void L10nRegistrySendUpdateL10nFileSources() { 433 MOZ_ASSERT(XRE_IsParentProcess()); 434 nsTArray<L10nFileSourceDescriptor> sources; 435 L10nRegistry::GetParentProcessFileSourceDescriptors(sources); 436 437 nsTArray<ContentParent*> parents; 438 ContentParent::GetAll(parents); 439 for (ContentParent* parent : parents) { 440 (void)parent->SendUpdateL10nFileSources(sources); 441 } 442 } 443 444 } // extern "C" 445 446 } // namespace mozilla::intl