Bug 1517880 - Plug DOMLocalization and DocumentL10n into XULProtypeCache. r=smaug
☠☠ backed out by 7efa66d98015 ☠ ☠
authorZibi Braniecki <zbraniecki@mozilla.com>
Fri, 26 Jul 2019 16:11:49 +0000
changeset 545453 39c3063994bfa5c3208f65051e85be8fa3b46893
parent 545452 45bf070f451bbd50933688d75743bf42fa2814b2
child 545454 7efa66d980152f09b30322a75d7a2e8b5671385b
push id11848
push userffxbld-merge
push dateMon, 26 Aug 2019 19:26:25 +0000
treeherdermozilla-beta@9b31bfdfac10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1517880
milestone70.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 1517880 - Plug DOMLocalization and DocumentL10n into XULProtypeCache. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D38972
dom/l10n/DOMLocalization.cpp
dom/l10n/DOMLocalization.h
dom/l10n/DocumentL10n.cpp
--- a/dom/l10n/DOMLocalization.cpp
+++ b/dom/l10n/DOMLocalization.cpp
@@ -169,19 +169,19 @@ already_AddRefed<Promise> DOMLocalizatio
 
 /**
  * A Promise Handler used to apply the result of
  * a call to Localization::FormatMessages onto the list
  * of translatable elements.
  */
 class ElementTranslationHandler : public PromiseNativeHandler {
  public:
-  explicit ElementTranslationHandler(DOMLocalization* aDOMLocalization) {
-    mDOMLocalization = aDOMLocalization;
-  };
+  explicit ElementTranslationHandler(DOMLocalization* aDOMLocalization,
+                                     nsXULPrototypeDocument* aProto)
+      : mDOMLocalization(aDOMLocalization), mProto(aProto){};
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
 
   nsTArray<nsCOMPtr<Element>>& Elements() { return mElements; }
 
   void SetReturnValuePromise(Promise* aReturnValuePromise) {
     mReturnValuePromise = aReturnValuePromise;
@@ -223,17 +223,17 @@ class ElementTranslationHandler : public
 
         if (!slotPtr->Init(aCx, temp)) {
           mReturnValuePromise->MaybeRejectWithUndefined();
           return;
         }
       }
     }
 
-    mDOMLocalization->ApplyTranslations(mElements, l10nData, rv);
+    mDOMLocalization->ApplyTranslations(mElements, l10nData, mProto, rv);
     if (NS_WARN_IF(rv.Failed())) {
       mReturnValuePromise->MaybeRejectWithUndefined();
       return;
     }
 
     mReturnValuePromise->MaybeResolveWithUndefined();
   }
 
@@ -243,46 +243,55 @@ class ElementTranslationHandler : public
   }
 
  private:
   ~ElementTranslationHandler() = default;
 
   nsTArray<nsCOMPtr<Element>> mElements;
   RefPtr<DOMLocalization> mDOMLocalization;
   RefPtr<Promise> mReturnValuePromise;
+  RefPtr<nsXULPrototypeDocument> mProto;
 };
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementTranslationHandler)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(ElementTranslationHandler)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementTranslationHandler)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementTranslationHandler)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementTranslationHandler)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMLocalization)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValuePromise)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mProto)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementTranslationHandler)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMLocalization)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValuePromise)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProto)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 already_AddRefed<Promise> DOMLocalization::TranslateElements(
     const Sequence<OwningNonNull<Element>>& aElements, ErrorResult& aRv) {
+  return TranslateElements(aElements, nullptr, aRv);
+}
+
+already_AddRefed<Promise> DOMLocalization::TranslateElements(
+    const Sequence<OwningNonNull<Element>>& aElements,
+    nsXULPrototypeDocument* aProto, ErrorResult& aRv) {
   JS::RootingContext* rcx = RootingCx();
   Sequence<L10nKey> l10nKeys;
   SequenceRooter<L10nKey> rooter(rcx, &l10nKeys);
   RefPtr<ElementTranslationHandler> nativeHandler =
-      new ElementTranslationHandler(this);
+      new ElementTranslationHandler(this, aProto);
   nsTArray<nsCOMPtr<Element>>& domElements = nativeHandler->Elements();
   domElements.SetCapacity(aElements.Length());
 
   if (!mGlobal) {
     return nullptr;
   }
 
   AutoEntryScript aes(mGlobal, "DOMLocalization TranslateElements");
@@ -343,17 +352,17 @@ already_AddRefed<Promise> DOMLocalizatio
       }
 
       if (!slotPtr->Init(cx, rootedMsg)) {
         promise->MaybeRejectWithUndefined();
         return MaybeWrapPromise(promise);
       }
     }
 
-    ApplyTranslations(domElements, l10nData, aRv);
+    ApplyTranslations(domElements, l10nData, aProto, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       promise->MaybeRejectWithUndefined();
       return MaybeWrapPromise(promise);
     }
 
     promise->MaybeResolveWithUndefined();
   } else {
     RefPtr<Promise> callbackResult = FormatMessages(cx, l10nKeys, aRv);
@@ -469,16 +478,17 @@ void DOMLocalization::SetRootInfo(Elemen
   nsAtom* dirAtom =
       nameSpace == kNameSpaceID_XUL ? nsGkAtoms::localedir : nsGkAtoms::dir;
 
   aElement->SetAttr(kNameSpaceID_None, dirAtom, dir, true);
 }
 
 void DOMLocalization::ApplyTranslations(nsTArray<nsCOMPtr<Element>>& aElements,
                                         nsTArray<L10nMessage>& aTranslations,
+                                        nsXULPrototypeDocument* aProto,
                                         ErrorResult& aRv) {
   if (aElements.Length() != aTranslations.Length()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   PauseObserving(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
@@ -489,16 +499,21 @@ void DOMLocalization::ApplyTranslations(
   nsTArray<L10nOverlaysError> errors;
   for (size_t i = 0; i < aTranslations.Length(); ++i) {
     Element* elem = aElements[i];
     L10nOverlays::TranslateElement(*elem, aTranslations[i], errors, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       aRv.Throw(NS_ERROR_FAILURE);
       return;
     }
+    if (aProto) {
+      // We only need to rebuild deep if the translation has a value.
+      // Otherwise we'll only rebuild the attributes.
+      aProto->RebuildL10nPrototype(elem, !aTranslations[i].mValue.IsVoid());
+    }
   }
 
   ResumeObserving(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
--- a/dom/l10n/DOMLocalization.h
+++ b/dom/l10n/DOMLocalization.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #ifndef mozilla_dom_l10n_DOMLocalization_h
 #define mozilla_dom_l10n_DOMLocalization_h
 
+#include "nsXULPrototypeDocument.h"
 #include "mozilla/intl/Localization.h"
 #include "mozilla/dom/DOMLocalizationBinding.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/L10nMutations.h"
 #include "mozilla/dom/L10nOverlaysBinding.h"
 #include "mozilla/dom/LocalizationBinding.h"
 
 namespace mozilla {
@@ -50,16 +51,19 @@ class DOMLocalization : public intl::Loc
                      ErrorResult& aRv);
   void GetAttributes(JSContext* aCx, Element& aElement, L10nKey& aResult,
                      ErrorResult& aRv);
 
   already_AddRefed<Promise> TranslateFragment(nsINode& aNode, ErrorResult& aRv);
 
   already_AddRefed<Promise> TranslateElements(
       const Sequence<OwningNonNull<Element>>& aElements, ErrorResult& aRv);
+  already_AddRefed<Promise> TranslateElements(
+      const Sequence<OwningNonNull<Element>>& aElements,
+      nsXULPrototypeDocument* aProto, ErrorResult& aRv);
 
   already_AddRefed<Promise> TranslateRoots(ErrorResult& aRv);
 
   /**
    * Helper methods
    */
 
   /**
@@ -73,20 +77,23 @@ class DOMLocalization : public intl::Loc
 
   /**
    * Sets the root information such as locale and direction.
    */
   static void SetRootInfo(Element* aElement);
 
   /**
    * Applies l10n translations on translatable elements.
+   *
+   * If `aProto` gets passed, it'll be used to cache
+   * the localized elements.
    */
   void ApplyTranslations(nsTArray<nsCOMPtr<Element>>& aElements,
                          nsTArray<L10nMessage>& aTranslations,
-                         ErrorResult& aRv);
+                         nsXULPrototypeDocument* aProto, ErrorResult& aRv);
 
  protected:
   virtual ~DOMLocalization();
   void OnChange();
   void DisconnectMutations();
   void DisconnectRoots();
   void ReportL10nOverlaysErrors(nsTArray<L10nOverlaysError>& aErrors);
   void ConvertStringToL10nArgs(JSContext* aCx, const nsString& aInput,
--- a/dom/l10n/DocumentL10n.cpp
+++ b/dom/l10n/DocumentL10n.cpp
@@ -95,34 +95,129 @@ void DocumentL10n::TriggerInitialDocumen
   if (mState >= DocumentL10nState::InitialTranslationTriggered) {
     return;
   }
 
   mState = DocumentL10nState::InitialTranslationTriggered;
 
   Element* elem = mDocument->GetDocumentElement();
   if (!elem) {
+    mReady->MaybeRejectWithUndefined();
+    InitialDocumentTranslationCompleted();
+    return;
+  }
+
+  ErrorResult rv;
+
+  // 1. Collect all localizable elements.
+  Sequence<OwningNonNull<Element>> elements;
+  GetTranslatables(*elem, elements, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    mReady->MaybeRejectWithUndefined();
+    InitialDocumentTranslationCompleted();
     return;
   }
 
-  Sequence<OwningNonNull<Element>> elements;
-  ErrorResult rv;
+  RefPtr<nsXULPrototypeDocument> proto = mDocument->GetPrototype();
+
+  // 2. Check if the document has a prototype that may cache
+  //    translated elements.
+  if (proto) {
+    // 2.1. Handle the case when we have proto.
+
+    // 2.1.1. Move elements that are not in the proto to a separate
+    //        array.
+    Sequence<OwningNonNull<Element>> nonProtoElements;
 
-  GetTranslatables(*elem, elements, rv);
+    uint32_t i = elements.Length();
+    while (i > 0) {
+      Element* elem = elements.ElementAt(i - 1);
+      MOZ_RELEASE_ASSERT(elem->HasAttr(nsGkAtoms::datal10nid));
+      if (!elem->HasElementCreatedFromPrototypeAndHasUnmodifiedL10n()) {
+        nonProtoElements.AppendElement(*elem, fallible);
+        elements.RemoveElement(elem);
+      }
+      i--;
+    }
+
+    // We populate the sequence in reverse order. Let's bring it
+    // back to top->bottom one.
+    nonProtoElements->Reverse();
+
+    nsTArray<RefPtr<Promise>> promises;
+
+    // 2.1.2. If we're not loading from cache, push the elements that
+    //        are in the prototype to be translated and cached.
+    if (!proto->WasL10nCached() && !elements.IsEmpty()) {
+      RefPtr<Promise> translatePromise = TranslateElements(elements, proto, rv);
+      if (NS_WARN_IF(!translatePromise || rv.Failed())) {
+        mReady->MaybeRejectWithUndefined();
+        InitialDocumentTranslationCompleted();
+        return;
+      }
+      promises.AppendElement(translatePromise);
+    }
 
-  ConnectRoot(*elem, rv);
+    // 2.1.3. If there are elements that are not in the prototype,
+    //        localize them without attempting to cache and
+    //        independently of if we're loading from cache.
+    if (!nonProtoElements.IsEmpty()) {
+      RefPtr<Promise> nonProtoTranslatePromise =
+          TranslateElements(nonProtoElements, nullptr, rv);
+      if (NS_WARN_IF(!nonProtoTranslatePromise || rv.Failed())) {
+        mReady->MaybeRejectWithUndefined();
+        InitialDocumentTranslationCompleted();
+        return;
+      }
+      promises.AppendElement(nonProtoTranslatePromise);
+    }
 
-  RefPtr<Promise> promise = TranslateElements(elements, rv);
-  if (!promise) {
-    return;
+    // 2.1.4. Check if anything has to be translated.
+    if (promises.IsEmpty()) {
+      // 2.1.4.1. If not, resolve the mReady and complete
+      //          initial translation.
+      mReady->MaybeResolveWithUndefined();
+      InitialDocumentTranslationCompleted();
+    } else {
+      // 2.1.4.2. If we have any TranslateElements promises,
+      //          collect them with Promise::All and schedule
+      //          the L10nReadyHandler.
+      AutoEntryScript aes(mGlobal,
+                          "DocumentL10n InitialDocumentTranslationCompleted");
+      RefPtr<Promise> promise = Promise::All(aes.cx(), promises, rv);
+      if (NS_WARN_IF(!promise || rv.Failed())) {
+        mReady->MaybeRejectWithUndefined();
+        InitialDocumentTranslationCompleted();
+        return;
+      }
+
+      RefPtr<PromiseNativeHandler> l10nReadyHandler =
+          new L10nReadyHandler(mReady, this);
+      promise->AppendNativeHandler(l10nReadyHandler);
+    }
+  } else {
+    // 2.2. Handle the case when we don't have proto.
+
+    // 2.2.1. Otherwise, translate all available elements,
+    //        without attempting to cache them and schedule
+    //        the L10nReadyHandler.
+    RefPtr<Promise> promise = TranslateElements(elements, nullptr, rv);
+    if (NS_WARN_IF(!promise || rv.Failed())) {
+      mReady->MaybeRejectWithUndefined();
+      InitialDocumentTranslationCompleted();
+      return;
+    }
+
+    RefPtr<PromiseNativeHandler> l10nReadyHandler =
+        new L10nReadyHandler(mReady, this);
+    promise->AppendNativeHandler(l10nReadyHandler);
   }
 
-  RefPtr<PromiseNativeHandler> l10nReadyHandler =
-      new L10nReadyHandler(mReady, this);
-  promise->AppendNativeHandler(l10nReadyHandler);
+  // 3. Connect the root to L10nMutations observer.
+  ConnectRoot(*elem, rv);
 }
 
 void DocumentL10n::InitialDocumentTranslationCompleted() {
   if (mState >= DocumentL10nState::InitialTranslationCompleted) {
     return;
   }
 
   Element* documentElement = mDocument->GetDocumentElement();