From fbc6e0304a87bc20ec61810f158b8c3bba21251e Mon Sep 17 00:00:00 2001 From: Zibi Braniecki Date: Tue, 21 May 2019 19:21:54 +0000 Subject: [PATCH] Bug 1546432 - Introduce mozilla::dom::l10n::Mutations. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D28979 --HG-- extra : moz-landing-system : lando --- dom/l10n/Mutations.cpp | 178 +++++++++++++++++++++++ dom/l10n/Mutations.h | 78 ++++++++++ dom/l10n/moz.build | 2 + dom/l10n/tests/gtest/TestDOMOverlays.cpp | 2 +- intl/l10n/DocumentL10n.h | 2 + 5 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 dom/l10n/Mutations.cpp create mode 100644 dom/l10n/Mutations.h diff --git a/dom/l10n/Mutations.cpp b/dom/l10n/Mutations.cpp new file mode 100644 index 000000000000..495fd674ad89 --- /dev/null +++ b/dom/l10n/Mutations.cpp @@ -0,0 +1,178 @@ +#include "Mutations.h" +#include "mozilla/dom/DocumentInlines.h" + +namespace mozilla { +namespace dom { +namespace l10n { + +NS_IMPL_CYCLE_COLLECTION_CLASS(Mutations) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Mutations) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingElements) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingElementsHash) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Mutations) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingElements) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingElementsHash) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Mutations) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Mutations) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Mutations) + +Mutations::Mutations(DocumentL10n* aDocumentL10n) + : mDocumentL10n(aDocumentL10n) { + mObserving = true; +} + +void Mutations::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + if (!mObserving) { + return; + } + Document* uncomposedDoc = aElement->GetUncomposedDoc(); + if (uncomposedDoc) { + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::datal10nid || + aAttribute == nsGkAtoms::datal10nargs)) { + L10nElementChanged(aElement); + } + } +} + +void Mutations::ContentAppended(nsIContent* aChild) { + if (!mObserving) { + return; + } + ErrorResult rv; + Sequence> elements; + + nsINode* node = aChild; + while (node) { + if (node->IsElement()) { + Element* elem = node->AsElement(); + + Document* uncomposedDoc = elem->GetUncomposedDoc(); + if (uncomposedDoc) { + mDocumentL10n->GetTranslatables(*node, elements, rv); + } + } + + node = node->GetNextSibling(); + } + + for (auto& elem : elements) { + L10nElementChanged(elem); + } +} + +void Mutations::ContentInserted(nsIContent* aChild) { + if (!mObserving) { + return; + } + ErrorResult rv; + Sequence> elements; + + if (!aChild->IsElement()) { + return; + } + Element* elem = aChild->AsElement(); + + Document* uncomposedDoc = elem->GetUncomposedDoc(); + if (!uncomposedDoc) { + return; + } + mDocumentL10n->GetTranslatables(*aChild, elements, rv); + + for (auto& elem : elements) { + L10nElementChanged(elem); + } +} + +void Mutations::L10nElementChanged(Element* aElement) { + if (!mPendingElementsHash.Contains(aElement)) { + mPendingElements.AppendElement(aElement); + mPendingElementsHash.PutEntry(aElement); + } + + if (!mRefreshObserver) { + StartRefreshObserver(); + } +} + +void Mutations::PauseObserving() { mObserving = false; } + +void Mutations::ResumeObserving() { mObserving = true; } + +void Mutations::WillRefresh(mozilla::TimeStamp aTime) { + StopRefreshObserver(); + FlushPendingTranslations(); +} + +void Mutations::FlushPendingTranslations() { + if (!mDocumentL10n) { + return; + } + + ErrorResult rv; + + Sequence> elements; + + for (auto& elem : mPendingElements) { + if (!elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) { + continue; + } + + elements.AppendElement(*elem, fallible); + } + + mPendingElementsHash.Clear(); + mPendingElements.Clear(); + + RefPtr promise = mDocumentL10n->TranslateElements(elements, rv); +} + +void Mutations::Disconnect() { + StopRefreshObserver(); + mDocumentL10n = nullptr; +} + +void Mutations::StartRefreshObserver() { + if (!mDocumentL10n) { + return; + } + + if (!mRefreshDriver) { + nsPresContext* ctx = mDocumentL10n->GetDocument()->GetPresContext(); + if (!ctx) { + return; + } + mRefreshDriver = ctx->RefreshDriver(); + } + + if (mRefreshDriver) { + mRefreshDriver->AddRefreshObserver(this, FlushType::Style); + mRefreshObserver = true; + } +} + +void Mutations::StopRefreshObserver() { + if (!mDocumentL10n) { + return; + } + + if (mRefreshDriver) { + mRefreshDriver->RemoveRefreshObserver(this, FlushType::Style); + mRefreshObserver = false; + } +} + +} // namespace l10n +} // namespace dom +} // namespace mozilla diff --git a/dom/l10n/Mutations.h b/dom/l10n/Mutations.h new file mode 100644 index 000000000000..c4c80dab77e2 --- /dev/null +++ b/dom/l10n/Mutations.h @@ -0,0 +1,78 @@ +#ifndef mozilla_dom_l10n_Mutations_h__ +#define mozilla_dom_l10n_Mutations_h__ + +#include "nsRefreshDriver.h" +#include "nsStubMutationObserver.h" +#include "nsTHashtable.h" +#include "mozilla/dom/DocumentL10n.h" + +namespace mozilla { +namespace dom { +namespace l10n { + +/** + * Mutations manage observing roots for localization + * changes and coalescing pending translations into + * batches - one per animation frame. + */ +class Mutations final : public nsStubMutationObserver, + public nsARefreshObserver { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(Mutations, nsIMutationObserver) + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + + explicit Mutations(DocumentL10n* aDocumentL10n); + + /** + * Pause root observation. + * This is useful for injecting already-translated + * content into an observed root, without causing + * superflues translation. + */ + void PauseObserving(); + + /** + * Resume root observation. + */ + void ResumeObserving(); + + /** + * Disconnect roots, stop refresh observer + * and break the cycle collection deadlock + * by removing the reference to mDocumentL10n. + */ + void Disconnect(); + + protected: + bool mObserving = false; + bool mRefreshObserver = false; + RefPtr mRefreshDriver; + DocumentL10n* mDocumentL10n; + + // The hash is used to speed up lookups into mPendingElements. + nsTHashtable> mPendingElementsHash; + nsTArray> mPendingElements; + + virtual void WillRefresh(mozilla::TimeStamp aTime) override; + + void StartRefreshObserver(); + void StopRefreshObserver(); + void L10nElementChanged(Element* aElement); + void FlushPendingTranslations(); + + private: + ~Mutations() { + StopRefreshObserver(); + MOZ_ASSERT(!mDocumentL10n, + "DocumentL10n<-->Mutations cycle should be broken."); + } +}; + +} // namespace l10n +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_l10n_Mutations_h__ diff --git a/dom/l10n/moz.build b/dom/l10n/moz.build index d917134e17f6..33a4d24a7df7 100644 --- a/dom/l10n/moz.build +++ b/dom/l10n/moz.build @@ -9,10 +9,12 @@ with Files("**"): EXPORTS.mozilla.dom.l10n += [ 'DOMOverlays.h', + 'Mutations.h', ] UNIFIED_SOURCES += [ 'DOMOverlays.cpp', + 'Mutations.cpp', ] LOCAL_INCLUDES += [ diff --git a/dom/l10n/tests/gtest/TestDOMOverlays.cpp b/dom/l10n/tests/gtest/TestDOMOverlays.cpp index a2ffa68307ca..5a43c4bb3a07 100644 --- a/dom/l10n/tests/gtest/TestDOMOverlays.cpp +++ b/dom/l10n/tests/gtest/TestDOMOverlays.cpp @@ -16,7 +16,7 @@ using mozilla::NullPrincipal; using namespace mozilla::dom; using namespace mozilla::dom::l10n; -already_AddRefed SetUpDocument() { +static already_AddRefed SetUpDocument() { nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), "about:blank"); nsCOMPtr principal = diff --git a/intl/l10n/DocumentL10n.h b/intl/l10n/DocumentL10n.h index 37139a53e3bf..001f5bc31b41 100644 --- a/intl/l10n/DocumentL10n.h +++ b/intl/l10n/DocumentL10n.h @@ -134,6 +134,8 @@ class DocumentL10n final : public nsIObserver, void TriggerInitialDocumentTranslation(); void InitialDocumentTranslationCompleted(); + + Document* GetDocument() { return mDocument; }; }; } // namespace dom