1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "Localization.h"
8 #include "nsIObserverService.h"
9 #include "mozilla/BasePrincipal.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/Services.h"
12 #include "mozilla/dom/PromiseNativeHandler.h"
13
14 #define INTL_APP_LOCALES_CHANGED "intl:app-locales-changed"
15 #define L10N_PSEUDO_PREF "intl.l10n.pseudo"
16
17 using namespace mozilla;
18 using namespace mozilla::dom;
19 using namespace mozilla::intl;
20
21 static const char* kObservedPrefs[] = {L10N_PSEUDO_PREF, nullptr};
22
ConvertFromL10nKeys(const Sequence<OwningUTF8StringOrL10nIdArgs> & aKeys)23 static nsTArray<ffi::L10nKey> ConvertFromL10nKeys(
24 const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys) {
25 nsTArray<ffi::L10nKey> l10nKeys(aKeys.Length());
26
27 for (const auto& entry : aKeys) {
28 if (entry.IsUTF8String()) {
29 const auto& id = entry.GetAsUTF8String();
30 ffi::L10nKey* key = l10nKeys.AppendElement();
31 key->id = &id;
32 } else {
33 const auto& e = entry.GetAsL10nIdArgs();
34 ffi::L10nKey* key = l10nKeys.AppendElement();
35 key->id = &e.mId;
36 if (!e.mArgs.IsNull()) {
37 FluentBundle::ConvertArgs(e.mArgs.Value(), key->args);
38 }
39 }
40 }
41
42 return l10nKeys;
43 }
44
ConvertToAttributeNameValue(const nsTArray<ffi::L10nAttribute> & aAttributes,FallibleTArray<AttributeNameValue> & aValues)45 [[nodiscard]] static bool ConvertToAttributeNameValue(
46 const nsTArray<ffi::L10nAttribute>& aAttributes,
47 FallibleTArray<AttributeNameValue>& aValues) {
48 if (!aValues.SetCapacity(aAttributes.Length(), fallible)) {
49 return false;
50 }
51 for (const auto& attr : aAttributes) {
52 auto* cvtAttr = aValues.AppendElement(fallible);
53 MOZ_ASSERT(cvtAttr, "SetCapacity didn't set enough capacity somehow?");
54 cvtAttr->mName = attr.name;
55 cvtAttr->mValue = attr.value;
56 }
57 return true;
58 }
59
ConvertToL10nMessages(const nsTArray<ffi::OptionalL10nMessage> & aMessages,nsTArray<Nullable<L10nMessage>> & aOut)60 [[nodiscard]] static bool ConvertToL10nMessages(
61 const nsTArray<ffi::OptionalL10nMessage>& aMessages,
62 nsTArray<Nullable<L10nMessage>>& aOut) {
63 if (!aOut.SetCapacity(aMessages.Length(), fallible)) {
64 return false;
65 }
66
67 for (const auto& entry : aMessages) {
68 Nullable<L10nMessage>* msg = aOut.AppendElement(fallible);
69 MOZ_ASSERT(msg, "SetCapacity didn't set enough capacity somehow?");
70
71 if (!entry.is_present) {
72 continue;
73 }
74
75 L10nMessage& m = msg->SetValue();
76 if (!entry.message.value.IsVoid()) {
77 m.mValue = entry.message.value;
78 }
79 if (!entry.message.attributes.IsEmpty()) {
80 auto& value = m.mAttributes.SetValue();
81 if (!ConvertToAttributeNameValue(entry.message.attributes, value)) {
82 return false;
83 }
84 }
85 }
86
87 return true;
88 }
89
90 NS_IMPL_CYCLE_COLLECTING_ADDREF(Localization)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Localization)91 NS_IMPL_CYCLE_COLLECTING_RELEASE(Localization)
92 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Localization)
93 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
94 NS_INTERFACE_MAP_ENTRY(nsIObserver)
95 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
96 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
97 NS_INTERFACE_MAP_END
98 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK(Localization, mGlobal)
99
100 /* static */
101 already_AddRefed<Localization> Localization::Create(
102 const nsTArray<nsCString>& aResourceIds, bool aIsSync) {
103 return MakeAndAddRef<Localization>(aResourceIds, aIsSync);
104 }
105
106 /* static */
Create(const nsTArray<ffi::GeckoResourceId> & aResourceIds,bool aIsSync)107 already_AddRefed<Localization> Localization::Create(
108 const nsTArray<ffi::GeckoResourceId>& aResourceIds, bool aIsSync) {
109 return MakeAndAddRef<Localization>(aResourceIds, aIsSync);
110 }
111
Localization(const nsTArray<nsCString> & aResIds,bool aIsSync)112 Localization::Localization(const nsTArray<nsCString>& aResIds, bool aIsSync) {
113 auto ffiResourceIds{L10nRegistry::ResourceIdsToFFI(aResIds)};
114 ffi::localization_new(&ffiResourceIds, aIsSync, nullptr,
115 getter_AddRefs(mRaw));
116
117 RegisterObservers();
118 }
119
Localization(const nsTArray<ffi::GeckoResourceId> & aResIds,bool aIsSync)120 Localization::Localization(const nsTArray<ffi::GeckoResourceId>& aResIds,
121 bool aIsSync) {
122 ffi::localization_new(&aResIds, aIsSync, nullptr, getter_AddRefs(mRaw));
123
124 RegisterObservers();
125 }
126
Localization(nsIGlobalObject * aGlobal,const nsTArray<nsCString> & aResIds,bool aIsSync)127 Localization::Localization(nsIGlobalObject* aGlobal,
128 const nsTArray<nsCString>& aResIds, bool aIsSync)
129 : mGlobal(aGlobal) {
130 nsTArray<ffi::GeckoResourceId> resourceIds{
131 L10nRegistry::ResourceIdsToFFI(aResIds)};
132 ffi::localization_new(&resourceIds, aIsSync, nullptr, getter_AddRefs(mRaw));
133
134 RegisterObservers();
135 }
136
Localization(nsIGlobalObject * aGlobal,bool aIsSync)137 Localization::Localization(nsIGlobalObject* aGlobal, bool aIsSync)
138 : mGlobal(aGlobal) {
139 nsTArray<ffi::GeckoResourceId> resIds;
140 ffi::localization_new(&resIds, aIsSync, nullptr, getter_AddRefs(mRaw));
141
142 RegisterObservers();
143 }
144
Localization(nsIGlobalObject * aGlobal,bool aIsSync,const ffi::LocalizationRc * aRaw)145 Localization::Localization(nsIGlobalObject* aGlobal, bool aIsSync,
146 const ffi::LocalizationRc* aRaw)
147 : mGlobal(aGlobal), mRaw(aRaw) {
148 RegisterObservers();
149 }
150
Constructor(const GlobalObject & aGlobal,const Sequence<OwningUTF8StringOrResourceId> & aResourceIds,bool aIsSync,const Optional<NonNull<L10nRegistry>> & aRegistry,const Optional<Sequence<nsCString>> & aLocales,ErrorResult & aRv)151 already_AddRefed<Localization> Localization::Constructor(
152 const GlobalObject& aGlobal,
153 const Sequence<OwningUTF8StringOrResourceId>& aResourceIds, bool aIsSync,
154 const Optional<NonNull<L10nRegistry>>& aRegistry,
155 const Optional<Sequence<nsCString>>& aLocales, ErrorResult& aRv) {
156 auto ffiResourceIds{L10nRegistry::ResourceIdsToFFI(aResourceIds)};
157 Maybe<nsTArray<nsCString>> locales;
158
159 if (aLocales.WasPassed()) {
160 locales.emplace();
161 locales->SetCapacity(aLocales.Value().Length());
162 for (const auto& locale : aLocales.Value()) {
163 locales->AppendElement(locale);
164 }
165 }
166
167 RefPtr<const ffi::LocalizationRc> raw;
168
169 bool result = ffi::localization_new_with_locales(
170 &ffiResourceIds, aIsSync,
171 aRegistry.WasPassed() ? aRegistry.Value().Raw() : nullptr,
172 locales.ptrOr(nullptr), getter_AddRefs(raw));
173
174 if (!result) {
175 aRv.ThrowInvalidStateError(
176 "Failed to create the Localization. Check the locales arguments.");
177 return nullptr;
178 }
179
180 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
181
182 return do_AddRef(new Localization(global, aIsSync, raw));
183 }
184
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)185 JSObject* Localization::WrapObject(JSContext* aCx,
186 JS::Handle<JSObject*> aGivenProto) {
187 return Localization_Binding::Wrap(aCx, this, aGivenProto);
188 }
189
190 Localization::~Localization() = default;
191
192 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)193 Localization::Observe(nsISupports* aSubject, const char* aTopic,
194 const char16_t* aData) {
195 if (!strcmp(aTopic, INTL_APP_LOCALES_CHANGED)) {
196 OnChange();
197 } else {
198 MOZ_ASSERT(!strcmp("nsPref:changed", aTopic));
199 nsDependentString pref(aData);
200 if (pref.EqualsLiteral(L10N_PSEUDO_PREF)) {
201 OnChange();
202 }
203 }
204
205 return NS_OK;
206 }
207
RegisterObservers()208 void Localization::RegisterObservers() {
209 DebugOnly<nsresult> rv = Preferences::AddWeakObservers(this, kObservedPrefs);
210 MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
211
212 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
213 if (obs) {
214 obs->AddObserver(this, INTL_APP_LOCALES_CHANGED, true);
215 }
216 }
217
OnChange()218 void Localization::OnChange() { ffi::localization_on_change(mRaw.get()); }
219
AddResourceId(const ffi::GeckoResourceId & aResourceId)220 void Localization::AddResourceId(const ffi::GeckoResourceId& aResourceId) {
221 ffi::localization_add_res_id(mRaw.get(), &aResourceId);
222 }
AddResourceId(const nsCString & aResourceId)223 void Localization::AddResourceId(const nsCString& aResourceId) {
224 auto ffiResourceId{L10nRegistry::ResourceIdToFFI(aResourceId)};
225 AddResourceId(ffiResourceId);
226 }
AddResourceId(const dom::OwningUTF8StringOrResourceId & aResourceId)227 void Localization::AddResourceId(
228 const dom::OwningUTF8StringOrResourceId& aResourceId) {
229 auto ffiResourceId{L10nRegistry::ResourceIdToFFI(aResourceId)};
230 AddResourceId(ffiResourceId);
231 }
232
RemoveResourceId(const ffi::GeckoResourceId & aResourceId)233 uint32_t Localization::RemoveResourceId(
234 const ffi::GeckoResourceId& aResourceId) {
235 return ffi::localization_remove_res_id(mRaw.get(), &aResourceId);
236 }
RemoveResourceId(const nsCString & aResourceId)237 uint32_t Localization::RemoveResourceId(const nsCString& aResourceId) {
238 auto ffiResourceId{L10nRegistry::ResourceIdToFFI(aResourceId)};
239 return RemoveResourceId(ffiResourceId);
240 }
RemoveResourceId(const dom::OwningUTF8StringOrResourceId & aResourceId)241 uint32_t Localization::RemoveResourceId(
242 const dom::OwningUTF8StringOrResourceId& aResourceId) {
243 auto ffiResourceId{L10nRegistry::ResourceIdToFFI(aResourceId)};
244 return RemoveResourceId(ffiResourceId);
245 }
246
AddResourceIds(const nsTArray<dom::OwningUTF8StringOrResourceId> & aResourceIds)247 void Localization::AddResourceIds(
248 const nsTArray<dom::OwningUTF8StringOrResourceId>& aResourceIds) {
249 auto ffiResourceIds{L10nRegistry::ResourceIdsToFFI(aResourceIds)};
250 ffi::localization_add_res_ids(mRaw.get(), &ffiResourceIds);
251 }
252
RemoveResourceIds(const nsTArray<dom::OwningUTF8StringOrResourceId> & aResourceIds)253 uint32_t Localization::RemoveResourceIds(
254 const nsTArray<dom::OwningUTF8StringOrResourceId>& aResourceIds) {
255 auto ffiResourceIds{L10nRegistry::ResourceIdsToFFI(aResourceIds)};
256 return ffi::localization_remove_res_ids(mRaw.get(), &ffiResourceIds);
257 }
258
FormatValue(const nsACString & aId,const Optional<L10nArgs> & aArgs,ErrorResult & aRv)259 already_AddRefed<Promise> Localization::FormatValue(
260 const nsACString& aId, const Optional<L10nArgs>& aArgs, ErrorResult& aRv) {
261 nsTArray<ffi::L10nArg> l10nArgs;
262 nsTArray<nsCString> errors;
263
264 if (aArgs.WasPassed()) {
265 const L10nArgs& args = aArgs.Value();
266 FluentBundle::ConvertArgs(args, l10nArgs);
267 }
268 RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
269
270 ffi::localization_format_value(
271 mRaw.get(), &aId, &l10nArgs, promise,
272 [](const Promise* aPromise, const nsACString* aValue,
273 const nsTArray<nsCString>* aErrors) {
274 Promise* promise = const_cast<Promise*>(aPromise);
275
276 ErrorResult rv;
277 if (MaybeReportErrorsToGecko(*aErrors, rv,
278 promise->GetParentObject())) {
279 promise->MaybeReject(std::move(rv));
280 } else {
281 promise->MaybeResolve(aValue);
282 }
283 });
284
285 return MaybeWrapPromise(promise);
286 }
287
FormatValues(const Sequence<OwningUTF8StringOrL10nIdArgs> & aKeys,ErrorResult & aRv)288 already_AddRefed<Promise> Localization::FormatValues(
289 const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys, ErrorResult& aRv) {
290 nsTArray<ffi::L10nKey> l10nKeys = ConvertFromL10nKeys(aKeys);
291
292 RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
293 if (aRv.Failed()) {
294 return nullptr;
295 }
296
297 ffi::localization_format_values(
298 mRaw.get(), &l10nKeys, promise,
299 // callback function which will be invoked by the rust code, passing the
300 // promise back in.
301 [](const Promise* aPromise, const nsTArray<nsCString>* aValues,
302 const nsTArray<nsCString>* aErrors) {
303 Promise* promise = const_cast<Promise*>(aPromise);
304
305 ErrorResult rv;
306 if (MaybeReportErrorsToGecko(*aErrors, rv,
307 promise->GetParentObject())) {
308 promise->MaybeReject(std::move(rv));
309 } else {
310 promise->MaybeResolve(*aValues);
311 }
312 });
313
314 return MaybeWrapPromise(promise);
315 }
316
FormatMessages(const Sequence<OwningUTF8StringOrL10nIdArgs> & aKeys,ErrorResult & aRv)317 already_AddRefed<Promise> Localization::FormatMessages(
318 const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys, ErrorResult& aRv) {
319 auto l10nKeys = ConvertFromL10nKeys(aKeys);
320
321 RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
322 if (aRv.Failed()) {
323 return nullptr;
324 }
325
326 ffi::localization_format_messages(
327 mRaw.get(), &l10nKeys, promise,
328 // callback function which will be invoked by the rust code, passing the
329 // promise back in.
330 [](const Promise* aPromise,
331 const nsTArray<ffi::OptionalL10nMessage>* aRaw,
332 const nsTArray<nsCString>* aErrors) {
333 Promise* promise = const_cast<Promise*>(aPromise);
334
335 ErrorResult rv;
336 if (MaybeReportErrorsToGecko(*aErrors, rv,
337 promise->GetParentObject())) {
338 promise->MaybeReject(std::move(rv));
339 } else {
340 nsTArray<Nullable<L10nMessage>> messages;
341 if (!ConvertToL10nMessages(*aRaw, messages)) {
342 promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
343 } else {
344 promise->MaybeResolve(std::move(messages));
345 }
346 }
347 });
348
349 return MaybeWrapPromise(promise);
350 }
351
FormatValueSync(const nsACString & aId,const Optional<L10nArgs> & aArgs,nsACString & aRetVal,ErrorResult & aRv)352 void Localization::FormatValueSync(const nsACString& aId,
353 const Optional<L10nArgs>& aArgs,
354 nsACString& aRetVal, ErrorResult& aRv) {
355 nsTArray<ffi::L10nArg> l10nArgs;
356 nsTArray<nsCString> errors;
357
358 if (aArgs.WasPassed()) {
359 const L10nArgs& args = aArgs.Value();
360 FluentBundle::ConvertArgs(args, l10nArgs);
361 }
362
363 bool rv = ffi::localization_format_value_sync(mRaw.get(), &aId, &l10nArgs,
364 &aRetVal, &errors);
365
366 if (rv) {
367 MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
368 } else {
369 aRv.ThrowInvalidStateError(
370 "Can't use formatValueSync when state is async.");
371 }
372 }
373
FormatValuesSync(const Sequence<OwningUTF8StringOrL10nIdArgs> & aKeys,nsTArray<nsCString> & aRetVal,ErrorResult & aRv)374 void Localization::FormatValuesSync(
375 const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
376 nsTArray<nsCString>& aRetVal, ErrorResult& aRv) {
377 nsTArray<ffi::L10nKey> l10nKeys(aKeys.Length());
378 nsTArray<nsCString> errors;
379
380 for (const auto& entry : aKeys) {
381 if (entry.IsUTF8String()) {
382 const auto& id = entry.GetAsUTF8String();
383 nsTArray<ffi::L10nArg> l10nArgs;
384 ffi::L10nKey* key = l10nKeys.AppendElement();
385 key->id = &id;
386 } else {
387 const auto& e = entry.GetAsL10nIdArgs();
388 nsTArray<ffi::L10nArg> l10nArgs;
389 ffi::L10nKey* key = l10nKeys.AppendElement();
390 key->id = &e.mId;
391 if (!e.mArgs.IsNull()) {
392 FluentBundle::ConvertArgs(e.mArgs.Value(), key->args);
393 }
394 }
395 }
396
397 bool rv = ffi::localization_format_values_sync(mRaw.get(), &l10nKeys,
398 &aRetVal, &errors);
399
400 if (rv) {
401 MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
402 } else {
403 aRv.ThrowInvalidStateError(
404 "Can't use formatValuesSync when state is async.");
405 }
406 }
407
FormatMessagesSync(const Sequence<OwningUTF8StringOrL10nIdArgs> & aKeys,nsTArray<Nullable<L10nMessage>> & aRetVal,ErrorResult & aRv)408 void Localization::FormatMessagesSync(
409 const Sequence<OwningUTF8StringOrL10nIdArgs>& aKeys,
410 nsTArray<Nullable<L10nMessage>>& aRetVal, ErrorResult& aRv) {
411 nsTArray<ffi::L10nKey> l10nKeys(aKeys.Length());
412 nsTArray<nsCString> errors;
413
414 for (const auto& entry : aKeys) {
415 if (entry.IsUTF8String()) {
416 const auto& id = entry.GetAsUTF8String();
417 nsTArray<ffi::L10nArg> l10nArgs;
418 ffi::L10nKey* key = l10nKeys.AppendElement();
419 key->id = &id;
420 } else {
421 const auto& e = entry.GetAsL10nIdArgs();
422 nsTArray<ffi::L10nArg> l10nArgs;
423 ffi::L10nKey* key = l10nKeys.AppendElement();
424 key->id = &e.mId;
425 if (!e.mArgs.IsNull()) {
426 FluentBundle::ConvertArgs(e.mArgs.Value(), key->args);
427 }
428 }
429 }
430
431 nsTArray<ffi::OptionalL10nMessage> result(l10nKeys.Length());
432
433 bool rv = ffi::localization_format_messages_sync(mRaw.get(), &l10nKeys,
434 &result, &errors);
435
436 if (!rv) {
437 return aRv.ThrowInvalidStateError(
438 "Can't use formatMessagesSync when state is async.");
439 }
440 MaybeReportErrorsToGecko(errors, aRv, GetParentObject());
441 if (aRv.Failed()) {
442 return;
443 }
444 if (!ConvertToL10nMessages(result, aRetVal)) {
445 return aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
446 }
447 }
448
SetAsync()449 void Localization::SetAsync() { ffi::localization_set_async(mRaw.get()); }
IsSync()450 bool Localization::IsSync() { return ffi::localization_is_sync(mRaw.get()); }
451
452 /**
453 * PromiseResolver is a PromiseNativeHandler used
454 * by MaybeWrapPromise method.
455 */
456 class PromiseResolver final : public PromiseNativeHandler {
457 public:
458 NS_DECL_ISUPPORTS
459
460 explicit PromiseResolver(Promise* aPromise);
461 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
462 ErrorResult& aRv) override;
463 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
464 ErrorResult& aRv) override;
465
466 protected:
467 virtual ~PromiseResolver();
468
469 RefPtr<Promise> mPromise;
470 };
471
472 NS_INTERFACE_MAP_BEGIN(PromiseResolver)
NS_INTERFACE_MAP_ENTRY(nsISupports)473 NS_INTERFACE_MAP_ENTRY(nsISupports)
474 NS_INTERFACE_MAP_END
475
476 NS_IMPL_ADDREF(PromiseResolver)
477 NS_IMPL_RELEASE(PromiseResolver)
478
479 PromiseResolver::PromiseResolver(Promise* aPromise) { mPromise = aPromise; }
480
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)481 void PromiseResolver::ResolvedCallback(JSContext* aCx,
482 JS::Handle<JS::Value> aValue,
483 ErrorResult& aRv) {
484 mPromise->MaybeResolveWithClone(aCx, aValue);
485 }
486
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)487 void PromiseResolver::RejectedCallback(JSContext* aCx,
488 JS::Handle<JS::Value> aValue,
489 ErrorResult& aRv) {
490 mPromise->MaybeRejectWithClone(aCx, aValue);
491 }
492
~PromiseResolver()493 PromiseResolver::~PromiseResolver() { mPromise = nullptr; }
494
495 /**
496 * MaybeWrapPromise is a helper method used by Localization
497 * API methods to clone the value returned by a promise
498 * into a new context.
499 *
500 * This allows for a promise from a privileged context
501 * to be returned into an unprivileged document.
502 *
503 * This method is only used for promises that carry values.
504 */
MaybeWrapPromise(Promise * aInnerPromise)505 already_AddRefed<Promise> Localization::MaybeWrapPromise(
506 Promise* aInnerPromise) {
507 // For system principal we don't need to wrap the
508 // result promise at all.
509 nsIPrincipal* principal = mGlobal->PrincipalOrNull();
510 if (principal && principal->IsSystemPrincipal()) {
511 return RefPtr<Promise>(aInnerPromise).forget();
512 }
513
514 ErrorResult result;
515 RefPtr<Promise> docPromise = Promise::Create(mGlobal, result);
516 if (NS_WARN_IF(result.Failed())) {
517 return nullptr;
518 }
519
520 RefPtr<PromiseResolver> resolver = new PromiseResolver(docPromise);
521 aInnerPromise->AppendNativeHandler(resolver);
522 return docPromise.forget();
523 }
524