/* 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 https://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_PrototypeDocumentContentSink_h_
#define mozilla_dom_PrototypeDocumentContentSink_h_

#include "js/experimental/JSStencil.h"
#include "mozilla/Logging.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/FromParser.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsCycleCollectionNoteChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsICSSLoaderObserver.h"
#include "nsIContentSink.h"
#include "nsIScriptContext.h"
#include "nsIStreamLoader.h"
#include "nsTArray.h"
#include "nsXULPrototypeDocument.h"

class nsIURI;
class nsIChannel;
class nsIContent;
class nsIParser;
class nsTextNode;
class nsINode;
class nsXULPrototypeElement;
class nsXULPrototypePI;
class nsXULPrototypeScript;

namespace mozilla::dom {
class Element;
class ScriptLoader;
class Document;
class XMLStylesheetProcessingInstruction;
}  // namespace mozilla::dom

nsresult NS_NewPrototypeDocumentContentSink(nsIContentSink** aResult,
                                            mozilla::dom::Document* aDoc,
                                            nsIURI* aURI,
                                            nsISupports* aContainer,
                                            nsIChannel* aChannel);

namespace mozilla::dom {

class PrototypeDocumentContentSink final : public nsIStreamLoaderObserver,
                                           public nsIContentSink,
                                           public nsICSSLoaderObserver,
                                           public nsIOffThreadScriptReceiver {
 public:
  PrototypeDocumentContentSink();

  nsresult Init(Document* aDoc, nsIURI* aURL, nsISupports* aContainer,
                nsIChannel* aChannel);

  // nsISupports
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
  NS_DECL_NSISTREAMLOADEROBSERVER

  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PrototypeDocumentContentSink,
                                           nsIContentSink)

  // nsIContentSink
  NS_IMETHOD WillParse(void) override { return NS_OK; };
  NS_IMETHOD WillInterrupt(void) override { return NS_OK; };
  void WillResume() override {};
  NS_IMETHOD SetParser(nsParserBase* aParser) override;
  virtual void InitialTranslationCompleted() override;
  virtual void FlushPendingNotifications(FlushType aType) override {};
  virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override;
  virtual nsISupports* GetTarget() override;
  virtual bool IsScriptExecuting() override;
  virtual void ContinueInterruptedParsingAsync() override;

  // nsICSSLoaderObserver
  NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
                              nsresult aStatus) override;

  // nsIOffThreadScriptReceiver
  NS_IMETHOD OnScriptCompileComplete(JS::Stencil* aStencil,
                                     nsresult aStatus) override;

  nsresult OnPrototypeLoadDone(nsXULPrototypeDocument* aPrototype);

 protected:
  virtual ~PrototypeDocumentContentSink();

  static LazyLogModule gLog;

  nsIParser* GetParser();

  void ContinueInterruptedParsingIfEnabled();
  void StartLayout();

  virtual nsresult AddAttributes(nsXULPrototypeElement* aPrototype,
                                 Element* aElement);

  RefPtr<nsParserBase> mParser;
  nsCOMPtr<nsIURI> mDocumentURI;
  RefPtr<Document> mDocument;
  RefPtr<ScriptLoader> mScriptLoader;

  PrototypeDocumentContentSink* mNextSrcLoadWaiter;  // [OWNER] but not COMPtr

  /**
   * The prototype-script of the current transcluded script that is being
   * loaded.  For document.write('<script src="nestedwrite.js"><\/script>')
   * to work, these need to be in a stack element type, and we need to hold
   * the top of stack here.
   */
  nsXULPrototypeScript* mCurrentScriptProto;

  /**
   * Whether the current transcluded script is being compiled off thread.
   * The load event is blocked while this is in progress.
   */
  bool mOffThreadCompiling;

  /**
   * Wether the prototype document is still be traversed to create the DOM.
   * Layout will not be started until false.
   */
  bool mStillWalking;

  /**
   * Number of style sheets still loading. Layout will not start until zero.
   */
  uint32_t mPendingSheets;

  /**
   * Context stack, which maintains the state of the Builder and allows
   * it to be interrupted.
   */
  class ContextStack {
   protected:
    struct Entry {
      nsXULPrototypeElement* mPrototype;
      nsIContent* mElement;
      int32_t mIndex;
      Entry* mNext;
    };

    Entry* mTop;
    int32_t mDepth;

   public:
    ContextStack();
    ~ContextStack();

    int32_t Depth() { return mDepth; }

    nsresult Push(nsXULPrototypeElement* aPrototype, nsIContent* aElement);
    nsresult Pop();
    nsresult Peek(nsXULPrototypeElement** aPrototype, nsIContent** aElement,
                  int32_t* aIndex);

    nsresult SetTopIndex(int32_t aIndex);

    void Traverse(nsCycleCollectionTraversalCallback& aCallback,
                  const char* aName, uint32_t aFlags = 0);
    void Clear();

    // Cycle collector helpers for ContextStack.
    friend void ImplCycleCollectionUnlink(
        PrototypeDocumentContentSink::ContextStack& aField) {
      aField.Clear();
    }

    friend void ImplCycleCollectionTraverse(
        nsCycleCollectionTraversalCallback& aCallback,
        PrototypeDocumentContentSink::ContextStack& aField, const char* aName,
        uint32_t aFlags = 0) {
      aField.Traverse(aCallback, aName, aFlags);
    }
  };

  friend class ContextStack;
  ContextStack mContextStack;

  /**
   * The current prototype that we are walking to construct the
   * content model.
   */
  RefPtr<nsXULPrototypeDocument> mCurrentPrototype;
  nsresult CreateAndInsertPI(const nsXULPrototypePI* aProtoPI);
  nsresult ExecuteScript(nsXULPrototypeScript* aScript);
  nsresult LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock);

  /**
   * A wrapper around ResumeWalkInternal to report walking errors.
   */
  nsresult ResumeWalk();

  /**
   * Resume (or initiate) an interrupted (or newly prepared)
   * prototype walk.
   */
  nsresult ResumeWalkInternal();

  /**
   * 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();

  /**
   * Create a delegate content model element from a prototype.
   * Note that the resulting content node is not bound to any tree
   */
  nsresult CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
                                      Element** aResult, nsIContent* aParent);
  /**
   * Prepare to walk the current prototype.
   */
  nsresult PrepareToWalk();
  /**
   * Creates a processing instruction based on aProtoPI and inserts
   * it to the DOM.
   */
  nsresult CreateAndInsertPI(const nsXULPrototypePI* aProtoPI, nsINode* aParent,
                             bool aInProlog);

  /**
   * Inserts the passed <?xml-stylesheet ?> PI at the specified
   * index. Loads and applies the associated stylesheet
   * asynchronously.
   * The prototype document walk can happen before the stylesheets
   * are loaded, but the final steps in the load process (see
   * DoneWalking()) are not run before all the stylesheets are done
   * loading.
   */
  nsresult InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
                                 nsINode* aParent,
                                 XMLStylesheetProcessingInstruction* aPINode);
  void CloseElement(Element* aElement);
};

}  // namespace mozilla::dom

#endif  // mozilla_dom_PrototypeDocumentContentSink_h_
