Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/intl/l10n/DocumentL10n.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim:set ts=2 sw=2 sts=2 et cindent: */
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 "js/JSON.h"
8
#include "mozilla/dom/DocumentL10n.h"
9
#include "mozilla/dom/DocumentL10nBinding.h"
10
#include "mozilla/dom/Element.h"
11
#include "mozilla/dom/Event.h"
12
#include "mozilla/dom/Promise.h"
13
#include "mozilla/dom/PromiseNativeHandler.h"
14
#include "nsQueryObject.h"
15
#include "nsISupports.h"
16
#include "nsContentUtils.h"
17
18
namespace mozilla {
19
namespace dom {
20
21
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentL10n)
22
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
23
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
24
0
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
25
0
NS_INTERFACE_MAP_END
26
27
NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentL10n)
28
NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentL10n)
29
30
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentL10n, mDocument, mDOMLocalization, mReady)
31
32
DocumentL10n::DocumentL10n(nsIDocument* aDocument)
33
  : mDocument(aDocument),
34
    mState(DocumentL10nState::Initialized)
35
0
{
36
0
}
37
38
DocumentL10n::~DocumentL10n()
39
0
{
40
0
}
41
42
bool
43
DocumentL10n::Init(nsTArray<nsString>& aResourceIds)
44
0
{
45
0
  nsCOMPtr<mozIDOMLocalization> domL10n = do_CreateInstance("@mozilla.org/intl/domlocalization;1");
46
0
  if (NS_WARN_IF(!domL10n)) {
47
0
    return false;
48
0
  }
49
0
50
0
  nsIGlobalObject* global = mDocument->GetScopeObject();
51
0
  if (!global) {
52
0
    return false;
53
0
  }
54
0
55
0
  ErrorResult rv;
56
0
  mReady = Promise::Create(global, rv);
57
0
  if (rv.Failed()) {
58
0
    return false;
59
0
  }
60
0
  mDOMLocalization = domL10n;
61
0
62
0
  // The `aEager = true` here allows us to eagerly trigger
63
0
  // resource fetching to increase the chance that the l10n
64
0
  // resources will be ready by the time the document
65
0
  // is ready for localization.
66
0
  uint32_t ret;
67
0
  mDOMLocalization->AddResourceIds(aResourceIds, true, &ret);
68
0
69
0
  // Register observers for this instance of
70
0
  // mozDOMLocalization to allow it to retranslate
71
0
  // the document when locale changes or pseudolocalization
72
0
  // gets turned on.
73
0
  mDOMLocalization->RegisterObservers();
74
0
  return true;
75
0
}
76
77
JSObject*
78
DocumentL10n::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
79
0
{
80
0
  return DocumentL10n_Binding::Wrap(aCx, this, aGivenProto);
81
0
}
82
83
NS_IMETHODIMP
84
DocumentL10n::HandleEvent(Event* aEvent)
85
0
{
86
#ifdef DEBUG
87
  nsAutoString eventType;
88
  aEvent->GetType(eventType);
89
  MOZ_ASSERT(eventType.EqualsLiteral("MozBeforeInitialXULLayout"));
90
#endif
91
92
0
  TriggerInitialDocumentTranslation();
93
0
94
0
  return NS_OK;
95
0
}
96
97
uint32_t
98
DocumentL10n::AddResourceIds(nsTArray<nsString>& aResourceIds)
99
0
{
100
0
  uint32_t ret = 0;
101
0
  mDOMLocalization->AddResourceIds(aResourceIds, false, &ret);
102
0
  return ret;
103
0
}
104
105
uint32_t
106
DocumentL10n::RemoveResourceIds(nsTArray<nsString>& aResourceIds)
107
0
{
108
0
  // We need to guard against a scenario where the
109
0
  // mDOMLocalization has been unlinked, but the elements
110
0
  // are only now removed from DOM.
111
0
  if (!mDOMLocalization) {
112
0
    return 0;
113
0
  }
114
0
  uint32_t ret = 0;
115
0
  mDOMLocalization->RemoveResourceIds(aResourceIds, &ret);
116
0
  return ret;
117
0
}
118
119
already_AddRefed<Promise>
120
DocumentL10n::FormatMessages(JSContext* aCx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv)
121
0
{
122
0
  nsTArray<JS::Value> jsKeys;
123
0
  SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
124
0
  for (auto& key : aKeys) {
125
0
    JS::RootedValue jsKey(aCx);
126
0
    if (!ToJSValue(aCx, key, &jsKey)) {
127
0
      aRv.NoteJSContextException(aCx);
128
0
      return nullptr;
129
0
    }
130
0
    jsKeys.AppendElement(jsKey);
131
0
  }
132
0
133
0
  RefPtr<Promise> promise;
134
0
  aRv = mDOMLocalization->FormatMessages(jsKeys, getter_AddRefs(promise));
135
0
  if (aRv.Failed()) {
136
0
    return nullptr;
137
0
  }
138
0
139
0
  return promise.forget();
140
0
}
141
142
already_AddRefed<Promise>
143
DocumentL10n::FormatValues(JSContext* aCx, const Sequence<L10nKey>& aKeys, ErrorResult& aRv)
144
0
{
145
0
  nsTArray<JS::Value> jsKeys;
146
0
  SequenceRooter<JS::Value> rooter(aCx, &jsKeys);
147
0
  for (auto& key : aKeys) {
148
0
    JS::RootedValue jsKey(aCx);
149
0
    if (!ToJSValue(aCx, key, &jsKey)) {
150
0
      aRv.NoteJSContextException(aCx);
151
0
      return nullptr;
152
0
    }
153
0
    jsKeys.AppendElement(jsKey);
154
0
  }
155
0
156
0
  RefPtr<Promise> promise;
157
0
  aRv = mDOMLocalization->FormatValues(jsKeys, getter_AddRefs(promise));
158
0
  if (aRv.Failed()) {
159
0
    return nullptr;
160
0
  }
161
0
162
0
  return promise.forget();
163
0
}
164
165
already_AddRefed<Promise>
166
DocumentL10n::FormatValue(JSContext* aCx, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv)
167
0
{
168
0
  JS::Rooted<JS::Value> args(aCx);
169
0
170
0
  if (aArgs.WasPassed()) {
171
0
    args = JS::ObjectValue(*aArgs.Value());
172
0
  } else {
173
0
    args = JS::UndefinedValue();
174
0
  }
175
0
176
0
  RefPtr<Promise> promise;
177
0
  nsresult rv = mDOMLocalization->FormatValue(aId, args, getter_AddRefs(promise));
178
0
  if (NS_FAILED(rv)) {
179
0
    aRv.Throw(rv);
180
0
    return nullptr;
181
0
  }
182
0
  return promise.forget();
183
0
}
184
185
void
186
DocumentL10n::SetAttributes(JSContext* aCx, Element& aElement, const nsAString& aId, const Optional<JS::Handle<JSObject*>>& aArgs, ErrorResult& aRv)
187
0
{
188
0
  aElement.SetAttribute(NS_LITERAL_STRING("data-l10n-id"), aId, aRv);
189
0
  if (aRv.Failed()) {
190
0
    return;
191
0
  }
192
0
  if (aArgs.WasPassed()) {
193
0
    nsAutoString data;
194
0
    JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*aArgs.Value()));
195
0
    if (!nsContentUtils::StringifyJSON(aCx, &val, data)) {
196
0
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
197
0
      return;
198
0
    }
199
0
    aElement.SetAttribute(NS_LITERAL_STRING("data-l10n-args"), data, aRv);
200
0
  } else {
201
0
    aElement.RemoveAttribute(NS_LITERAL_STRING("data-l10n-args"), aRv);
202
0
  }
203
0
}
204
205
void
206
DocumentL10n::GetAttributes(JSContext* aCx, Element& aElement, L10nKey& aResult, ErrorResult& aRv)
207
0
{
208
0
  nsAutoString l10nId;
209
0
  nsAutoString l10nArgs;
210
0
211
0
  aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nid, l10nId);
212
0
  aResult.mId = l10nId;
213
0
214
0
  aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::datal10nargs, l10nArgs);
215
0
  if (!l10nArgs.IsEmpty()) {
216
0
    JS::Rooted<JS::Value> json(aCx);
217
0
    if (!JS_ParseJSON(aCx, l10nArgs.get(), l10nArgs.Length(), &json)) {
218
0
      aRv.NoteJSContextException(aCx);
219
0
      return;
220
0
    } else if (!json.isObject()) {
221
0
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
222
0
      return;
223
0
    }
224
0
    aResult.mArgs.Construct();
225
0
    aResult.mArgs.Value() = &json.toObject();
226
0
  }
227
0
}
228
229
already_AddRefed<Promise>
230
DocumentL10n::TranslateFragment(nsINode& aNode, ErrorResult& aRv)
231
0
{
232
0
  RefPtr<Promise> promise;
233
0
  nsresult rv = mDOMLocalization->TranslateFragment(&aNode, getter_AddRefs(promise));
234
0
  if (NS_FAILED(rv)) {
235
0
    aRv.Throw(rv);
236
0
    return nullptr;
237
0
  }
238
0
  return promise.forget();
239
0
}
240
241
already_AddRefed<Promise>
242
DocumentL10n::TranslateElements(const Sequence<OwningNonNull<Element>>& aElements, ErrorResult& aRv)
243
0
{
244
0
  AutoTArray<RefPtr<Element>, 10> elements;
245
0
  elements.SetCapacity(aElements.Length());
246
0
  for (auto& element : aElements) {
247
0
    elements.AppendElement(element);
248
0
  }
249
0
  RefPtr<Promise> promise;
250
0
  aRv = mDOMLocalization->TranslateElements(
251
0
      elements, getter_AddRefs(promise));
252
0
  if (aRv.Failed()) {
253
0
    return nullptr;
254
0
  }
255
0
  return promise.forget();
256
0
}
257
258
class L10nReadyHandler final : public PromiseNativeHandler
259
{
260
public:
261
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
262
  NS_DECL_CYCLE_COLLECTION_CLASS(L10nReadyHandler)
263
264
  explicit L10nReadyHandler(Promise* aPromise)
265
    : mPromise(aPromise)
266
0
  {
267
0
  }
268
269
  void
270
  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
271
0
  {
272
0
    mPromise->MaybeResolveWithUndefined();
273
0
  }
274
275
  void
276
  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
277
0
  {
278
0
    mPromise->MaybeRejectWithUndefined();
279
0
  }
280
281
private:
282
0
  ~L10nReadyHandler() = default;
283
284
  RefPtr<Promise> mPromise;
285
};
286
287
NS_IMPL_CYCLE_COLLECTION(L10nReadyHandler, mPromise)
288
289
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nReadyHandler)
290
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
291
0
NS_INTERFACE_MAP_END
292
293
NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nReadyHandler)
294
NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nReadyHandler)
295
296
void
297
DocumentL10n::TriggerInitialDocumentTranslation()
298
0
{
299
0
  if (mState == DocumentL10nState::InitialTranslationTriggered) {
300
0
    return;
301
0
  }
302
0
303
0
  mState = DocumentL10nState::InitialTranslationTriggered;
304
0
305
0
  Element* elem = mDocument->GetDocumentElement();
306
0
  if (elem) {
307
0
    mDOMLocalization->ConnectRoot(elem);
308
0
  }
309
0
310
0
  RefPtr<Promise> promise;
311
0
  mDOMLocalization->TranslateRoots(getter_AddRefs(promise));
312
0
  RefPtr<PromiseNativeHandler> l10nReadyHandler = new L10nReadyHandler(mReady);
313
0
  promise->AppendNativeHandler(l10nReadyHandler);
314
0
}
315
316
Promise*
317
DocumentL10n::Ready()
318
0
{
319
0
  return mReady;
320
0
}
321
322
} // namespace dom
323
} // namespace mozilla