Bug 1546432 - Introduce mozilla::dom::l10n::Mutations. r=smaug
authorZibi Braniecki <zbraniecki@mozilla.com>
Tue, 21 May 2019 19:21:54 +0000
changeset 475234 b2413635c1484c3c1ea426020b074161732f8ac6
parent 475233 1f4aa15478f1464a55ecb07068e4498ccf384225
child 475235 03ee100c9534af5152231d40b83a6154f9585242
push id36058
push useraciure@mozilla.com
push dateFri, 24 May 2019 03:53:25 +0000
treeherdermozilla-central@c87317c41902 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1546432
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1546432 - Introduce mozilla::dom::l10n::Mutations. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D28979
dom/l10n/Mutations.cpp
dom/l10n/Mutations.h
dom/l10n/moz.build
dom/l10n/tests/gtest/TestDOMOverlays.cpp
intl/l10n/DocumentL10n.h
new file mode 100644
--- /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<OwningNonNull<Element>> 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<OwningNonNull<Element>> 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<OwningNonNull<Element>> elements;
+
+  for (auto& elem : mPendingElements) {
+    if (!elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
+      continue;
+    }
+
+    elements.AppendElement(*elem, fallible);
+  }
+
+  mPendingElementsHash.Clear();
+  mPendingElements.Clear();
+
+  RefPtr<Promise> 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
new file mode 100644
--- /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<nsRefreshDriver> mRefreshDriver;
+  DocumentL10n* mDocumentL10n;
+
+  // The hash is used to speed up lookups into mPendingElements.
+  nsTHashtable<nsRefPtrHashKey<Element>> mPendingElementsHash;
+  nsTArray<RefPtr<Element>> 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__
--- a/dom/l10n/moz.build
+++ b/dom/l10n/moz.build
@@ -4,20 +4,22 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "Internationalization")
 
 EXPORTS.mozilla.dom.l10n += [
     'DOMOverlays.h',
+    'Mutations.h',
 ]
 
 UNIFIED_SOURCES += [
     'DOMOverlays.cpp',
+    'Mutations.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
 
 FINAL_LIBRARY = 'xul'
 
--- a/dom/l10n/tests/gtest/TestDOMOverlays.cpp
+++ b/dom/l10n/tests/gtest/TestDOMOverlays.cpp
@@ -11,17 +11,17 @@
 #include "mozilla/dom/L10nUtilsBinding.h"
 #include "mozilla/NullPrincipal.h"
 #include "nsNetUtil.h"
 
 using mozilla::NullPrincipal;
 using namespace mozilla::dom;
 using namespace mozilla::dom::l10n;
 
-already_AddRefed<Document> SetUpDocument() {
+static already_AddRefed<Document> SetUpDocument() {
   nsCOMPtr<nsIURI> uri;
   NS_NewURI(getter_AddRefs(uri), "about:blank");
   nsCOMPtr<nsIPrincipal> principal =
       NullPrincipal::CreateWithoutOriginAttributes();
   nsCOMPtr<Document> document;
   nsresult rv = NS_NewDOMDocument(getter_AddRefs(document),
                                   EmptyString(),  // aNamespaceURI
                                   EmptyString(),  // aQualifiedName
--- a/intl/l10n/DocumentL10n.h
+++ b/intl/l10n/DocumentL10n.h
@@ -129,14 +129,16 @@ class DocumentL10n final : public nsIObs
   void PauseObserving(ErrorResult& aRv);
   void ResumeObserving(ErrorResult& aRv);
 
   Promise* Ready();
 
   void TriggerInitialDocumentTranslation();
 
   void InitialDocumentTranslationCompleted();
+
+  Document* GetDocument() { return mDocument; };
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_DocumentL10n_h