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 "L10nMutations.h"
8 #include "mozilla/dom/DocumentInlines.h"
9 #include "nsRefreshDriver.h"
10
11 using namespace mozilla::dom;
12
13 NS_IMPL_CYCLE_COLLECTION_CLASS(L10nMutations)
14
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(L10nMutations)15 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(L10nMutations)
16 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingElements)
17 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingElementsHash)
18 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
19
20 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(L10nMutations)
21 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingElements)
22 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingElementsHash)
23 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
24
25 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nMutations)
26 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
27 NS_INTERFACE_MAP_ENTRY(nsISupports)
28 NS_INTERFACE_MAP_END
29
30 NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nMutations)
31 NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nMutations)
32
33 L10nMutations::L10nMutations(DOMLocalization* aDOMLocalization)
34 : mDOMLocalization(aDOMLocalization) {
35 mObserving = true;
36 }
37
~L10nMutations()38 L10nMutations::~L10nMutations() {
39 StopRefreshObserver();
40 MOZ_ASSERT(!mDOMLocalization,
41 "DOMLocalization<-->L10nMutations cycle should be broken.");
42 }
43
AttributeChanged(Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)44 void L10nMutations::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
45 nsAtom* aAttribute, int32_t aModType,
46 const nsAttrValue* aOldValue) {
47 if (!mObserving) {
48 return;
49 }
50
51 if (aNameSpaceID == kNameSpaceID_None &&
52 (aAttribute == nsGkAtoms::datal10nid ||
53 aAttribute == nsGkAtoms::datal10nargs)) {
54 if (IsInRoots(aElement)) {
55 L10nElementChanged(aElement);
56 }
57 }
58 }
59
ContentAppended(nsIContent * aChild)60 void L10nMutations::ContentAppended(nsIContent* aChild) {
61 if (!mObserving) {
62 return;
63 }
64
65 nsINode* node = aChild;
66 if (!IsInRoots(node)) {
67 return;
68 }
69
70 ErrorResult rv;
71 Sequence<OwningNonNull<Element>> elements;
72 while (node) {
73 if (node->IsElement()) {
74 DOMLocalization::GetTranslatables(*node, elements, rv);
75 }
76
77 node = node->GetNextSibling();
78 }
79
80 for (auto& elem : elements) {
81 L10nElementChanged(elem);
82 }
83 }
84
ContentInserted(nsIContent * aChild)85 void L10nMutations::ContentInserted(nsIContent* aChild) {
86 if (!mObserving) {
87 return;
88 }
89 ErrorResult rv;
90 Sequence<OwningNonNull<Element>> elements;
91
92 if (!aChild->IsElement()) {
93 return;
94 }
95 Element* elem = aChild->AsElement();
96
97 if (!IsInRoots(elem)) {
98 return;
99 }
100 DOMLocalization::GetTranslatables(*aChild, elements, rv);
101
102 for (auto& elem : elements) {
103 L10nElementChanged(elem);
104 }
105 }
106
L10nElementChanged(Element * aElement)107 void L10nMutations::L10nElementChanged(Element* aElement) {
108 if (!mPendingElementsHash.Contains(aElement)) {
109 mPendingElements.AppendElement(aElement);
110 mPendingElementsHash.Insert(aElement);
111 }
112
113 if (!mRefreshObserver) {
114 StartRefreshObserver();
115 }
116 }
117
PauseObserving()118 void L10nMutations::PauseObserving() { mObserving = false; }
119
ResumeObserving()120 void L10nMutations::ResumeObserving() { mObserving = true; }
121
WillRefresh(mozilla::TimeStamp aTime)122 void L10nMutations::WillRefresh(mozilla::TimeStamp aTime) {
123 StopRefreshObserver();
124 FlushPendingTranslations();
125 }
126
FlushPendingTranslations()127 void L10nMutations::FlushPendingTranslations() {
128 if (!mDOMLocalization) {
129 return;
130 }
131
132 ErrorResult rv;
133
134 Sequence<OwningNonNull<Element>> elements;
135
136 for (auto& elem : mPendingElements) {
137 if (!elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
138 continue;
139 }
140
141 if (!elements.AppendElement(*elem, fallible)) {
142 mozalloc_handle_oom(0);
143 }
144 }
145
146 mPendingElementsHash.Clear();
147 mPendingElements.Clear();
148
149 RefPtr<Promise> promise = mDOMLocalization->TranslateElements(elements, rv);
150 }
151
Disconnect()152 void L10nMutations::Disconnect() {
153 StopRefreshObserver();
154 mDOMLocalization = nullptr;
155 }
156
StartRefreshObserver()157 void L10nMutations::StartRefreshObserver() {
158 if (!mDOMLocalization || mRefreshObserver) {
159 return;
160 }
161
162 if (!mRefreshDriver) {
163 nsPIDOMWindowInner* innerWindow =
164 mDOMLocalization->GetParentObject()->AsInnerWindow();
165 Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
166 if (doc) {
167 nsPresContext* ctx = doc->GetPresContext();
168 if (ctx) {
169 mRefreshDriver = ctx->RefreshDriver();
170 }
171 }
172 }
173
174 // If we can't start the refresh driver, it means
175 // that the presContext is not available yet.
176 // In that case, we'll trigger the flush of pending
177 // elements in Document::CreatePresShell.
178 if (mRefreshDriver) {
179 mRefreshDriver->AddRefreshObserver(this, FlushType::Style,
180 "L10n mutations");
181 mRefreshObserver = true;
182 } else {
183 NS_WARNING("[l10n][mutations] Failed to start a refresh observer.");
184 }
185 }
186
StopRefreshObserver()187 void L10nMutations::StopRefreshObserver() {
188 if (!mDOMLocalization) {
189 return;
190 }
191
192 if (mRefreshDriver) {
193 mRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
194 mRefreshObserver = false;
195 }
196 }
197
OnCreatePresShell()198 void L10nMutations::OnCreatePresShell() {
199 if (!mPendingElements.IsEmpty()) {
200 StartRefreshObserver();
201 }
202 }
203
IsInRoots(nsINode * aNode)204 bool L10nMutations::IsInRoots(nsINode* aNode) {
205 // If the root of the mutated element is in the light DOM,
206 // we know it must be covered by our observer directly.
207 //
208 // Otherwise, we need to check if its subtree root is the same
209 // as any of the `DOMLocalization::mRoots` subtree roots.
210 nsINode* root = aNode->SubtreeRoot();
211
212 // If element is in light DOM, it must be covered by one of
213 // the DOMLocalization roots to end up here.
214 MOZ_ASSERT_IF(!root->IsShadowRoot(),
215 mDOMLocalization->SubtreeRootInRoots(root));
216
217 return !root->IsShadowRoot() || mDOMLocalization->SubtreeRootInRoots(root);
218 }
219