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