Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / l10n / DocumentL10n.cpp
blob760cd7f3328b6b35f529d774dd173a322291657e
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/. */
7 #include "DocumentL10n.h"
8 #include "nsIContentSink.h"
9 #include "mozilla/dom/AutoEntryScript.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/DocumentL10nBinding.h"
12 #include "mozilla/Telemetry.h"
14 using namespace mozilla::dom;
16 NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentL10n)
17 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentL10n, DOMLocalization)
18 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
19 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady)
20 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentSink)
21 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
22 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
23 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentL10n, DOMLocalization)
24 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
25 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady)
26 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentSink)
27 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
29 NS_IMPL_ADDREF_INHERITED(DocumentL10n, DOMLocalization)
30 NS_IMPL_RELEASE_INHERITED(DocumentL10n, DOMLocalization)
32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentL10n)
33 NS_INTERFACE_MAP_END_INHERITING(DOMLocalization)
35 bool DocumentL10n::mIsFirstBrowserWindow = true;
37 /* static */
38 RefPtr<DocumentL10n> DocumentL10n::Create(Document* aDocument,
39 const bool aSync) {
40 RefPtr<DocumentL10n> l10n = new DocumentL10n(aDocument, aSync);
42 if (!l10n->Init()) {
43 return nullptr;
45 return l10n.forget();
48 DocumentL10n::DocumentL10n(Document* aDocument, const bool aSync)
49 : DOMLocalization(aDocument->GetScopeObject(), aSync, {}),
50 mDocument(aDocument),
51 mState(DocumentL10nState::Constructed) {
52 mContentSink = do_QueryInterface(aDocument->GetCurrentContentSink());
55 bool DocumentL10n::Init() {
56 DOMLocalization::Init();
57 ErrorResult rv;
58 mReady = Promise::Create(mGlobal, rv);
59 if (NS_WARN_IF(rv.Failed())) {
60 return false;
62 return true;
65 JSObject* DocumentL10n::WrapObject(JSContext* aCx,
66 JS::Handle<JSObject*> aGivenProto) {
67 return DocumentL10n_Binding::Wrap(aCx, this, aGivenProto);
70 class L10nReadyHandler final : public PromiseNativeHandler {
71 public:
72 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
73 NS_DECL_CYCLE_COLLECTION_CLASS(L10nReadyHandler)
75 explicit L10nReadyHandler(Promise* aPromise, DocumentL10n* aDocumentL10n)
76 : mPromise(aPromise), mDocumentL10n(aDocumentL10n) {}
78 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
79 mDocumentL10n->InitialTranslationCompleted(true);
80 mPromise->MaybeResolveWithUndefined();
83 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
84 mDocumentL10n->InitialTranslationCompleted(false);
85 mPromise->MaybeRejectWithUndefined();
88 private:
89 ~L10nReadyHandler() = default;
91 RefPtr<Promise> mPromise;
92 RefPtr<DocumentL10n> mDocumentL10n;
95 NS_IMPL_CYCLE_COLLECTION(L10nReadyHandler, mPromise, mDocumentL10n)
97 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nReadyHandler)
98 NS_INTERFACE_MAP_ENTRY(nsISupports)
99 NS_INTERFACE_MAP_END
101 NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nReadyHandler)
102 NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nReadyHandler)
104 void DocumentL10n::TriggerInitialTranslation() {
105 if (mState >= DocumentL10nState::InitialTranslationTriggered) {
106 return;
108 mInitialTranslationStart = mozilla::TimeStamp::NowUnfuzzed();
110 AutoAllowLegacyScriptExecution exemption;
112 nsTArray<RefPtr<Promise>> promises;
114 ErrorResult rv;
115 promises.AppendElement(TranslateDocument(rv));
116 if (NS_WARN_IF(rv.Failed())) {
117 InitialTranslationCompleted(false);
118 mReady->MaybeRejectWithUndefined();
119 return;
121 promises.AppendElement(TranslateRoots(rv));
122 Element* documentElement = mDocument->GetDocumentElement();
123 if (!documentElement) {
124 InitialTranslationCompleted(false);
125 mReady->MaybeRejectWithUndefined();
126 return;
129 DOMLocalization::ConnectRoot(*documentElement, rv);
130 if (NS_WARN_IF(rv.Failed())) {
131 InitialTranslationCompleted(false);
132 mReady->MaybeRejectWithUndefined();
133 return;
136 AutoEntryScript aes(mGlobal, "DocumentL10n InitialTranslation");
137 RefPtr<Promise> promise = Promise::All(aes.cx(), promises, rv);
139 if (promise->State() == Promise::PromiseState::Resolved) {
140 // If the promise is already resolved, we can fast-track
141 // to initial translation completed.
142 InitialTranslationCompleted(true);
143 mReady->MaybeResolveWithUndefined();
144 } else {
145 RefPtr<PromiseNativeHandler> l10nReadyHandler =
146 new L10nReadyHandler(mReady, this);
147 promise->AppendNativeHandler(l10nReadyHandler);
149 mState = DocumentL10nState::InitialTranslationTriggered;
153 already_AddRefed<Promise> DocumentL10n::TranslateDocument(ErrorResult& aRv) {
154 MOZ_ASSERT(mState == DocumentL10nState::Constructed,
155 "This method should be called only from Constructed state.");
156 RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
158 Element* elem = mDocument->GetDocumentElement();
159 if (!elem) {
160 promise->MaybeRejectWithUndefined();
161 return promise.forget();
164 // 1. Collect all localizable elements.
165 Sequence<OwningNonNull<Element>> elements;
166 GetTranslatables(*elem, elements, aRv);
167 if (NS_WARN_IF(aRv.Failed())) {
168 promise->MaybeRejectWithUndefined();
169 return promise.forget();
172 RefPtr<nsXULPrototypeDocument> proto = mDocument->GetPrototype();
174 // 2. Check if the document has a prototype that may cache
175 // translated elements.
176 if (proto) {
177 // 2.1. Handle the case when we have proto.
179 // 2.1.1. Move elements that are not in the proto to a separate
180 // array.
181 Sequence<OwningNonNull<Element>> nonProtoElements;
183 uint32_t i = elements.Length();
184 while (i > 0) {
185 Element* elem = elements.ElementAt(i - 1);
186 MOZ_RELEASE_ASSERT(elem->HasAttr(nsGkAtoms::datal10nid));
187 if (!elem->HasElementCreatedFromPrototypeAndHasUnmodifiedL10n()) {
188 if (NS_WARN_IF(!nonProtoElements.AppendElement(*elem, fallible))) {
189 promise->MaybeRejectWithUndefined();
190 return promise.forget();
192 elements.RemoveElement(elem);
194 i--;
197 // We populate the sequence in reverse order. Let's bring it
198 // back to top->bottom one.
199 nonProtoElements.Reverse();
201 AutoAllowLegacyScriptExecution exemption;
203 nsTArray<RefPtr<Promise>> promises;
205 // 2.1.2. If we're not loading from cache, push the elements that
206 // are in the prototype to be translated and cached.
207 if (!proto->WasL10nCached() && !elements.IsEmpty()) {
208 RefPtr<Promise> translatePromise =
209 TranslateElements(elements, proto, aRv);
210 if (NS_WARN_IF(!translatePromise || aRv.Failed())) {
211 promise->MaybeRejectWithUndefined();
212 return promise.forget();
214 promises.AppendElement(translatePromise);
217 // 2.1.3. If there are elements that are not in the prototype,
218 // localize them without attempting to cache and
219 // independently of if we're loading from cache.
220 if (!nonProtoElements.IsEmpty()) {
221 RefPtr<Promise> nonProtoTranslatePromise =
222 TranslateElements(nonProtoElements, nullptr, aRv);
223 if (NS_WARN_IF(!nonProtoTranslatePromise || aRv.Failed())) {
224 promise->MaybeRejectWithUndefined();
225 return promise.forget();
227 promises.AppendElement(nonProtoTranslatePromise);
230 // 2.1.4. Collect promises with Promise::All (maybe empty).
231 AutoEntryScript aes(mGlobal, "DocumentL10n InitialTranslationCompleted");
232 promise = Promise::All(aes.cx(), promises, aRv);
233 } else {
234 // 2.2. Handle the case when we don't have proto.
236 // 2.2.1. Otherwise, translate all available elements,
237 // without attempting to cache them.
238 promise = TranslateElements(elements, nullptr, aRv);
241 if (NS_WARN_IF(!promise || aRv.Failed())) {
242 promise->MaybeRejectWithUndefined();
243 return promise.forget();
246 return promise.forget();
249 void DocumentL10n::MaybeRecordTelemetry() {
250 mozilla::TimeStamp initialTranslationEnd = mozilla::TimeStamp::NowUnfuzzed();
252 nsAutoString documentURI;
253 ErrorResult rv;
254 rv = mDocument->GetDocumentURI(documentURI);
255 if (rv.Failed()) {
256 return;
259 nsCString key;
261 if (documentURI.Find("chrome://browser/content/browser.xhtml") == 0) {
262 if (mIsFirstBrowserWindow) {
263 key = "browser_first_window";
264 mIsFirstBrowserWindow = false;
265 } else {
266 key = "browser_new_window";
268 } else if (documentURI.Find("about:home") == 0) {
269 key = "about:home";
270 } else if (documentURI.Find("about:newtab") == 0) {
271 key = "about:newtab";
272 } else if (documentURI.Find("about:preferences") == 0) {
273 key = "about:preferences";
274 } else {
275 return;
278 mozilla::TimeDuration totalTime(initialTranslationEnd -
279 mInitialTranslationStart);
280 Accumulate(Telemetry::L10N_DOCUMENT_INITIAL_TRANSLATION_TIME_US, key,
281 totalTime.ToMicroseconds());
284 void DocumentL10n::InitialTranslationCompleted(bool aL10nCached) {
285 if (mState >= DocumentL10nState::Ready) {
286 return;
289 Element* documentElement = mDocument->GetDocumentElement();
290 if (documentElement) {
291 SetRootInfo(documentElement);
294 mState = DocumentL10nState::Ready;
296 MaybeRecordTelemetry();
298 mDocument->InitialTranslationCompleted(aL10nCached);
300 // In XUL scenario contentSink is nullptr.
301 if (mContentSink) {
302 mContentSink->InitialTranslationCompleted();
305 // From now on, the state of Localization is unconditionally
306 // async.
307 SetIsSync(false);
310 void DocumentL10n::ConnectRoot(nsINode& aNode, bool aTranslate,
311 ErrorResult& aRv) {
312 if (aTranslate) {
313 if (mState >= DocumentL10nState::InitialTranslationTriggered) {
314 RefPtr<Promise> promise = TranslateFragment(aNode, aRv);
317 DOMLocalization::ConnectRoot(aNode, aRv);
320 Promise* DocumentL10n::Ready() { return mReady; }
322 void DocumentL10n::OnCreatePresShell() { mMutations->OnCreatePresShell(); }