Blob Blame History Raw
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 nsXBLPrototypeBinding_h__
#define nsXBLPrototypeBinding_h__

#include "nsAutoPtr.h"
#include "nsClassHashtable.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsICSSLoaderObserver.h"
#include "nsInterfaceHashtable.h"
#include "nsWeakReference.h"
#include "nsXBLDocumentInfo.h"
#include "nsXBLProtoImpl.h"
#include "nsXBLProtoImplMethod.h"
#include "nsXBLPrototypeHandler.h"
#include "nsXBLPrototypeResources.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/StyleSheet.h"

class nsAtom;
class nsIContent;
class nsIDocument;
class nsXBLAttributeEntry;
class nsXBLBinding;
class nsXBLProtoImplField;

// *********************************************************************/
// The XBLPrototypeBinding class

// Instances of this class are owned by the nsXBLDocumentInfo object returned
// by XBLDocumentInfo().  Consumers who want to refcount things should refcount
// that.
class nsXBLPrototypeBinding final
    : public mozilla::SupportsWeakPtr<nsXBLPrototypeBinding> {
 public:
  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsXBLPrototypeBinding)

  mozilla::dom::Element* GetBindingElement() const { return mBinding; }
  void SetBindingElement(mozilla::dom::Element* aElement);

  nsIURI* BindingURI() const { return mBindingURI; }
  nsIURI* AlternateBindingURI() const { return mAlternateBindingURI; }
  nsIURI* DocURI() const { return mXBLDocInfoWeak->DocumentURI(); }
  nsIURI* GetBaseBindingURI() const { return mBaseBindingURI; }

  // Checks if aURI refers to this binding by comparing to both possible
  // binding URIs.
  bool CompareBindingURI(nsIURI* aURI) const;

  bool GetAllowScripts() const;

  nsresult BindingAttached(nsIContent* aBoundElement);
  nsresult BindingDetached(nsIContent* aBoundElement);

  // aBoundElement is passed in here because we need to get owner document
  // and PresContext in nsXBLResourceLoader::LoadResources().
  bool LoadResources(nsIContent* aBoundElement);
  nsresult AddResource(nsAtom* aResourceType, const nsAString& aSrc);

  bool InheritsStyle() const { return mInheritStyle; }
  void SetInheritsStyle(bool aInheritStyle) { mInheritStyle = aInheritStyle; }

  nsXBLPrototypeHandler* GetPrototypeHandlers() { return mPrototypeHandler; }
  void SetPrototypeHandlers(nsXBLPrototypeHandler* aHandler) {
    mPrototypeHandler = aHandler;
  }

  nsXBLProtoImplAnonymousMethod* GetConstructor();
  nsresult SetConstructor(nsXBLProtoImplAnonymousMethod* aConstructor);
  nsXBLProtoImplAnonymousMethod* GetDestructor();
  nsresult SetDestructor(nsXBLProtoImplAnonymousMethod* aDestructor);

  nsXBLProtoImplField* FindField(const nsString& aFieldName) const {
    return mImplementation ? mImplementation->FindField(aFieldName) : nullptr;
  }

  // Resolve all the fields for this binding on the object |obj|.
  // False return means a JS exception was set.
  bool ResolveAllFields(JSContext* cx, JS::Handle<JSObject*> obj) const {
    return !mImplementation || mImplementation->ResolveAllFields(cx, obj);
  }

  // Undefine all our fields from object |obj| (which should be a
  // JSObject for a bound element).
  void UndefineFields(JSContext* cx, JS::Handle<JSObject*> obj) const {
    if (mImplementation) {
      mImplementation->UndefineFields(cx, obj);
    }
  }

  const nsString& ClassName() const {
    return mImplementation ? mImplementation->mClassName : EmptyString();
  }

  nsresult InitClass(const nsString& aClassName, JSContext* aContext,
                     JS::Handle<JSObject*> aScriptObject,
                     JS::MutableHandle<JSObject*> aClassObject, bool* aNew);

  nsresult ConstructInterfaceTable(const nsAString& aImpls);

  void SetImplementation(nsXBLProtoImpl* aImpl) { mImplementation = aImpl; }
  nsXBLProtoImpl* GetImplementation() { return mImplementation; }
  nsresult InstallImplementation(nsXBLBinding* aBinding);
  bool HasImplementation() const { return mImplementation != nullptr; }

  void AttributeChanged(nsAtom* aAttribute, int32_t aNameSpaceID,
                        bool aRemoveFlag,
                        mozilla::dom::Element* aChangedElement,
                        nsIContent* aAnonymousContent, bool aNotify);

  void SetBasePrototype(nsXBLPrototypeBinding* aBinding);
  nsXBLPrototypeBinding* GetBasePrototype() { return mBaseBinding; }

  nsXBLDocumentInfo* XBLDocumentInfo() const { return mXBLDocInfoWeak; }
  bool IsChrome() { return mXBLDocInfoWeak->IsChrome(); }

  void SetInitialAttributes(mozilla::dom::Element* aBoundElement,
                            nsIContent* aAnonymousContent);

  void AppendStyleSheet(mozilla::StyleSheet* aSheet);
  void RemoveStyleSheet(mozilla::StyleSheet* aSheet);
  void InsertStyleSheetAt(size_t aIndex, mozilla::StyleSheet* aSheet);
  mozilla::StyleSheet* StyleSheetAt(size_t aIndex) const;
  size_t SheetCount() const;
  bool HasStyleSheets() const;
  void AppendStyleSheetsTo(nsTArray<mozilla::StyleSheet*>& aResult) const;

#ifdef MOZ_OLD_STYLE
  nsIStyleRuleProcessor* GetRuleProcessor();
#endif

  const RawServoAuthorStyles* GetServoStyles() const {
    return mResources ? mResources->GetServoStyles() : nullptr;
  }

  void SyncServoStyles() {
    MOZ_ASSERT(mResources);
    mResources->SyncServoStyles();
  }

  RawServoAuthorStyles* GetServoStyles() {
    return mResources
               ? const_cast<RawServoAuthorStyles*>(mResources->GetServoStyles())
               : nullptr;
  }

  mozilla::ServoStyleRuleMap* GetServoStyleRuleMap() {
    return mResources ? mResources->GetServoStyleRuleMap() : nullptr;
  }

  nsresult FlushSkinSheets();

  nsAtom* GetBaseTag(int32_t* aNamespaceID);
  void SetBaseTag(int32_t aNamespaceID, nsAtom* aTag);

  bool ImplementsInterface(REFNSIID aIID) const;

  nsresult AddResourceListener(nsIContent* aBoundElement);

  void Initialize();

  nsresult ResolveBaseBinding();

  const nsCOMArray<nsXBLKeyEventHandler>* GetKeyEventHandlers() {
    if (!mKeyHandlersRegistered) {
      CreateKeyHandlers();
      mKeyHandlersRegistered = true;
    }

    return &mKeyHandlers;
  }

 private:
  nsresult Read(nsIObjectInputStream* aStream, nsXBLDocumentInfo* aDocInfo,
                nsIDocument* aDocument, uint8_t aFlags);

  /**
   * Read a new binding from the stream aStream into the xbl document aDocument.
   * aDocInfo should be the xbl document info for the binding document.
   * aFlags can contain XBLBinding_Serialize_InheritStyle to indicate that
   * mInheritStyle flag should be set, and XBLBinding_Serialize_IsFirstBinding
   * to indicate the first binding in a document.
   * XBLBinding_Serialize_ChromeOnlyContent indicates that
   * nsXBLPrototypeBinding::mChromeOnlyContent should be true.
   * XBLBinding_Serialize_BindToUntrustedContent indicates that
   * nsXBLPrototypeBinding::mBindToUntrustedContent should be true.
   */
 public:
  static nsresult ReadNewBinding(nsIObjectInputStream* aStream,
                                 nsXBLDocumentInfo* aDocInfo,
                                 nsIDocument* aDocument, uint8_t aFlags);

  /**
   * Write this binding to the stream.
   */
  nsresult Write(nsIObjectOutputStream* aStream);

  /**
   * Read a content node from aStream and return it in aChild.
   * aDocument and aNim are the document and node info manager for the document
   * the child will be inserted into.
   */
  nsresult ReadContentNode(nsIObjectInputStream* aStream,
                           nsIDocument* aDocument, nsNodeInfoManager* aNim,
                           nsIContent** aChild);

  /**
   * Write the content node aNode to aStream.
   *
   * This method is called recursively for each child descendant. For the
   * topmost call, aNode must be an element.
   *
   * Text, CDATA and comment nodes are serialized as:
   *   the constant XBLBinding_Serialize_TextNode,
   * XBLBinding_Serialize_CDATANode or XBLBinding_Serialize_CommentNode the text
   * for the node Elements are serialized in the following format: node's
   * namespace, written with WriteNamespace node's namespace prefix node's tag
   *   32-bit attribute count
   *   table of attributes:
   *     attribute's namespace, written with WriteNamespace
   *     attribute's namespace prefix
   *     attribute's tag
   *     attribute's value
   *   attribute forwarding table:
   *     source namespace
   *     source attribute
   *     destination namespace
   *     destination attribute
   *   the constant XBLBinding_Serialize_NoMoreAttributes
   *   32-bit count of the number of child nodes
   *     each child node is serialized in the same manner in sequence
   *   the constant XBLBinding_Serialize_NoContent
   */
  nsresult WriteContentNode(nsIObjectOutputStream* aStream, nsIContent* aNode);

  /**
   * Read or write a namespace id from or to aStream. If the namespace matches
   * one of the built-in ones defined in nsNameSpaceManager.h, it will be
   * written as a single byte with that value. Otherwise,
   * XBLBinding_Serialize_CustomNamespace is written out, followed by a string
   * written with writeWStringZ.
   */
  nsresult ReadNamespace(nsIObjectInputStream* aStream, int32_t& aNameSpaceID);
  nsresult WriteNamespace(nsIObjectOutputStream* aStream, int32_t aNameSpaceID);

 public:
  nsXBLPrototypeBinding();
  ~nsXBLPrototypeBinding();

  // Init must be called after construction to initialize the prototype
  // binding.  It may well throw errors (eg on out-of-memory).  Do not confuse
  // this with the Initialize() method, which must be called after the
  // binding's handlers, properties, etc are all set.
  nsresult Init(const nsACString& aRef, nsXBLDocumentInfo* aInfo,
                mozilla::dom::Element* aElement, bool aFirstBinding = false);

  void Traverse(nsCycleCollectionTraversalCallback& cb) const;
  void Unlink();
  void Trace(const TraceCallbacks& aCallbacks, void* aClosure) const;

  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;

  // Internal member functions.
 public:
  /**
   * GetImmediateChild locates the immediate child of our binding element which
   * has the localname given by aTag and is in the XBL namespace.
   */
  mozilla::dom::Element* GetImmediateChild(nsAtom* aTag);
  mozilla::dom::Element* LocateInstance(mozilla::dom::Element* aBoundElt,
                                        nsIContent* aTemplRoot,
                                        nsIContent* aCopyRoot,
                                        mozilla::dom::Element* aTemplChild);

  bool ChromeOnlyContent() { return mChromeOnlyContent; }
  bool BindToUntrustedContent() { return mBindToUntrustedContent; }

  typedef nsClassHashtable<nsRefPtrHashKey<nsAtom>, nsXBLAttributeEntry>
      InnerAttributeTable;

 protected:
  // Ensure that mAttributeTable has been created.
  void EnsureAttributeTable();
  // Ad an entry to the attribute table
  void AddToAttributeTable(int32_t aSourceNamespaceID, nsAtom* aSourceTag,
                           int32_t aDestNamespaceID, nsAtom* aDestTag,
                           mozilla::dom::Element* aContent);
  void ConstructAttributeTable(mozilla::dom::Element* aElement);
  void CreateKeyHandlers();

 private:
  void EnsureResources();

  // MEMBER VARIABLES
 protected:
  nsCOMPtr<nsIURI> mBindingURI;
  nsCOMPtr<nsIURI> mAlternateBindingURI;   // Alternate id-less URI that is only
                                           // non-null on the first binding.
  RefPtr<mozilla::dom::Element> mBinding;  // Strong. We own a ref to our
                                           // content element in the binding
                                           // doc.
  nsAutoPtr<nsXBLPrototypeHandler>
      mPrototypeHandler;  // Strong. DocInfo owns us, and we own the handlers.

  // the url of the base binding
  nsCOMPtr<nsIURI> mBaseBindingURI;

  nsXBLProtoImpl* mImplementation;  // Our prototype implementation (includes
                                    // methods, properties, fields, the
                                    // constructor, and the destructor).

  // Weak.  The docinfo will own our base binding.
  mozilla::WeakPtr<nsXBLPrototypeBinding> mBaseBinding;
  bool mInheritStyle;
  bool mCheckedBaseProto;
  bool mKeyHandlersRegistered;
  bool mChromeOnlyContent;
  bool mBindToUntrustedContent;

  nsAutoPtr<nsXBLPrototypeResources>
      mResources;  // If we have any resources, this will be non-null.

  nsXBLDocumentInfo* mXBLDocInfoWeak;  // A pointer back to our doc info.  Weak,
                                       // since it owns us.

  // A table for attribute containers. Namespace IDs are used as
  // keys in the table. Containers are InnerAttributeTables.
  // This table is used to efficiently handle attribute changes.
  nsAutoPtr<nsClassHashtable<nsUint32HashKey, InnerAttributeTable>>
      mAttributeTable;

  class IIDHashKey : public PLDHashEntryHdr {
   public:
    typedef const nsIID& KeyType;
    typedef const nsIID* KeyTypePointer;

    explicit IIDHashKey(const nsIID* aKey) : mKey(*aKey) {}
    IIDHashKey(const IIDHashKey& aOther) : mKey(aOther.GetKey()) {}
    ~IIDHashKey() {}

    KeyType GetKey() const { return mKey; }
    bool KeyEquals(const KeyTypePointer aKey) const {
      return mKey.Equals(*aKey);
    }

    static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
    static PLDHashNumber HashKey(const KeyTypePointer aKey) {
      // Just use the 32-bit m0 field.
      return aKey->m0;
    }

    enum { ALLOW_MEMMOVE = true };

   private:
    nsIID mKey;
  };
  nsInterfaceHashtable<IIDHashKey, nsIContent>
      mInterfaceTable;  // A table of cached interfaces that we support.

  int32_t mBaseNameSpaceID;  // If we extend a tagname/namespace, then that
                             // information will
  RefPtr<nsAtom> mBaseTag;   // be stored in here.

  nsCOMArray<nsXBLKeyEventHandler> mKeyHandlers;
};

#endif