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/. */
7 #include "L10nMutations.h"
8 #include "mozilla/dom/DocumentInlines.h"
9 #include "nsRefreshDriver.h"
11 using namespace mozilla::dom
;
13 NS_IMPL_CYCLE_COLLECTION_CLASS(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
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
25 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nMutations
)
26 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver
)
27 NS_INTERFACE_MAP_ENTRY(nsISupports
)
30 NS_IMPL_CYCLE_COLLECTING_ADDREF(L10nMutations
)
31 NS_IMPL_CYCLE_COLLECTING_RELEASE(L10nMutations
)
33 L10nMutations::L10nMutations(DOMLocalization
* aDOMLocalization
)
34 : mDOMLocalization(aDOMLocalization
) {
38 L10nMutations::~L10nMutations() {
39 StopRefreshObserver();
40 MOZ_ASSERT(!mDOMLocalization
,
41 "DOMLocalization<-->L10nMutations cycle should be broken.");
44 void L10nMutations::AttributeChanged(Element
* aElement
, int32_t aNameSpaceID
,
45 nsAtom
* aAttribute
, int32_t aModType
,
46 const nsAttrValue
* aOldValue
) {
51 if (aNameSpaceID
== kNameSpaceID_None
&&
52 (aAttribute
== nsGkAtoms::datal10nid
||
53 aAttribute
== nsGkAtoms::datal10nargs
)) {
54 if (IsInRoots(aElement
)) {
55 L10nElementChanged(aElement
);
60 void L10nMutations::ContentAppended(nsIContent
* aChild
) {
65 nsINode
* node
= aChild
;
66 if (!IsInRoots(node
)) {
71 Sequence
<OwningNonNull
<Element
>> elements
;
73 if (node
->IsElement()) {
74 DOMLocalization::GetTranslatables(*node
, elements
, rv
);
77 node
= node
->GetNextSibling();
80 for (auto& elem
: elements
) {
81 L10nElementChanged(elem
);
85 void L10nMutations::ContentInserted(nsIContent
* aChild
) {
90 Sequence
<OwningNonNull
<Element
>> elements
;
92 if (!aChild
->IsElement()) {
95 Element
* elem
= aChild
->AsElement();
97 if (!IsInRoots(elem
)) {
100 DOMLocalization::GetTranslatables(*aChild
, elements
, rv
);
102 for (auto& elem
: elements
) {
103 L10nElementChanged(elem
);
107 void L10nMutations::L10nElementChanged(Element
* aElement
) {
108 if (!mPendingElementsHash
.Contains(aElement
)) {
109 mPendingElements
.AppendElement(aElement
);
110 mPendingElementsHash
.Insert(aElement
);
113 if (!mRefreshObserver
) {
114 StartRefreshObserver();
118 void L10nMutations::PauseObserving() { mObserving
= false; }
120 void L10nMutations::ResumeObserving() { mObserving
= true; }
122 void L10nMutations::WillRefresh(mozilla::TimeStamp aTime
) {
123 StopRefreshObserver();
124 FlushPendingTranslations();
127 void L10nMutations::FlushPendingTranslations() {
128 if (!mDOMLocalization
) {
134 Sequence
<OwningNonNull
<Element
>> elements
;
136 for (auto& elem
: mPendingElements
) {
137 if (!elem
->HasAttr(kNameSpaceID_None
, nsGkAtoms::datal10nid
)) {
141 if (!elements
.AppendElement(*elem
, fallible
)) {
142 mozalloc_handle_oom(0);
146 mPendingElementsHash
.Clear();
147 mPendingElements
.Clear();
149 RefPtr
<Promise
> promise
= mDOMLocalization
->TranslateElements(elements
, rv
);
152 void L10nMutations::Disconnect() {
153 StopRefreshObserver();
154 mDOMLocalization
= nullptr;
157 void L10nMutations::StartRefreshObserver() {
158 if (!mDOMLocalization
|| mRefreshObserver
) {
162 if (!mRefreshDriver
) {
163 nsPIDOMWindowInner
* innerWindow
=
164 mDOMLocalization
->GetParentObject()->AsInnerWindow();
165 Document
* doc
= innerWindow
? innerWindow
->GetExtantDoc() : nullptr;
167 nsPresContext
* ctx
= doc
->GetPresContext();
169 mRefreshDriver
= ctx
->RefreshDriver();
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
,
181 mRefreshObserver
= true;
183 NS_WARNING("[l10n][mutations] Failed to start a refresh observer.");
187 void L10nMutations::StopRefreshObserver() {
188 if (!mDOMLocalization
) {
192 if (mRefreshDriver
) {
193 mRefreshDriver
->RemoveRefreshObserver(this, FlushType::Style
);
194 mRefreshObserver
= false;
198 void L10nMutations::OnCreatePresShell() {
199 if (!mPendingElements
.IsEmpty()) {
200 StartRefreshObserver();
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.
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();
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
));
217 return !root
->IsShadowRoot() || mDOMLocalization
->SubtreeRootInRoots(root
);