And use them for SimpleFilter as well. Inspired by a claudometer patch. Differential Revision: https://phabricator.services.mozilla.com/D307250
426 lines
14 KiB
C++
426 lines
14 KiB
C++
/* 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/. */
|
|
|
|
/*
|
|
* Storage of the attributes of a DOM node.
|
|
*/
|
|
|
|
#ifndef AttrArray_h_
|
|
#define AttrArray_h_
|
|
|
|
#include "mozilla/BindgenUniquePtr.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/Span.h"
|
|
#include "mozilla/dom/BorrowedAttrInfo.h"
|
|
#include "nsAttrName.h"
|
|
#include "nsAttrValue.h"
|
|
#include "nsCaseTreatment.h"
|
|
#include "nscore.h"
|
|
|
|
namespace mozilla {
|
|
class AttributeStyles;
|
|
struct StyleLockedDeclarationBlock;
|
|
|
|
namespace dom {
|
|
class Element;
|
|
class ElementInternals;
|
|
|
|
// Caller-supplied assertion that an attribute does not already exist, used
|
|
// to skip redundant scans on the addition path.
|
|
enum class IsKnownNewAttr : bool { No, Yes };
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
class AttrArray {
|
|
using BorrowedAttrInfo = mozilla::dom::BorrowedAttrInfo;
|
|
|
|
// Declare as friend to grant access to call SetAndSwapAttr.
|
|
friend class mozilla::dom::Element;
|
|
friend class mozilla::dom::ElementInternals;
|
|
|
|
public:
|
|
AttrArray() {
|
|
// Initialize bloom filter.
|
|
SetTaggedBloom(0x1ULL);
|
|
}
|
|
~AttrArray() {
|
|
// If mImpl contains a tagged bloom filter (bit 0 = 1), we must clear it
|
|
// before the BindgenUniquePtr destructor tries to free it as a pointer.
|
|
if (HasTaggedBloom()) {
|
|
mImpl.release();
|
|
}
|
|
}
|
|
|
|
bool HasAttrs() const { return !!AttrCount(); }
|
|
|
|
uint32_t AttrCount() const { return HasImpl() ? GetImpl()->mAttrCount : 0; }
|
|
|
|
const nsAttrValue* GetAttr(const nsAtom* aLocalName) const;
|
|
|
|
const nsAttrValue* GetAttr(const nsAtom* aLocalName,
|
|
int32_t aNamespaceID) const;
|
|
// As above but using a string attr name and always using
|
|
// kNameSpaceID_None. This is always case-sensitive.
|
|
const nsAttrValue* GetAttr(const nsAString& aName) const;
|
|
// Get an nsAttrValue by qualified name. Can optionally do
|
|
// ASCII-case-insensitive name matching.
|
|
const nsAttrValue* GetAttr(const nsAString& aName,
|
|
nsCaseTreatment aCaseSensitive) const;
|
|
const nsAttrValue* AttrAt(uint32_t aPos) const;
|
|
|
|
// This stores the argument and clears the pending mapped attribute evaluation
|
|
// bit, so after calling this IsPendingMappedAttributeEvaluation() is
|
|
// guaranteed to return false.
|
|
void SetMappedDeclarationBlock(
|
|
already_AddRefed<mozilla::StyleLockedDeclarationBlock>);
|
|
|
|
bool IsPendingMappedAttributeEvaluation() const {
|
|
return HasImpl() && GetImpl()->mMappedAttributeBits & 1;
|
|
}
|
|
|
|
mozilla::StyleLockedDeclarationBlock* GetMappedDeclarationBlock() const {
|
|
return HasImpl() ? GetImpl()->GetMappedDeclarationBlock() : nullptr;
|
|
}
|
|
|
|
// Remove the attr at position aPos. The value of the attr is placed in
|
|
// aValue; any value that was already in aValue is destroyed.
|
|
nsresult RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue);
|
|
|
|
// Returns attribute name at given position, *not* out-of-bounds safe
|
|
const nsAttrName* AttrNameAt(uint32_t aPos) const;
|
|
|
|
// Returns the attribute info at a given position, *not* out-of-bounds safe
|
|
BorrowedAttrInfo AttrInfoAt(uint32_t aPos) const;
|
|
|
|
// If aPos is in bounds, set aResult to the attribute at the given position
|
|
// without AddRefing it and return true. Otherwise, return false.
|
|
[[nodiscard]] bool GetSafeAttrNameAt(uint32_t aPos,
|
|
const nsAttrName** aResult) const;
|
|
|
|
// If aPos is in bounds, return the attribute at the given position.
|
|
// Otherwise, crash.
|
|
const nsAttrName* GetSafeAttrNameAt(uint32_t aPos) const;
|
|
|
|
// Find an existing attribute by qualified name.
|
|
//
|
|
// When aOutAtom is non-null and no matching attribute is found, *aOutAtom
|
|
// is set to the atomized lookup name so callers can reuse it without
|
|
// re-atomizing. In all other cases (match found, or the element has no
|
|
// attributes), *aOutAtom is set to nullptr.
|
|
const nsAttrName* GetExistingAttrNameFromQName(
|
|
const nsAString& aName, RefPtr<nsAtom>* aOutAtom = nullptr) const;
|
|
int32_t IndexOfAttr(const nsAtom* aLocalName) const;
|
|
int32_t IndexOfAttr(const nsAtom* aLocalName, int32_t aNamespaceID) const;
|
|
|
|
void Compact();
|
|
|
|
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
|
|
|
// Mark the element as pending mapped attribute evaluation. This should be
|
|
// called when a mapped attribute is changed (regardless of connectedness).
|
|
bool MarkAsPendingPresAttributeEvaluation() {
|
|
// It'd be great to be able to assert that HasImpl() is true or we're the
|
|
// <body> or <svg> elements.
|
|
if (MOZ_UNLIKELY(!HasImpl()) && !GrowBy(1)) {
|
|
return false;
|
|
}
|
|
InfallibleMarkAsPendingPresAttributeEvaluation();
|
|
return true;
|
|
}
|
|
|
|
// See above.
|
|
void InfallibleMarkAsPendingPresAttributeEvaluation() {
|
|
MOZ_ASSERT(HasImpl());
|
|
GetImpl()->mMappedAttributeBits |= 1;
|
|
}
|
|
|
|
// Clear the servo declaration block on the mapped attributes, if any
|
|
// Will assert off main thread
|
|
void ClearMappedServoStyle();
|
|
|
|
// Increases capacity (if necessary) to have enough space to accomodate the
|
|
// unmapped attributes of |aOther|.
|
|
nsresult EnsureCapacityToClone(const AttrArray& aOther);
|
|
|
|
enum AttrValuesState { ATTR_MISSING = -1, ATTR_VALUE_NO_MATCH = -2 };
|
|
using AttrValuesArray = nsStaticAtom* const;
|
|
int32_t FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
|
|
AttrValuesArray* aValues,
|
|
nsCaseTreatment aCaseSensitive) const;
|
|
|
|
inline bool GetAttr(int32_t aNameSpaceID, const nsAtom* aName,
|
|
nsAString& aResult) const {
|
|
MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in");
|
|
const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
|
|
if (!val) {
|
|
return false;
|
|
}
|
|
val->ToString(aResult);
|
|
return true;
|
|
}
|
|
|
|
inline bool GetAttr(const nsAtom* aName, nsAString& aResult) const {
|
|
MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in");
|
|
const nsAttrValue* val = GetAttr(aName);
|
|
if (!val) {
|
|
return false;
|
|
}
|
|
val->ToString(aResult);
|
|
return true;
|
|
}
|
|
|
|
inline bool HasAttr(const nsAtom* aName) const { return !!GetAttr(aName); }
|
|
|
|
inline bool HasAttr(int32_t aNameSpaceID, const nsAtom* aName) const {
|
|
return !!GetAttr(aName, aNameSpaceID);
|
|
}
|
|
|
|
inline bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
|
|
const nsAString& aValue,
|
|
nsCaseTreatment aCaseSensitive) const {
|
|
NS_ASSERTION(aName, "Must have attr name");
|
|
NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
|
|
const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
|
|
return val && val->Equals(aValue, aCaseSensitive);
|
|
}
|
|
|
|
inline bool AttrValueIs(int32_t aNameSpaceID, const nsAtom* aName,
|
|
const nsAtom* aValue,
|
|
nsCaseTreatment aCaseSensitive) const {
|
|
NS_ASSERTION(aName, "Must have attr name");
|
|
NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
|
|
NS_ASSERTION(aValue, "Null value atom");
|
|
|
|
const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
|
|
return val && val->Equals(aValue, aCaseSensitive);
|
|
}
|
|
|
|
struct InternalAttr {
|
|
nsAttrName mName;
|
|
nsAttrValue mValue;
|
|
};
|
|
|
|
AttrArray(const AttrArray& aOther) = delete;
|
|
AttrArray& operator=(const AttrArray& aOther) = delete;
|
|
|
|
bool GrowBy(uint32_t aGrowSize);
|
|
bool GrowTo(uint32_t aCapacity);
|
|
|
|
void Clear() {
|
|
// If mImpl contains a tagged bloom filter, release it first to prevent
|
|
// unique_ptr from trying to delete it as a pointer
|
|
if (HasTaggedBloom()) {
|
|
mImpl.release();
|
|
} else {
|
|
mImpl.reset();
|
|
}
|
|
// Reinitialize to default tagged bloom filter
|
|
SetTaggedBloom(0x1ULL);
|
|
}
|
|
|
|
// For the HTML parser to call only after ensuring capacity.
|
|
// Moves out of the arguments and returns a pointer to the
|
|
// value once it is in place.
|
|
const nsAttrValue* AddNewAttributeAssumeAvailableSlot(RefPtr<nsAtom>& aName,
|
|
nsAttrValue& aValue);
|
|
|
|
private:
|
|
// Tries to create an attribute, growing the buffer if needed, with the given
|
|
// name and value.
|
|
//
|
|
// The value is moved from the argument.
|
|
//
|
|
// `Name` can be anything you construct a `nsAttrName` with (either an atom or
|
|
// a NodeInfo pointer).
|
|
template <typename Name>
|
|
nsresult AddNewAttribute(Name*, nsAttrValue&);
|
|
|
|
class Impl {
|
|
public:
|
|
constexpr static size_t AllocationSizeForAttributes(uint32_t aAttrCount) {
|
|
return sizeof(Impl) + aAttrCount * sizeof(InternalAttr);
|
|
}
|
|
|
|
mozilla::StyleLockedDeclarationBlock* GetMappedDeclarationBlock() const {
|
|
return reinterpret_cast<mozilla::StyleLockedDeclarationBlock*>(
|
|
mMappedAttributeBits & ~uintptr_t(1));
|
|
}
|
|
|
|
auto Attrs() const {
|
|
return mozilla::Span<const InternalAttr>{mBuffer, mAttrCount};
|
|
}
|
|
|
|
auto Attrs() { return mozilla::Span<InternalAttr>{mBuffer, mAttrCount}; }
|
|
|
|
Impl(const Impl&) = delete;
|
|
Impl(Impl&&) = delete;
|
|
~Impl();
|
|
|
|
uint32_t mAttrCount;
|
|
uint32_t mCapacity; // In number of InternalAttrs
|
|
|
|
// mMappedAttributeBits is a tagged pointer of a
|
|
// StyleLockedDeclarationBlock, which holds the style information that our
|
|
// attributes map to.
|
|
//
|
|
// If the lower bit is set, then our mapped attributes are dirty. This just
|
|
// means that we might have mapped attributes (or used to and no longer
|
|
// have), and are pending an update to recompute our declaration.
|
|
uintptr_t mMappedAttributeBits = 0;
|
|
|
|
// Combined bloom filter (63 bits) for both classes and attributes
|
|
// Bit 0: Always 1 to match tagged pointer implementation.
|
|
// Bits 1-63: Combined bloom filter (63 bits)
|
|
uint64_t mSubtreeBloomFilter;
|
|
|
|
public:
|
|
Impl() : mSubtreeBloomFilter(0xFFFFFFFFFFFFFFFFULL) {}
|
|
|
|
// Allocated in the same buffer as `Impl`.
|
|
InternalAttr mBuffer[0];
|
|
};
|
|
|
|
mozilla::Span<InternalAttr> Attrs() {
|
|
return HasImpl() ? GetImpl()->Attrs() : mozilla::Span<InternalAttr>();
|
|
}
|
|
|
|
mozilla::Span<const InternalAttr> Attrs() const {
|
|
return HasImpl() ? GetImpl()->Attrs() : mozilla::Span<const InternalAttr>();
|
|
}
|
|
|
|
bool HasTaggedBloom() const {
|
|
return (reinterpret_cast<uintptr_t>(mImpl.get()) & 1) != 0;
|
|
}
|
|
|
|
bool HasImpl() const {
|
|
MOZ_ASSERT(mImpl.get() != nullptr);
|
|
return !HasTaggedBloom();
|
|
}
|
|
|
|
Impl* GetImpl() {
|
|
MOZ_ASSERT(HasImpl());
|
|
return mImpl.get();
|
|
}
|
|
|
|
const Impl* GetImpl() const {
|
|
MOZ_ASSERT(HasImpl());
|
|
return mImpl.get();
|
|
}
|
|
|
|
uint64_t GetTaggedBloom() const {
|
|
MOZ_ASSERT(HasTaggedBloom());
|
|
return reinterpret_cast<uint64_t>(mImpl.get());
|
|
}
|
|
|
|
void SetTaggedBloom(uint64_t aBloom) {
|
|
// If we already have a tagged bloom, release it first
|
|
if (HasTaggedBloom()) {
|
|
mImpl.release();
|
|
}
|
|
// Ensure bit 0 is set (tag bit) and upper bit is clear (valid pointer
|
|
// range)
|
|
MOZ_ASSERT((aBloom & 1) != 0);
|
|
mImpl.reset(reinterpret_cast<Impl*>(static_cast<uintptr_t>(aBloom)));
|
|
}
|
|
|
|
void SetImpl(Impl* aImpl) {
|
|
MOZ_ASSERT(aImpl != nullptr &&
|
|
(reinterpret_cast<uintptr_t>(aImpl) & 1) == 0);
|
|
// If we currently have a tagged bloom filter, release it first to prevent
|
|
// unique_ptr from trying to delete it as a pointer
|
|
if (HasTaggedBloom()) {
|
|
mImpl.release();
|
|
}
|
|
mImpl.reset(aImpl);
|
|
}
|
|
|
|
public:
|
|
// Set bloom filter directly from 64-bit value
|
|
void SetSubtreeBloomFilter(uint64_t aBloom) {
|
|
if (HasImpl()) {
|
|
GetImpl()->mSubtreeBloomFilter = aBloom;
|
|
} else {
|
|
SetTaggedBloom(aBloom);
|
|
}
|
|
}
|
|
|
|
// Get bloom filter, used for fast querySelector
|
|
uint64_t GetSubtreeBloomFilter() const {
|
|
if (HasImpl()) {
|
|
return GetImpl()->mSubtreeBloomFilter;
|
|
} else if (HasTaggedBloom()) {
|
|
return GetTaggedBloom();
|
|
}
|
|
MOZ_ASSERT_UNREACHABLE("Bloom filter should never be nullptr");
|
|
return 0xFFFFFFFFFFFFFFFFULL;
|
|
}
|
|
|
|
// Update bloom filter with new 64-bit hash
|
|
void UpdateSubtreeBloomFilter(uint64_t aHash) {
|
|
if (HasImpl()) {
|
|
GetImpl()->mSubtreeBloomFilter |= aHash;
|
|
} else {
|
|
uint64_t current = GetSubtreeBloomFilter();
|
|
SetTaggedBloom(current | aHash);
|
|
}
|
|
}
|
|
|
|
// Check if bloom may contain the given hash
|
|
bool BloomMayHave(uint64_t aHash) const {
|
|
uint64_t bloom = GetSubtreeBloomFilter();
|
|
return (bloom & aHash) == aHash;
|
|
}
|
|
|
|
// Hash function for bloom filter (k=2). Returns a 64-bit value with bit 0
|
|
// set to 1 (tag bit) and 2 bits set in the available bloom range.
|
|
static uint64_t HashForBloomFilter(const nsAtom* aAtom) {
|
|
if (!aAtom) {
|
|
return 1ULL; // Just the tag bit
|
|
}
|
|
// On 32-bit platforms, we have 31 bits for bloom + 1 tag bit
|
|
// On 64-bit platforms, we have 63 bits for bloom + 1 tag bit
|
|
constexpr int kAttrBloomBits = sizeof(uintptr_t) == 4 ? 31 : 63;
|
|
|
|
uint32_t hash = aAtom->hash();
|
|
uint64_t filter = 1ULL;
|
|
// Set 2 bits in the available range (bits 1-31 on 32-bit, 1-63 on 64-bit)
|
|
uint32_t bit1 = hash % kAttrBloomBits;
|
|
uint32_t bit2 = (hash >> 6) % kAttrBloomBits;
|
|
filter |= 1ULL << (1 + bit1);
|
|
filter |= 1ULL << (1 + bit2);
|
|
return filter;
|
|
}
|
|
|
|
private:
|
|
// Internal method that swaps the current attribute value with aValue.
|
|
// Does NOT update bloom filters - external code should always use
|
|
// Element::SetAndSwapAttr instead, which calls this and updates bloom
|
|
// filters. If the attribute was unset, an empty value will be swapped into
|
|
// aValue and aHadValue will be set to false. Otherwise, aHadValue will be set
|
|
// to true.
|
|
nsresult SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue,
|
|
bool* aHadValue,
|
|
mozilla::dom::IsKnownNewAttr aIsKnownNew);
|
|
nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue,
|
|
bool* aHadValue,
|
|
mozilla::dom::IsKnownNewAttr aIsKnownNew);
|
|
|
|
// mImpl serves dual purposes using pointer tagging:
|
|
//
|
|
// 1. When the element has no attributes:
|
|
// - Stores a "tagged bloom filter" (bit 0 is 1). The remaining
|
|
// bits are used as a bloomfilter to identify the existence of
|
|
// attribute & class names in this subtree for querySelector.
|
|
//
|
|
// 2. When the element has attributes:
|
|
// - Points to an Impl struct (bit 0 is 0).
|
|
// - Contains actual attribute storage and the bloom filter
|
|
// is now copied as a member of Impl.
|
|
//
|
|
// Use HasTaggedBloom() vs HasImpl() to distinguish.
|
|
mozilla::BindgenUniquePtr<Impl> mImpl;
|
|
};
|
|
|
|
#endif
|