Bug 1518252 - Block layout on Fluent. r=smaug
☠☠ backed out by 9aae2b5ef219 ☠ ☠
authorZibi Braniecki <zbraniecki@mozilla.com>
Wed, 23 Jan 2019 21:29:15 +0000
changeset 512314 b6f80451898e0878e3299e5ea949f9c81dfce770
parent 512313 06114c66901fd20d3c2177d51307ac5045827fcc
child 512315 bec07b59907f2dad66ce976e67cc5adc8bf961fa
push id10566
push userarchaeopteryx@coole-files.de
push dateMon, 28 Jan 2019 12:41:12 +0000
treeherdermozilla-beta@69a3d7c8d04b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1518252
milestone66.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 1518252 - Block layout on Fluent. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D17334
dom/base/Document.cpp
dom/base/Document.h
dom/base/nsContentSink.cpp
dom/xml/nsXMLContentSink.cpp
dom/xml/nsXMLContentSink.h
dom/xul/XULDocument.cpp
dom/xul/XULDocument.h
intl/l10n/DocumentL10n.cpp
intl/l10n/DocumentL10n.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
parser/htmlparser/nsIContentSink.h
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -1334,17 +1334,18 @@ Document::Document(const char* aContentT
       mScrollAnchorAdjustmentCount(0),
       mBoxObjectTable(nullptr),
       mCurrentOrientationAngle(0),
       mCurrentOrientationType(OrientationType::Portrait_primary),
       mServoRestyleRootDirtyBits(0),
       mThrowOnDynamicMarkupInsertionCounter(0),
       mIgnoreOpensDuringUnloadCounter(0),
       mDocLWTheme(Doc_Theme_Uninitialized),
-      mSavedResolution(1.0f) {
+      mSavedResolution(1.0f),
+      mPendingInitialTranslation(false) {
   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
 
   SetIsInDocument();
   SetIsConnected(true);
 
   if (StaticPrefs::layout_css_use_counters_enabled()) {
     mStyleUseCounters.reset(Servo_UseCounters_Create());
   }
@@ -3126,16 +3127,18 @@ void Document::LocalizationLinkAdded(Ele
     InitializeLocalization(resourceIds);
     mDocumentL10n->TriggerInitialDocumentTranslation();
   } else {
     // Otherwise, we're still parsing the document.
     // In that case, add it to the pending list. This list
     // will be resolved once the end of l10n resource
     // container is reached.
     mL10nResources.AppendElement(href);
+
+    mPendingInitialTranslation = true;
   }
 }
 
 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
   if (!PrincipalAllowsL10n(NodePrincipal())) {
     return;
   }
 
@@ -3168,21 +3171,40 @@ void Document::LocalizationLinkRemoved(E
 void Document::OnL10nResourceContainerParsed() {
   if (!mL10nResources.IsEmpty()) {
     InitializeLocalization(mL10nResources);
     mL10nResources.Clear();
   }
 }
 
 void Document::TriggerInitialDocumentTranslation() {
+  // Let's call it again, in case the resource
+  // container has not been closed, and only
+  // now we're closing the document.
+  OnL10nResourceContainerParsed();
+
   if (mDocumentL10n) {
     mDocumentL10n->TriggerInitialDocumentTranslation();
   }
 }
 
+void Document::InitialDocumentTranslationCompleted() {
+  mPendingInitialTranslation = false;
+
+  nsCOMPtr<nsIContentSink> sink;
+  if (mParser) {
+    sink = mParser->GetContentSink();
+  } else {
+    sink = do_QueryReferent(mWeakSink);
+  }
+  if (sink) {
+    sink->InitialDocumentTranslationCompleted();
+  }
+}
+
 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
   MOZ_ASSERT(NS_IsMainThread());
 
   return nsContentUtils::IsSystemCaller(aCx) ||
          nsContentUtils::AnimationsAPICoreEnabled();
 }
 
 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
@@ -8136,17 +8158,16 @@ void Document::SetReadyStateInternal(Rea
   if (READYSTATE_INTERACTIVE == rs) {
     if (nsContentUtils::IsSystemPrincipal(NodePrincipal())) {
       Element* root = GetRootElement();
       if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozpersist)) {
         mXULPersist = new XULPersist(this);
         mXULPersist->Init();
       }
     }
-    TriggerInitialDocumentTranslation();
   }
 
   RecordNavigationTiming(rs);
 
   RefPtr<AsyncEventDispatcher> asyncDispatcher =
       new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"),
                                CanBubble::eNo, ChromeOnlyDispatch::eNo);
   asyncDispatcher->RunDOMEventWhenSafe();
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3420,31 +3420,42 @@ class Document : public nsINode,
   void LocalizationLinkAdded(Element* aLinkElement);
 
   /**
    * This method should be called when a link element
    * with rel="localization" is being removed.
    */
   void LocalizationLinkRemoved(Element* aLinkElement);
 
- protected:
-  /**
-   * This method should be collect as soon as the
+  /**
+   * This method should be called as soon as the
    * parsing of the document is completed.
    *
-   * In HTML this happens when readyState becomes
-   * `interactive`.
+   * In HTML/XHTML this happens when we finish parsing
+   * the document element.
    * In XUL it happens at `DoneWalking`, during
    * `MozBeforeInitialXULLayout`.
    *
    * It triggers the initial translation of the
    * document.
    */
   void TriggerInitialDocumentTranslation();
 
+  /**
+   * This method is called when the initial translation
+   * of the document is completed.
+   *
+   * It unblocks the layout.
+   *
+   * This method is virtual so that XULDocument can
+   * override it.
+   */
+  virtual void InitialDocumentTranslationCompleted();
+
+ protected:
   RefPtr<mozilla::dom::DocumentL10n> mDocumentL10n;
 
  private:
   void InitializeLocalization(nsTArray<nsString>& aResourceIds);
 
   void ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
                                          const nsAString& aHeightString,
                                          const nsAString& aScaleString);
@@ -4508,19 +4519,23 @@ class Document : public nsINode,
 
   // document lightweight theme for use with :-moz-lwtheme,
   // :-moz-lwtheme-brighttext and :-moz-lwtheme-darktext
   DocumentTheme mDocLWTheme;
 
   // Pres shell resolution saved before entering fullscreen mode.
   float mSavedResolution;
 
+  bool mPendingInitialTranslation;
+
  public:
   // Needs to be public because the bindings code pokes at it.
   js::ExpandoAndGeneration mExpandoAndGeneration;
+
+  bool HasPendingInitialTranslation() { return mPendingInitialTranslation; }
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(Document, NS_IDOCUMENT_IID)
 
 /**
  * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
  * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified
  * object is deleted.
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -1165,18 +1165,19 @@ void nsContentSink::StartLayout(bool aIg
 
   if (mLayoutStarted) {
     // Nothing to do here
     return;
   }
 
   mDeferredLayoutStart = true;
 
-  if (!aIgnorePendingSheets && WaitForPendingSheets()) {
-    // Bail out; we'll start layout when the sheets load
+  if (!aIgnorePendingSheets &&
+      (WaitForPendingSheets() || mDocument->HasPendingInitialTranslation())) {
+    // Bail out; we'll start layout when the sheets and l10n load
     return;
   }
 
   mDeferredLayoutStart = false;
 
   // Notify on all our content.  If none of our presshells have started layout
   // yet it'll be a no-op except for updating our data structures, a la
   // UpdateChildCounts() (because we don't want to double-notify on whatever we
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -1068,16 +1068,18 @@ nsresult nsXMLContentSink::HandleEndElem
     mCurrentHead = nullptr;
   }
 
   if (mDocElement == content) {
     // XXXbz for roots that don't want to be appended on open, we
     // probably need to deal here.... (and stop appending them on open).
     mState = eXMLContentSinkState_InEpilog;
 
+    mDocument->TriggerInitialDocumentTranslation();
+
     // We might have had no occasion to start layout yet.  Do so now.
     MaybeStartLayout(false);
   }
 
   DidAddContent();
 
   if (content->IsSVGElement(nsGkAtoms::svg)) {
     FlushTags();
@@ -1402,16 +1404,20 @@ nsresult nsXMLContentSink::AddText(const
     mTextLength += amount;
     offset += amount;
     aLength -= amount;
   }
 
   return NS_OK;
 }
 
+void nsXMLContentSink::InitialDocumentTranslationCompleted() {
+  StartLayout(false);
+}
+
 void nsXMLContentSink::FlushPendingNotifications(FlushType aType) {
   // Only flush tags if we're not doing the notification ourselves
   // (since we aren't reentrant)
   if (!mInNotification) {
     if (mIsDocumentObserver) {
       // Only flush if we're still a document observer (so that our child
       // counts should be correct).
       if (aType >= FlushType::ContentAndNotify) {
--- a/dom/xml/nsXMLContentSink.h
+++ b/dom/xml/nsXMLContentSink.h
@@ -62,16 +62,17 @@ class nsXMLContentSink : public nsConten
 
   // nsIContentSink
   NS_IMETHOD WillParse(void) override;
   NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
   NS_IMETHOD DidBuildModel(bool aTerminated) override;
   NS_IMETHOD WillInterrupt(void) override;
   NS_IMETHOD WillResume(void) override;
   NS_IMETHOD SetParser(nsParserBase* aParser) override;
+  virtual void InitialDocumentTranslationCompleted() override;
   virtual void FlushPendingNotifications(mozilla::FlushType aType) override;
   virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override;
   virtual nsISupports* GetTarget() override;
   virtual bool IsScriptExecuting() override;
   virtual void ContinueInterruptedParsingAsync() override;
 
   // nsITransformObserver
   NS_IMETHOD OnDocumentCreated(
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -444,16 +444,21 @@ void XULDocument::AddElementToDocumentPo
 
   if (aElement->IsXULElement(nsGkAtoms::link)) {
     LocalizationLinkAdded(aElement);
   } else if (aElement->IsXULElement(nsGkAtoms::linkset)) {
     OnL10nResourceContainerParsed();
   }
 }
 
+void XULDocument::InitialDocumentTranslationCompleted() {
+  mPendingInitialTranslation = false;
+  MaybeDoneWalking();
+}
+
 void XULDocument::AddSubtreeToDocument(nsIContent* aContent) {
   MOZ_ASSERT(aContent->GetComposedDoc() == this, "Element not in doc!");
 
   // If the content is not in the document, it must be in a shadow tree.
   //
   // The shadow root itself takes care of maintaining the ID tables and such,
   // and there's no use case for localization links in shadow trees, or at
   // least they don't work in regular HTML documents either as of today so...
@@ -953,53 +958,56 @@ nsresult XULDocument::ResumeWalk() {
 
   // If we get here, there is nothing left for us to walk. The content
   // model is built and ready for layout.
 
   mXULPersist = new XULPersist(this);
   mXULPersist->Init();
 
   mStillWalking = false;
-  if (mPendingSheets == 0) {
-    rv = DoneWalking();
+  return MaybeDoneWalking();
+}
+
+nsresult XULDocument::MaybeDoneWalking() {
+  if (mPendingSheets > 0 || mStillWalking) {
+    return NS_OK;
   }
-  return rv;
+
+  if (mPendingInitialTranslation) {
+    TriggerInitialDocumentTranslation();
+    return NS_OK;
+  }
+
+  return DoneWalking();
 }
 
 nsresult XULDocument::DoneWalking() {
   MOZ_ASSERT(mPendingSheets == 0, "there are sheets to be loaded");
   MOZ_ASSERT(!mStillWalking, "walk not done");
+  MOZ_ASSERT(!mPendingInitialTranslation, "translation pending");
 
   // XXXldb This is where we should really be setting the chromehidden
   // attribute.
 
   if (!mDocumentLoaded) {
     // Make sure we don't reenter here from StartLayout().  Note that
     // setting mDocumentLoaded to true here means that if StartLayout()
     // causes ResumeWalk() to be reentered, we'll take the other branch of
     // the |if (!mDocumentLoaded)| check above and since
     // mInitialLayoutComplete will be false will follow the else branch
     // there too.  See the big comment there for how such reentry can
     // happen.
     mDocumentLoaded = true;
 
     NotifyPossibleTitleChange(false);
 
-    // For performance reasons, we want to trigger the DocumentL10n's
-    // `TriggerInitialDocumentTranslation` within the same microtask that will
-    // be created for a `MozBeforeInitialXULLayout` event listener.
-    AddEventListener(NS_LITERAL_STRING("MozBeforeInitialXULLayout"),
-                     mDocumentL10n, true, false);
-
     nsContentUtils::DispatchTrustedEvent(
         this, ToSupports(this), NS_LITERAL_STRING("MozBeforeInitialXULLayout"),
         CanBubble::eYes, Cancelable::eNo);
 
-    RemoveEventListener(NS_LITERAL_STRING("MozBeforeInitialXULLayout"),
-                        mDocumentL10n, true);
 
     // Before starting layout, check whether we're a toplevel chrome
     // window.  If we are, setup some state so that we don't have to restyle
     // the whole tree after StartLayout.
     if (nsCOMPtr<nsIXULWindow> win = GetXULWindowIfToplevelChrome()) {
       // We're the chrome document!
       win->BeforeStartLayout();
     }
@@ -1034,19 +1042,17 @@ NS_IMETHODIMP
 XULDocument::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
                               nsresult aStatus) {
   if (!aWasDeferred) {
     // Don't care about when alternate sheets finish loading
     MOZ_ASSERT(mPendingSheets > 0, "Unexpected StyleSheetLoaded notification");
 
     --mPendingSheets;
 
-    if (!mStillWalking && mPendingSheets == 0) {
-      return DoneWalking();
-    }
+    return MaybeDoneWalking();
   }
 
   return NS_OK;
 }
 
 void XULDocument::EndUpdate() { XMLDocument::EndUpdate(); }
 
 nsresult XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto,
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -71,16 +71,18 @@ class XULDocument final : public XMLDocu
                                      nsIStreamListener** aDocListener,
                                      bool aReset = true,
                                      nsIContentSink* aSink = nullptr) override;
 
   virtual void SetContentType(const nsAString& aContentType) override;
 
   virtual void EndLoad() override;
 
+  virtual void InitialDocumentTranslationCompleted() override;
+
   // nsIMutationObserver interface
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
 
   /**
    * Notify the XUL document that a subtree has been added
    */
@@ -291,17 +293,25 @@ class XULDocument final : public XMLDocu
 
   /**
    * Resume (or initiate) an interrupted (or newly prepared)
    * prototype walk.
    */
   nsresult ResumeWalk();
 
   /**
-   * Called at the end of ResumeWalk() and from StyleSheetLoaded().
+   * Called at the end of ResumeWalk(), from StyleSheetLoaded(),
+   * and from DocumentL10n.
+   * If walking, stylesheets and l10n are not blocking, it
+   * will trigger `DoneWalking()`.
+   */
+  nsresult MaybeDoneWalking();
+
+  /**
+   * Called from `MaybeDoneWalking()`.
    * Expects that both the prototype document walk is complete and
    * all referenced stylesheets finished loading.
    */
   nsresult DoneWalking();
 
   class CachedChromeStreamListener : public nsIStreamListener {
    protected:
     RefPtr<XULDocument> mDocument;
--- a/intl/l10n/DocumentL10n.cpp
+++ b/intl/l10n/DocumentL10n.cpp
@@ -3,17 +3,16 @@
 /* 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/. */
 
 #include "js/JSON.h"
 #include "mozilla/dom/DocumentL10n.h"
 #include "mozilla/dom/DocumentL10nBinding.h"
 #include "mozilla/dom/Element.h"
-#include "mozilla/dom/Event.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "nsQueryObject.h"
 #include "nsISupports.h"
 #include "nsContentUtils.h"
 #include "xpcprivate.h"
 
 namespace mozilla {
@@ -55,28 +54,22 @@ void PromiseResolver::RejectedCallback(J
   StackScopedClone(cx, options, sourceScope, &value);
 
   mPromise->MaybeReject(cx, value);
   mPromise = nullptr;
 }
 
 PromiseResolver::~PromiseResolver() { mPromise = nullptr; }
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentL10n)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentL10n)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentL10n)
-
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentL10n, mDocument, mDOMLocalization,
                                       mReady)
 
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DocumentL10n, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DocumentL10n, Release)
+
 DocumentL10n::DocumentL10n(Document* aDocument)
     : mDocument(aDocument), mState(DocumentL10nState::Initialized) {}
 
 DocumentL10n::~DocumentL10n() {}
 
 bool DocumentL10n::Init(nsTArray<nsString>& aResourceIds) {
   nsCOMPtr<mozIDOMLocalization> domL10n =
       do_CreateInstance("@mozilla.org/intl/domlocalization;1");
@@ -135,29 +128,16 @@ already_AddRefed<Promise> DocumentL10n::
     return nullptr;
   }
 
   RefPtr<PromiseResolver> resolver = new PromiseResolver(docPromise);
   aInnerPromise->AppendNativeHandler(resolver);
   return docPromise.forget();
 }
 
-NS_IMETHODIMP
-DocumentL10n::HandleEvent(Event* aEvent) {
-#ifdef DEBUG
-  nsAutoString eventType;
-  aEvent->GetType(eventType);
-  MOZ_ASSERT(eventType.EqualsLiteral("MozBeforeInitialXULLayout"));
-#endif
-
-  TriggerInitialDocumentTranslation();
-
-  return NS_OK;
-}
-
 uint32_t DocumentL10n::AddResourceIds(nsTArray<nsString>& aResourceIds) {
   uint32_t ret = 0;
   mDOMLocalization->AddResourceIds(aResourceIds, false, &ret);
   return ret;
 }
 
 uint32_t DocumentL10n::RemoveResourceIds(nsTArray<nsString>& aResourceIds) {
   // We need to guard against a scenario where the
@@ -307,30 +287,34 @@ already_AddRefed<Promise> DocumentL10n::
   return MaybeWrapPromise(promise);
 }
 
 class L10nReadyHandler final : public PromiseNativeHandler {
  public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(L10nReadyHandler)
 
-  explicit L10nReadyHandler(Promise* aPromise) : mPromise(aPromise) {}
+  explicit L10nReadyHandler(Promise* aPromise, Document* aDocument)
+      : mPromise(aPromise), mDocument(aDocument) {}
 
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
+    mDocument->InitialDocumentTranslationCompleted();
     mPromise->MaybeResolveWithUndefined();
   }
 
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
+    mDocument->InitialDocumentTranslationCompleted();
     mPromise->MaybeRejectWithUndefined();
   }
 
  private:
   ~L10nReadyHandler() = default;
 
   RefPtr<Promise> mPromise;
+  RefPtr<Document> mDocument;
 };
 
 NS_IMPL_CYCLE_COLLECTION(L10nReadyHandler, mPromise)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(L10nReadyHandler)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
@@ -346,16 +330,17 @@ void DocumentL10n::TriggerInitialDocumen
 
   Element* elem = mDocument->GetDocumentElement();
   if (elem) {
     mDOMLocalization->ConnectRoot(elem);
   }
 
   RefPtr<Promise> promise;
   mDOMLocalization->TranslateRoots(getter_AddRefs(promise));
-  RefPtr<PromiseNativeHandler> l10nReadyHandler = new L10nReadyHandler(mReady);
+  RefPtr<PromiseNativeHandler> l10nReadyHandler =
+      new L10nReadyHandler(mReady, mDocument);
   promise->AppendNativeHandler(l10nReadyHandler);
 }
 
 Promise* DocumentL10n::Ready() { return mReady; }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/intl/l10n/DocumentL10n.h
+++ b/intl/l10n/DocumentL10n.h
@@ -6,17 +6,16 @@
 
 #ifndef mozilla_dom_DocumentL10n_h
 #define mozilla_dom_DocumentL10n_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
-#include "nsIDOMEventListener.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "mozilla/dom/Document.h"
 #include "nsINode.h"
 #include "mozIDOMLocalization.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 
@@ -48,21 +47,20 @@ enum class DocumentL10nState { Initializ
  *
  * The document will initialize it lazily when a link with a localization
  * resource is added to the document.
  *
  * Once initialized, DocumentL10n relays all API methods to an
  * instance of mozIDOMLocalization and maintains a single promise
  * which gets resolved the first time the document gets translated.
  */
-class DocumentL10n final : public nsIDOMEventListener, public nsWrapperCache {
+class DocumentL10n final : public nsWrapperCache {
  public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DocumentL10n)
-  NS_DECL_NSIDOMEVENTLISTENER
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DocumentL10n)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DocumentL10n)
 
  public:
   explicit DocumentL10n(Document* aDocument);
   bool Init(nsTArray<nsString>& aResourceIds);
 
  protected:
   virtual ~DocumentL10n();
 
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -165,16 +165,17 @@ nsHtml5TreeOpExecutor::DidBuildModel(boo
     // likely cause us to crash, or at best waste a lot of time as we
     // are just going to tear it down anyway.
     bool destroying = true;
     if (mDocShell) {
       mDocShell->IsBeingDestroyed(&destroying);
     }
 
     if (!destroying) {
+      mDocument->TriggerInitialDocumentTranslation();
       nsContentSink::StartLayout(false);
     }
   }
 
   ScrollToRef();
   mDocument->RemoveObserver(this);
   if (!mParser) {
     // DidBuildModelImpl may cause mParser to be nulled out
@@ -223,16 +224,20 @@ nsHtml5TreeOpExecutor::WillResume() {
 }
 
 NS_IMETHODIMP
 nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) {
   mParser = aParser;
   return NS_OK;
 }
 
+void nsHtml5TreeOpExecutor::InitialDocumentTranslationCompleted() {
+  nsContentSink::StartLayout(false);
+}
+
 void nsHtml5TreeOpExecutor::FlushPendingNotifications(FlushType aType) {
   if (aType >= FlushType::EnsurePresShellInitAndFrames) {
     // Bug 577508 / 253951
     nsContentSink::StartLayout(true);
   }
 }
 
 nsISupports* nsHtml5TreeOpExecutor::GetTarget() {
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -131,16 +131,18 @@ class nsHtml5TreeOpExecutor final
    */
   NS_IMETHOD WillInterrupt() override;
 
   /**
    * Unimplemented. For interface compat only.
    */
   NS_IMETHOD WillResume() override;
 
+  virtual void InitialDocumentTranslationCompleted() override;
+
   /**
    * Sets the parser.
    */
   NS_IMETHOD SetParser(nsParserBase* aParser) override;
 
   /**
    * No-op for backwards compat.
    */
--- a/parser/htmlparser/nsIContentSink.h
+++ b/parser/htmlparser/nsIContentSink.h
@@ -125,13 +125,15 @@ class nsIContentSink : public nsISupport
    * parsing for.
    */
   virtual bool IsScriptExecuting() { return false; }
 
   /**
    * Posts a runnable that continues parsing.
    */
   virtual void ContinueInterruptedParsingAsync() {}
+
+  virtual void InitialDocumentTranslationCompleted() {}
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIContentSink, NS_ICONTENT_SINK_IID)
 
 #endif /* nsIContentSink_h___ */