/* 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/. */

/*
 * Base class for all element classes as well as nsDocumentFragment.  This
 * provides an implementation of nsINode, implements nsIContent, provides
 * utility methods for subclasses, and so forth.
 */

#ifndef FragmentOrElement_h_
#define FragmentOrElement_h_

#include "mozilla/Attributes.h"
#include "mozilla/EnumSet.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/RadioGroupContainer.h"
#include "nsAtomHashKeys.h"
#include "nsCycleCollectionParticipant.h"  // NS_DECL_CYCLE_*
#include "nsIContent.h"                    // base class
#include "nsIWeakReferenceUtils.h"
#include "nsTHashSet.h"

class ContentUnbinder;
class nsDOMAttributeMap;
class nsDOMTokenList;
class nsIControllers;
class nsDOMCSSAttributeDeclaration;
class nsDOMCSSDeclaration;
class nsDOMStringMap;
class nsIURI;

namespace mozilla {
struct StyleLockedDeclarationBlock;
enum class ContentRelevancyReason;
using ContentRelevancy = EnumSet<ContentRelevancyReason, uint8_t>;
class ElementAnimationData;
namespace dom {
class ContentList;
class Element;
class HTMLCollection;
class LabelsNodeList;
class PopoverData;
class StylePropertyMap;
class StylePropertyMapReadOnly;
struct CustomElementData;
}  // namespace dom
}  // namespace mozilla

/**
 * Tearoff to use for nodes to implement nsISupportsWeakReference
 */
class nsNodeSupportsWeakRefTearoff final : public nsISupportsWeakReference {
 public:
  explicit nsNodeSupportsWeakRefTearoff(nsINode* aNode) : mNode(aNode) {}

  // nsISupports
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL

  // nsISupportsWeakReference
  NS_DECL_NSISUPPORTSWEAKREFERENCE

  NS_DECL_CYCLE_COLLECTION_CLASS(nsNodeSupportsWeakRefTearoff)

 private:
  ~nsNodeSupportsWeakRefTearoff() = default;

  nsCOMPtr<nsINode> mNode;
};

/**
 * A generic base class for DOM elements and document fragments,
 * implementing many nsIContent, nsINode and Element methods.
 */
namespace mozilla::dom {

class DOMIntersectionObserver;
class ShadowRoot;

class FragmentOrElement : public nsIContent {
 public:
  explicit FragmentOrElement(
      already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
  explicit FragmentOrElement(
      already_AddRefed<mozilla::dom::NodeInfo> aNodeInfo);

  // We want to avoid the overhead of extra function calls for
  // refcounting when we're not doing refcount logging, so we can't
  // NS_DECL_ISUPPORTS_INHERITED.
  NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
  NS_INLINE_DECL_REFCOUNTING_INHERITED(FragmentOrElement, nsIContent);

  NS_DECL_ADDSIZEOFEXCLUDINGTHIS

  // nsINode interface methods
  virtual void GetTextContentInternal(nsAString& aTextContent,
                                      mozilla::OOMReporter& aError) override;
  virtual void SetTextContentInternal(
      const nsAString& aTextContent, nsIPrincipal* aSubjectPrincipal,
      mozilla::ErrorResult& aError,
      MutationEffectOnScript aMutationEffectOnScript) override;

  // nsIContent interface methods
  const CharacterDataBuffer* GetCharacterDataBuffer() const override;
  uint32_t TextLength() const override;
  bool TextIsOnlyWhitespace() override;
  bool ThreadSafeTextIsOnlyWhitespace() const override;

  void DestroyContent() override;
  void SaveSubtreeState() override;

  HTMLCollection* Children();
  uint32_t ChildElementCount();

  RadioGroupContainer& OwnedRadioGroupContainer() {
    auto* slots = ExtendedDOMSlots();
    if (!slots->mRadioGroupContainer) {
      slots->mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
    }
    return *slots->mRadioGroupContainer;
  }

 public:
  NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS_INHERITED(
      FragmentOrElement, nsIContent)

  static void ClearContentUnbinder();
  static bool CanSkip(nsINode* aNode, bool aRemovingAllowed);
  static bool CanSkipInCC(nsINode* aNode);
  static bool CanSkipThis(nsINode* aNode);
  static void RemoveBlackMarkedNode(nsINode* aNode);
  static void MarkNodeChildren(nsINode* aNode);
  static void InitCCCallbacks();

  /**
   * Is the HTML local name a void element?
   */
  static bool IsHTMLVoid(const nsAtom* aLocalName);

 protected:
  virtual ~FragmentOrElement();

  /**
   * Dummy CopyInnerTo so that we can use the same macros for
   * Elements and DocumentFragments.
   */
  nsresult CopyInnerTo(FragmentOrElement* aDest) { return NS_OK; }

 public:
  /**
   * There are a set of DOM- and scripting-specific instance variables
   * that may only be instantiated when a content object is accessed
   * through the DOM. Rather than burn actual slots in the content
   * objects for each of these instance variables, we put them off
   * in a side structure that's only allocated when the content is
   * accessed through the DOM.
   */

  class nsExtendedDOMSlots : public nsIContent::nsExtendedContentSlots {
   public:
    nsExtendedDOMSlots();
    ~nsExtendedDOMSlots();

    void TraverseExtendedSlots(nsCycleCollectionTraversalCallback&) final;
    void UnlinkExtendedSlots(nsIContent&) final;

    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const final;

    /**
     * SMIL Overridde style rules (for SMIL animation of CSS properties)
     * @see Element::GetSMILOverrideStyle
     */
    RefPtr<nsDOMCSSAttributeDeclaration> mSMILOverrideStyle;

    /**
     * Holds any SMIL override style declaration for this element.
     */
    RefPtr<mozilla::StyleLockedDeclarationBlock> mSMILOverrideStyleDeclaration;

    /**
     * The controllers of the XUL Element.
     */
    nsCOMPtr<nsIControllers> mControllers;

    /**
     * An object implementing the .labels property for this element.
     */
    RefPtr<mozilla::dom::LabelsNodeList> mLabelsList;

    /**
     * ShadowRoot bound to the element.
     */
    RefPtr<ShadowRoot> mShadowRoot;

    /**
     * Web components custom element data.
     */
    UniquePtr<CustomElementData> mCustomElementData;

    /**
     * Web animations data.
     */
    UniquePtr<ElementAnimationData> mAnimations;

    /**
     * PopoverData for the element.
     */
    UniquePtr<PopoverData> mPopoverData;

    /**
     * The association to a popover that this element was the source
     * showing a popover, e.g. the source in `el.showPopover({source})`.
     */
    nsWeakPtr mAssociatedPopover;

    /**
     * CustomStates for the element.
     */
    nsTArray<RefPtr<nsAtom>> mCustomStates;

    /**
     * RadioGroupContainer for radio buttons grouped under this disconnected
     * element.
     */
    UniquePtr<RadioGroupContainer> mRadioGroupContainer;

    /**
     * Last remembered size (in CSS pixels) for the element.
     * @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered}
     */
    Maybe<float> mLastRememberedBSize;
    Maybe<float> mLastRememberedISize;

    /**
     * Whether the content of this element is relevant for the purposes
     * of `content-visibility: auto.
     * Reflects 'relevant to the user' concept, see
     * https://drafts.csswg.org/css-contain/#relevant-to-the-user.
     */
    Maybe<ContentRelevancy> mContentRelevancy;

    /**
     * Whether the content of this element is considered visible for
     * the purposes of `content-visibility: auto.
     * Reflects 'proximity to the viewport' concept, see
     * https://drafts.csswg.org/css-contain/#proximity-to-the-viewport.
     */
    Maybe<bool> mVisibleForContentVisibility;

    /**
     * Whether content-visibility: auto is temporarily visible for
     * the purposes of the descendant of scrollIntoView.
     */
    bool mTemporarilyVisibleForScrolledIntoViewDescendant = false;

    /**
     * The .dataset attribute.
     * @see nsGenericHTMLElement::GetDataset
     */
    nsDOMStringMap* MOZ_UNSAFE_REF("ClearDataSet clears it") mDataset = nullptr;

    /** An object implementing the .part property for this element. */
    RefPtr<nsDOMTokenList> mPart;

    /**
     * Explicitly set attr-element, see
     * https://html.spec.whatwg.org/#explicitly-set-attr-element
     */
    nsTHashMap<RefPtr<nsAtom>, nsWeakPtr> mExplicitlySetAttrElementMap;
    /**
     * Explicitly set attr-elements, see
     * https://html.spec.whatwg.org/#explicitly-set-attr-elements
     *
     * The first member of the pair are the explicitly set attr-elements. The
     * second member is the cached attr-associated elements.
     */

    nsTHashMap<RefPtr<nsAtom>, std::pair<Maybe<nsTArray<nsWeakPtr>>,
                                         Maybe<nsTArray<RefPtr<Element>>>>>
        mAttrElementsMap;

    typedef bool (*AttrTargetObserver)(Element* aOldElement,
                                       Element* aNewelement,
                                       Element* aThisElement);
    struct AttrElementObserverCallbackData {
      nsWeakPtr mElement;
      RefPtr<nsAtom> mAttr;
    };
    struct AttrElementObserverData {
      // Used as the value for |aOldElement| when calling an AttrTargetObserver
      // callback.
      nsWeakPtr mLastKnownAttrElement;  // TODO: should be an array

      // Used to add/remove ID target observers when the attribute value changes
      // or the attribute host is added to or removed from a document or shadow
      // root.
      RefPtr<nsAtom> mLastKnownAttrValue;  // TODO: should be a ParsedAttr
      nsTHashSet<AttrTargetObserver> mObservers;

      // Used for removing the IDTargetObserver(s)
      UniquePtr<AttrElementObserverCallbackData> mCallbackData;
    };
    nsTHashMap<RefPtr<nsAtom>, AttrElementObserverData> mAttrElementObserverMap;

    /**
     * Callback called when an element's resolved reference target changes.
     * @param aData The callback data which was stored using
     * AddReferenceTargetChangeObserver.
     * @return true to keep the callback in the callback set, false to remove
     * it.
     */
    typedef bool (*ReferenceTargetChangeObserver)(void* aData);

    struct ReferenceTargetChangeCallback {
      ReferenceTargetChangeObserver mObserver;
      void* mData;
    };

    struct ReferenceTargetChangeCallbackEntry : public PLDHashEntryHdr {
      typedef const ReferenceTargetChangeCallback KeyType;
      typedef const ReferenceTargetChangeCallback* KeyTypePointer;

      explicit ReferenceTargetChangeCallbackEntry(
          const ReferenceTargetChangeCallback* aKey)
          : mKey(*aKey) {}
      ReferenceTargetChangeCallbackEntry(
          ReferenceTargetChangeCallbackEntry&& aOther)
          : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {}

      KeyType GetKey() const { return mKey; }
      bool KeyEquals(KeyTypePointer aKey) const {
        return aKey->mObserver == mKey.mObserver && aKey->mData == mKey.mData;
      }

      static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
      static PLDHashNumber HashKey(KeyTypePointer aKey) {
        return HashGeneric(aKey->mObserver, aKey->mData);
      }
      enum { ALLOW_MEMMOVE = true };

      ReferenceTargetChangeCallback mKey;
    };
    nsTHashSet<ReferenceTargetChangeCallbackEntry> mReferenceTargetObservers;
  };

  class nsDOMSlots : public nsIContent::nsContentSlots {
   public:
    nsDOMSlots();
    ~nsDOMSlots();

    void Traverse(nsCycleCollectionTraversalCallback&) final;
    void Unlink(nsINode&) final;

    size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;

    /**
     * The .style attribute (an interface that forwards to the actual
     * style rules)
     * @see nsGenericHTMLElement::GetStyle
     */
    nsCOMPtr<nsDOMCSSDeclaration> mStyle;

    /**
     * @see Element::Attributes
     */
    RefPtr<nsDOMAttributeMap> mAttributeMap;

    /**
     * An object implementing the .children property for this element.
     */
    RefPtr<ContentList> mChildrenList;

    /**
     * An object implementing the .classList property for this element.
     */
    RefPtr<nsDOMTokenList> mClassList;

    /**
     * An object implementing the .computedStyleMap() method for this element.
     */
    RefPtr<StylePropertyMapReadOnly> mComputedStyleMap;

    /**
     * An object implementing the .attributeStyleMap property for this element.
     */
    RefPtr<StylePropertyMap> mAttributeStyleMap;
  };

  /**
   * In case ExtendedDOMSlots is needed before normal DOMSlots, an instance of
   * FatSlots class, which combines those two slot types, is created.
   * This way we can avoid extra allocation for ExtendedDOMSlots.
   * FatSlots is useful for example when creating Custom Elements.
   */
  class FatSlots final : public nsDOMSlots, public nsExtendedDOMSlots {
   public:
    FatSlots() : nsDOMSlots(), nsExtendedDOMSlots() {
      MOZ_COUNT_CTOR(FatSlots);
      SetExtendedContentSlots(this, false);
    }

    ~FatSlots() final { MOZ_COUNT_DTOR(FatSlots); }
  };

 protected:
  void GetMarkup(bool aIncludeSelf, nsAString& aMarkup);
  void SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError);

  // Override from nsINode
  nsIContent::nsContentSlots* CreateSlots() override;

  nsIContent::nsExtendedContentSlots* CreateExtendedSlots() final;

  nsDOMSlots* DOMSlots() { return static_cast<nsDOMSlots*>(Slots()); }

  nsDOMSlots* GetExistingDOMSlots() const {
    return static_cast<nsDOMSlots*>(GetExistingSlots());
  }

  nsExtendedDOMSlots* ExtendedDOMSlots();

  const nsExtendedDOMSlots* GetExistingExtendedDOMSlots() const {
    return static_cast<const nsExtendedDOMSlots*>(
        GetExistingExtendedContentSlots());
  }

  nsExtendedDOMSlots* GetExistingExtendedDOMSlots() {
    return static_cast<nsExtendedDOMSlots*>(GetExistingExtendedContentSlots());
  }

  friend class ::ContentUnbinder;
};

}  // namespace mozilla::dom

#define NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE               \
  if (NS_SUCCEEDED(rv)) return rv;                            \
                                                              \
  rv = FragmentOrElement::QueryInterface(aIID, aInstancePtr); \
  NS_INTERFACE_TABLE_TO_MAP_SEGUE

#endif /* FragmentOrElement_h_ */
