mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 22:48:47 +00:00
1819 lines
65 KiB
C++
1819 lines
65 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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 "nsCSSRuleUtils.h"
|
|
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/HTMLSlotElement.h"
|
|
#include "mozilla/dom/ShadowRoot.h"
|
|
#include "nsIMozBrowserFrame.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsRuleWalker.h"
|
|
#include "nsStyleUtil.h"
|
|
#include "StyleRule.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
#define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled"
|
|
|
|
static bool gSupportVisitedPseudo = true;
|
|
static bool gLoadImportedSheetsInOrder = true;
|
|
|
|
static nsTArray<nsCOMPtr<nsIAtom>>* sSystemMetrics = 0;
|
|
|
|
#ifdef XP_WIN
|
|
uint8_t nsCSSRuleUtils::sWinThemeId = LookAndFeel::eWindowsTheme_Generic;
|
|
#endif
|
|
|
|
/* static */ void
|
|
nsCSSRuleUtils::Startup()
|
|
{
|
|
Preferences::AddBoolVarCache(
|
|
&gSupportVisitedPseudo, VISITED_PSEUDO_PREF, true);
|
|
Preferences::AddBoolVarCache(&gLoadImportedSheetsInOrder,
|
|
"layout.css.load-imported-sheets-in-order",
|
|
true);
|
|
}
|
|
|
|
static bool
|
|
InitSystemMetrics()
|
|
{
|
|
NS_ASSERTION(!sSystemMetrics, "already initialized");
|
|
|
|
sSystemMetrics = new nsTArray<nsCOMPtr<nsIAtom>>;
|
|
NS_ENSURE_TRUE(sSystemMetrics, false);
|
|
|
|
/***************************************************************************
|
|
* ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN *
|
|
* nsMediaFeatures.cpp *
|
|
***************************************************************************/
|
|
|
|
int32_t metricResult =
|
|
LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollArrowStyle);
|
|
if (metricResult & LookAndFeel::eScrollArrow_StartBackward) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_backward);
|
|
}
|
|
if (metricResult & LookAndFeel::eScrollArrow_StartForward) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_forward);
|
|
}
|
|
if (metricResult & LookAndFeel::eScrollArrow_EndBackward) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_backward);
|
|
}
|
|
if (metricResult & LookAndFeel::eScrollArrow_EndForward) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_forward);
|
|
}
|
|
|
|
metricResult = LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollSliderStyle);
|
|
if (metricResult != LookAndFeel::eScrollThumbStyle_Normal) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_thumb_proportional);
|
|
}
|
|
|
|
metricResult = LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars);
|
|
if (metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::overlay_scrollbars);
|
|
}
|
|
|
|
metricResult = LookAndFeel::GetInt(LookAndFeel::eIntID_MenuBarDrag);
|
|
if (metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::menubar_drag);
|
|
}
|
|
|
|
nsresult rv =
|
|
LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsDefaultTheme, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_default_theme);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacGraphiteTheme, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::mac_graphite_theme);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacLionTheme, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::mac_lion_theme);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacYosemiteTheme, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::mac_yosemite_theme);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsAccentColorApplies,
|
|
&metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_accent_color_applies);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsAccentColorIsDark,
|
|
&metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_accent_color_is_dark);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_DWMCompositor, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_compositor);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsGlass, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_glass);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ColorPickerAvailable,
|
|
&metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::color_picker_available);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsClassic, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_classic);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_TouchEnabled, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::touch_enabled);
|
|
}
|
|
|
|
rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled,
|
|
&metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled);
|
|
}
|
|
|
|
rv =
|
|
LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton, &metricResult);
|
|
if (NS_SUCCEEDED(rv) && metricResult) {
|
|
sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button);
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
if (NS_SUCCEEDED(LookAndFeel::GetInt(
|
|
LookAndFeel::eIntID_WindowsThemeIdentifier, &metricResult))) {
|
|
nsCSSRuleUtils::SetWindowsThemeIdentifier(
|
|
static_cast<uint8_t>(metricResult));
|
|
switch (metricResult) {
|
|
case LookAndFeel::eWindowsTheme_Aero:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero);
|
|
break;
|
|
case LookAndFeel::eWindowsTheme_AeroLite:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero_lite);
|
|
break;
|
|
case LookAndFeel::eWindowsTheme_LunaBlue:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_blue);
|
|
break;
|
|
case LookAndFeel::eWindowsTheme_LunaOlive:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_olive);
|
|
break;
|
|
case LookAndFeel::eWindowsTheme_LunaSilver:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_luna_silver);
|
|
break;
|
|
case LookAndFeel::eWindowsTheme_Royale:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_royale);
|
|
break;
|
|
case LookAndFeel::eWindowsTheme_Zune:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_zune);
|
|
break;
|
|
case LookAndFeel::eWindowsTheme_Generic:
|
|
sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_generic);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static nsPseudoClassList*
|
|
FindPseudoClass(const nsCSSSelector* aSelector, CSSPseudoClassType aType)
|
|
{
|
|
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
|
|
pseudoClass;
|
|
pseudoClass = pseudoClass->mNext) {
|
|
if (pseudoClass->mType == aType) {
|
|
return pseudoClass;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static bool
|
|
ElementMatchesPart(Element* aElement, const nsPseudoClassList* aPseudoClass)
|
|
{
|
|
MOZ_ASSERT(aPseudoClass->mType == CSSPseudoClassType::part);
|
|
MOZ_ASSERT(aPseudoClass->u.mString);
|
|
|
|
nsAutoString partValue;
|
|
if (!aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::part, partValue)) {
|
|
return false;
|
|
}
|
|
|
|
const nsDefaultStringComparator comparator;
|
|
return nsStyleUtil::ValueIncludes(
|
|
partValue, nsDependentString(aPseudoClass->u.mString), comparator);
|
|
}
|
|
|
|
/* static */ void
|
|
nsCSSRuleUtils::FreeSystemMetrics()
|
|
{
|
|
delete sSystemMetrics;
|
|
sSystemMetrics = nullptr;
|
|
}
|
|
|
|
/* static */ void
|
|
nsCSSRuleUtils::Shutdown()
|
|
{
|
|
FreeSystemMetrics();
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::HasSystemMetric(nsIAtom* aMetric)
|
|
{
|
|
if (!sSystemMetrics && !InitSystemMetrics()) {
|
|
return false;
|
|
}
|
|
return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex;
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::LoadImportedSheetsInOrderEnabled()
|
|
{
|
|
return gLoadImportedSheetsInOrder;
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
/* static */ uint8_t
|
|
nsCSSRuleUtils::GetWindowsThemeIdentifier()
|
|
{
|
|
if (!sSystemMetrics)
|
|
InitSystemMetrics();
|
|
return sWinThemeId;
|
|
}
|
|
#endif
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::RestrictedSelectorListMatches(
|
|
Element* aElement,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
nsCSSSelectorList* aSelectorList)
|
|
{
|
|
MOZ_ASSERT(!aTreeMatchContext.mForScopedStyle,
|
|
"mCurrentStyleScope will need to be saved and restored after the "
|
|
"SelectorMatchesTree call");
|
|
|
|
NodeMatchContext nodeContext(EventStates(), false);
|
|
SelectorMatchesFlags flags = aElement->IsInShadowTree()
|
|
? SelectorMatchesFlags::NONE
|
|
: SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE;
|
|
return SelectorListMatches(
|
|
aElement, aSelectorList, nodeContext, aTreeMatchContext, flags);
|
|
}
|
|
|
|
/* static */ EventStates
|
|
nsCSSRuleUtils::GetContentState(Element* aElement,
|
|
const TreeMatchContext& aTreeMatchContext)
|
|
{
|
|
EventStates state = aElement->StyleState();
|
|
|
|
// If we are not supposed to mark visited links as such, be sure to
|
|
// flip the bits appropriately. We want to do this here, rather
|
|
// than in GetContentStateForVisitedHandling, so that we don't
|
|
// expose that :visited support is disabled to the Web page.
|
|
if (state.HasState(NS_EVENT_STATE_VISITED) &&
|
|
(!gSupportVisitedPseudo || aElement->OwnerDoc()->IsBeingUsedAsImage() ||
|
|
aTreeMatchContext.mUsingPrivateBrowsing)) {
|
|
state &= ~NS_EVENT_STATE_VISITED;
|
|
state |= NS_EVENT_STATE_UNVISITED;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::IsLink(const Element* aElement)
|
|
{
|
|
EventStates state = aElement->StyleState();
|
|
return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED |
|
|
NS_EVENT_STATE_UNVISITED);
|
|
}
|
|
|
|
/* static */ EventStates
|
|
nsCSSRuleUtils::GetContentStateForVisitedHandling(
|
|
Element* aElement,
|
|
const TreeMatchContext& aTreeMatchContext,
|
|
nsRuleWalker::VisitedHandlingType aVisitedHandling,
|
|
bool aIsRelevantLink)
|
|
{
|
|
EventStates contentState = GetContentState(aElement, aTreeMatchContext);
|
|
if (contentState.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED |
|
|
NS_EVENT_STATE_UNVISITED)) {
|
|
MOZ_ASSERT(IsLink(aElement), "IsLink() should match state");
|
|
contentState &= ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED);
|
|
if (aIsRelevantLink) {
|
|
switch (aVisitedHandling) {
|
|
case nsRuleWalker::eRelevantLinkUnvisited:
|
|
contentState |= NS_EVENT_STATE_UNVISITED;
|
|
break;
|
|
case nsRuleWalker::eRelevantLinkVisited:
|
|
contentState |= NS_EVENT_STATE_VISITED;
|
|
break;
|
|
case nsRuleWalker::eLinksVisitedOrUnvisited:
|
|
contentState |= NS_EVENT_STATE_UNVISITED | NS_EVENT_STATE_VISITED;
|
|
break;
|
|
}
|
|
} else {
|
|
contentState |= NS_EVENT_STATE_UNVISITED;
|
|
}
|
|
}
|
|
return contentState;
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::RestrictedSelectorMatches(
|
|
Element* aElement,
|
|
nsCSSSelector* aSelector,
|
|
TreeMatchContext& aTreeMatchContext)
|
|
{
|
|
MOZ_ASSERT(aSelector->IsRestrictedSelector(),
|
|
"aSelector must not have a pseudo-element");
|
|
|
|
NS_WARNING_ASSERTION(
|
|
!HasPseudoClassSelectorArgsWithCombinators(aSelector),
|
|
"processing eRestyle_SomeDescendants can be slow if pseudo-classes with "
|
|
"selector arguments can now have combinators in them");
|
|
|
|
// We match aSelector as if :visited and :link both match visited and
|
|
// unvisited links.
|
|
|
|
NodeMatchContext nodeContext(EventStates(),
|
|
nsCSSRuleUtils::IsLink(aElement));
|
|
if (nodeContext.mIsRelevantLink) {
|
|
aTreeMatchContext.SetHaveRelevantLink();
|
|
}
|
|
aTreeMatchContext.ResetForUnvisitedMatching();
|
|
bool matches = SelectorMatches(aElement,
|
|
aSelector,
|
|
nodeContext,
|
|
aTreeMatchContext,
|
|
SelectorMatchesFlags::NONE);
|
|
if (nodeContext.mIsRelevantLink) {
|
|
aTreeMatchContext.ResetForVisitedMatching();
|
|
if (SelectorMatches(aElement,
|
|
aSelector,
|
|
nodeContext,
|
|
aTreeMatchContext,
|
|
SelectorMatchesFlags::NONE)) {
|
|
matches = true;
|
|
}
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
// Return whether the selector matches conditions for the :active and
|
|
// :hover quirk.
|
|
static inline bool
|
|
ActiveHoverQuirkMatches(nsCSSSelector* aSelector,
|
|
SelectorMatchesFlags aSelectorFlags)
|
|
{
|
|
if (aSelector->HasTagSelector() || aSelector->mAttrList ||
|
|
aSelector->mIDList || aSelector->mClassList ||
|
|
aSelector->IsPseudoElement() || aSelector->IsHybridPseudoElement() ||
|
|
// Having this quirk means that some selectors will no longer match,
|
|
// so it's better to return false when we aren't sure (i.e., the
|
|
// flags are unknown).
|
|
aSelectorFlags & (SelectorMatchesFlags::UNKNOWN |
|
|
SelectorMatchesFlags::HAS_PSEUDO_ELEMENT |
|
|
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT |
|
|
SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE)) {
|
|
return false;
|
|
}
|
|
|
|
// No pseudo-class other than :active and :hover.
|
|
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
|
|
pseudoClass;
|
|
pseudoClass = pseudoClass->mNext) {
|
|
if (pseudoClass->mType != CSSPseudoClassType::hover &&
|
|
pseudoClass->mType != CSSPseudoClassType::active) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// This function is to be called once we have fetched a value for an attribute
|
|
// whose namespace and name match those of aAttrSelector. This function
|
|
// performs comparisons on the value only, based on aAttrSelector->mFunction.
|
|
static bool
|
|
AttrMatchesValue(const nsAttrSelector* aAttrSelector,
|
|
const nsString& aValue,
|
|
bool isHTML)
|
|
{
|
|
NS_PRECONDITION(aAttrSelector, "Must have an attribute selector");
|
|
|
|
// http://lists.w3.org/Archives/Public/www-style/2008Apr/0038.html
|
|
// *= (CONTAINSMATCH) ~= (INCLUDES) ^= (BEGINSMATCH) $= (ENDSMATCH)
|
|
// all accept the empty string, but match nothing.
|
|
if (aAttrSelector->mValue.IsEmpty() &&
|
|
(aAttrSelector->mFunction == NS_ATTR_FUNC_INCLUDES ||
|
|
aAttrSelector->mFunction == NS_ATTR_FUNC_ENDSMATCH ||
|
|
aAttrSelector->mFunction == NS_ATTR_FUNC_BEGINSMATCH ||
|
|
aAttrSelector->mFunction == NS_ATTR_FUNC_CONTAINSMATCH))
|
|
return false;
|
|
|
|
const nsDefaultStringComparator defaultComparator;
|
|
const nsASCIICaseInsensitiveStringComparator ciComparator;
|
|
const nsStringComparator& comparator =
|
|
aAttrSelector->IsValueCaseSensitive(isHTML)
|
|
? static_cast<const nsStringComparator&>(defaultComparator)
|
|
: static_cast<const nsStringComparator&>(ciComparator);
|
|
|
|
switch (aAttrSelector->mFunction) {
|
|
case NS_ATTR_FUNC_EQUALS:
|
|
return aValue.Equals(aAttrSelector->mValue, comparator);
|
|
case NS_ATTR_FUNC_INCLUDES:
|
|
return nsStyleUtil::ValueIncludes(
|
|
aValue, aAttrSelector->mValue, comparator);
|
|
case NS_ATTR_FUNC_DASHMATCH:
|
|
return nsStyleUtil::DashMatchCompare(
|
|
aValue, aAttrSelector->mValue, comparator);
|
|
case NS_ATTR_FUNC_ENDSMATCH:
|
|
return StringEndsWith(aValue, aAttrSelector->mValue, comparator);
|
|
case NS_ATTR_FUNC_BEGINSMATCH:
|
|
return StringBeginsWith(aValue, aAttrSelector->mValue, comparator);
|
|
case NS_ATTR_FUNC_CONTAINSMATCH:
|
|
return FindInReadable(aAttrSelector->mValue, aValue, comparator);
|
|
default:
|
|
NS_NOTREACHED("Shouldn't be ending up here");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
edgeChildMatches(Element* aElement,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
bool checkFirst,
|
|
bool checkLast)
|
|
{
|
|
nsIContent* parent = aElement->GetParent();
|
|
if (parent && aTreeMatchContext.mForStyling)
|
|
parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR);
|
|
|
|
return (!checkFirst || aTreeMatchContext.mNthIndexCache.GetNthIndex(
|
|
aElement, false, false, true) == 1) &&
|
|
(!checkLast || aTreeMatchContext.mNthIndexCache.GetNthIndex(
|
|
aElement, false, true, true) == 1);
|
|
}
|
|
|
|
static inline bool
|
|
nthChildGenericMatches(Element* aElement,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
nsPseudoClassList* pseudoClass,
|
|
bool isOfType,
|
|
bool isFromEnd)
|
|
{
|
|
nsIContent* parent = aElement->GetParent();
|
|
if (parent && aTreeMatchContext.mForStyling) {
|
|
if (isFromEnd)
|
|
parent->SetFlags(NODE_HAS_SLOW_SELECTOR);
|
|
else
|
|
parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
|
|
}
|
|
|
|
const int32_t index = aTreeMatchContext.mNthIndexCache.GetNthIndex(
|
|
aElement, isOfType, isFromEnd, false);
|
|
if (index <= 0) {
|
|
// Node is anonymous content (not really a child of its parent).
|
|
return false;
|
|
}
|
|
|
|
const int32_t a = pseudoClass->u.mNumbers[0];
|
|
const int32_t b = pseudoClass->u.mNumbers[1];
|
|
// result should be true if there exists n >= 0 such that
|
|
// a * n + b == index.
|
|
if (a == 0) {
|
|
return b == index;
|
|
}
|
|
|
|
// Integer division in C does truncation (towards 0). So
|
|
// check that the result is nonnegative, and that there was no
|
|
// truncation.
|
|
const CheckedInt<int32_t> indexMinusB = CheckedInt<int32_t>(index) - b;
|
|
const CheckedInt<int32_t> n = indexMinusB / a;
|
|
return n.isValid() && n.value() >= 0 && a * n == indexMinusB;
|
|
}
|
|
|
|
static inline bool
|
|
edgeOfTypeMatches(Element* aElement,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
bool checkFirst,
|
|
bool checkLast)
|
|
{
|
|
nsIContent* parent = aElement->GetParent();
|
|
if (parent && aTreeMatchContext.mForStyling) {
|
|
if (checkLast)
|
|
parent->SetFlags(NODE_HAS_SLOW_SELECTOR);
|
|
else
|
|
parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
|
|
}
|
|
|
|
return (!checkFirst || aTreeMatchContext.mNthIndexCache.GetNthIndex(
|
|
aElement, true, false, true) == 1) &&
|
|
(!checkLast || aTreeMatchContext.mNthIndexCache.GetNthIndex(
|
|
aElement, true, true, true) == 1);
|
|
}
|
|
|
|
static inline bool
|
|
checkGenericEmptyMatches(Element* aElement,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
bool isWhitespaceSignificant)
|
|
{
|
|
nsIContent* child = nullptr;
|
|
int32_t index = -1;
|
|
|
|
if (aTreeMatchContext.mForStyling)
|
|
aElement->SetFlags(NODE_HAS_EMPTY_SELECTOR);
|
|
|
|
do {
|
|
child = aElement->GetChildAt(++index);
|
|
// stop at first non-comment (and non-whitespace for
|
|
// :-moz-only-whitespace) node
|
|
} while (child && !nsStyleUtil::IsSignificantChild(
|
|
child, true, isWhitespaceSignificant));
|
|
return (child == nullptr);
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::StateSelectorMatches(Element* aElement,
|
|
nsCSSSelector* aSelector,
|
|
NodeMatchContext& aNodeMatchContext,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
SelectorMatchesFlags aSelectorFlags,
|
|
bool* const aDependence,
|
|
EventStates aStatesToCheck)
|
|
{
|
|
NS_PRECONDITION(!aStatesToCheck.IsEmpty(),
|
|
"should only need to call StateSelectorMatches if "
|
|
"aStatesToCheck is not empty");
|
|
|
|
// Bit-based pseudo-classes
|
|
if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
|
|
NS_EVENT_STATE_HOVER) &&
|
|
aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks &&
|
|
ActiveHoverQuirkMatches(aSelector, aSelectorFlags) &&
|
|
aElement->IsHTMLElement() && !nsCSSRuleUtils::IsLink(aElement)) {
|
|
// In quirks mode, only make links sensitive to selectors ":active"
|
|
// and ":hover".
|
|
return false;
|
|
}
|
|
|
|
if (aTreeMatchContext.mForStyling &&
|
|
aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER)) {
|
|
// Mark the element as having :hover-dependent style
|
|
aElement->SetHasRelevantHoverRules();
|
|
}
|
|
|
|
if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(aStatesToCheck)) {
|
|
if (aDependence) {
|
|
*aDependence = true;
|
|
}
|
|
} else {
|
|
EventStates contentState =
|
|
nsCSSRuleUtils::GetContentStateForVisitedHandling(
|
|
aElement,
|
|
aTreeMatchContext,
|
|
aTreeMatchContext.VisitedHandling(),
|
|
aNodeMatchContext.mIsRelevantLink);
|
|
if (!contentState.HasAtLeastOneOfStates(aStatesToCheck)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::StateSelectorMatches(Element* aElement,
|
|
nsCSSSelector* aSelector,
|
|
NodeMatchContext& aNodeMatchContext,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
SelectorMatchesFlags aSelectorFlags)
|
|
{
|
|
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
|
|
pseudoClass;
|
|
pseudoClass = pseudoClass->mNext) {
|
|
if (pseudoClass->mType == CSSPseudoClassType::autofill ||
|
|
pseudoClass->mType == CSSPseudoClassType::mozAutofillHighlight) {
|
|
// Match if the element has the autofill state, regardless of focus
|
|
if (!aElement->State().HasState(NS_EVENT_STATE_AUTOFILL)) {
|
|
return false;
|
|
}
|
|
continue; // This pseudo-class matches
|
|
}
|
|
auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
|
|
EventStates statesToCheck = nsCSSPseudoClasses::sPseudoClassStates[idx];
|
|
if (!statesToCheck.IsEmpty() && !StateSelectorMatches(aElement,
|
|
aSelector,
|
|
aNodeMatchContext,
|
|
aTreeMatchContext,
|
|
aSelectorFlags,
|
|
nullptr,
|
|
statesToCheck)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns whether aSelector can match featureless elements.
|
|
/* static */ bool
|
|
nsCSSRuleUtils::CanMatchFeaturelessElement(nsCSSSelector* aSelector)
|
|
{
|
|
if (aSelector->HasFeatureSelectors()) {
|
|
return false;
|
|
}
|
|
|
|
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
|
|
pseudoClass;
|
|
pseudoClass = pseudoClass->mNext) {
|
|
if (pseudoClass->mType == CSSPseudoClassType::host ||
|
|
pseudoClass->mType == CSSPseudoClassType::hostContext) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// |aDependence| has two functions:
|
|
// * when non-null, it indicates that we're processing a negation,
|
|
// which is done only when SelectorMatches calls itself recursively
|
|
// * what it points to should be set to true whenever a test is skipped
|
|
// because of aNodeMatchContent.mStateMask
|
|
/* static */ bool
|
|
nsCSSRuleUtils::SelectorMatches(Element* aElement,
|
|
nsCSSSelector* aSelector,
|
|
NodeMatchContext& aNodeMatchContext,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
SelectorMatchesFlags aSelectorFlags,
|
|
bool* const aDependence)
|
|
{
|
|
NS_PRECONDITION(!aSelector->IsPseudoElement(),
|
|
"Pseudo-element snuck into SelectorMatches?");
|
|
MOZ_ASSERT(aTreeMatchContext.mForStyling ||
|
|
!aNodeMatchContext.mIsRelevantLink,
|
|
"mIsRelevantLink should be set to false when mForStyling "
|
|
"is false since we don't know how to set it correctly in "
|
|
"Has(Attribute|State)DependentStyle");
|
|
|
|
nsPseudoClassList* partPseudo =
|
|
FindPseudoClass(aSelector, CSSPseudoClassType::part);
|
|
|
|
if (aNodeMatchContext.mIsFeatureless && !partPseudo &&
|
|
!CanMatchFeaturelessElement(aSelector)) {
|
|
return false;
|
|
}
|
|
|
|
Element* targetElement = aElement;
|
|
if (aTreeMatchContext.mForAssignedSlot) {
|
|
HTMLSlotElement* slot = aElement->GetAssignedSlot();
|
|
// We're likely testing the slottable's ancestors and it might
|
|
// not have an assigned slot, so return early.
|
|
if (!slot) {
|
|
return false;
|
|
}
|
|
targetElement = slot->AsElement();
|
|
}
|
|
|
|
if (partPseudo) {
|
|
if (!ElementMatchesPart(aElement, partPseudo)) {
|
|
return false;
|
|
}
|
|
|
|
nsIContent* partHostContent = aElement->GetContainingShadowHost();
|
|
if (!partHostContent || !partHostContent->IsElement()) {
|
|
return false;
|
|
}
|
|
|
|
Element* partHost = partHostContent->AsElement();
|
|
if (aTreeMatchContext.mScopedRoot) {
|
|
ShadowRoot* scopedShadow = aTreeMatchContext.mScopedRoot->GetShadowRoot();
|
|
if (!scopedShadow || partHost->GetContainingShadow() != scopedShadow) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
targetElement = partHost;
|
|
}
|
|
|
|
// namespace/tag match
|
|
// optimization : bail out early if we can
|
|
if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
|
|
targetElement->GetNameSpaceID() != aSelector->mNameSpace))
|
|
return false;
|
|
|
|
if (aSelector->mLowercaseTag) {
|
|
nsIAtom* selectorTag =
|
|
(aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement())
|
|
? aSelector->mLowercaseTag
|
|
: aSelector->mCasedTag;
|
|
if (selectorTag != targetElement->NodeInfo()->NameAtom()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
nsAtomList* IDList = aSelector->mIDList;
|
|
if (IDList) {
|
|
nsIAtom* id = targetElement->GetID();
|
|
if (id) {
|
|
// case sensitivity: bug 93371
|
|
const bool isCaseSensitive =
|
|
aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks;
|
|
|
|
if (isCaseSensitive) {
|
|
do {
|
|
if (IDList->mAtom != id) {
|
|
return false;
|
|
}
|
|
IDList = IDList->mNext;
|
|
} while (IDList);
|
|
} else {
|
|
// Use EqualsIgnoreASCIICase instead of full on unicode case conversion
|
|
// in order to save on performance. This is only used in quirks mode
|
|
// anyway.
|
|
nsDependentAtomString id1Str(id);
|
|
do {
|
|
if (!nsContentUtils::EqualsIgnoreASCIICase(
|
|
id1Str, nsDependentAtomString(IDList->mAtom))) {
|
|
return false;
|
|
}
|
|
IDList = IDList->mNext;
|
|
} while (IDList);
|
|
}
|
|
} else {
|
|
// Element has no id but we have an id selector
|
|
return false;
|
|
}
|
|
}
|
|
|
|
nsAtomList* classList = aSelector->mClassList;
|
|
if (classList) {
|
|
// test for class match
|
|
const nsAttrValue* elementClasses = targetElement->GetClasses();
|
|
if (!elementClasses) {
|
|
// Element has no classes but we have a class selector
|
|
return false;
|
|
}
|
|
|
|
// case sensitivity: bug 93371
|
|
const bool isCaseSensitive =
|
|
aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks;
|
|
|
|
while (classList) {
|
|
if (!elementClasses->Contains(
|
|
classList->mAtom, isCaseSensitive ? eCaseMatters : eIgnoreCase)) {
|
|
return false;
|
|
}
|
|
classList = classList->mNext;
|
|
}
|
|
}
|
|
|
|
const bool isOutsideShadowTree =
|
|
!!(aSelectorFlags & SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE);
|
|
const bool isNegated = (aDependence != nullptr);
|
|
// The selectors for which we set node bits are, unfortunately, early
|
|
// in this function (because they're pseudo-classes, which are
|
|
// generally quick to test, and thus earlier). If they were later,
|
|
// we'd probably avoid setting those bits in more cases where setting
|
|
// them is unnecessary.
|
|
NS_ASSERTION(aNodeMatchContext.mStateMask.IsEmpty() ||
|
|
!aTreeMatchContext.mForStyling,
|
|
"mForStyling must be false if we're just testing for "
|
|
"state-dependence");
|
|
|
|
// test for pseudo class match
|
|
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
|
|
pseudoClass;
|
|
pseudoClass = pseudoClass->mNext) {
|
|
auto idx = static_cast<CSSPseudoClassTypeBase>(pseudoClass->mType);
|
|
EventStates statesToCheck = nsCSSPseudoClasses::sPseudoClassStates[idx];
|
|
if (statesToCheck.IsEmpty()) {
|
|
// keep the cases here in the same order as the list in
|
|
// nsCSSPseudoClassList.h
|
|
switch (pseudoClass->mType) {
|
|
case CSSPseudoClassType::empty:
|
|
if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, true)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::mozOnlyWhitespace:
|
|
if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, false)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::mozEmptyExceptChildrenWithLocalname: {
|
|
NS_ASSERTION(pseudoClass->u.mString, "Must have string!");
|
|
nsIContent* child = nullptr;
|
|
int32_t index = -1;
|
|
|
|
if (aTreeMatchContext.mForStyling)
|
|
// FIXME: This isn't sufficient to handle:
|
|
// :-moz-empty-except-children-with-localname() + E
|
|
// :-moz-empty-except-children-with-localname() ~ E
|
|
// because we don't know to restyle the grandparent of the
|
|
// inserted/removed element (as in bug 534804 for :empty).
|
|
aElement->SetFlags(NODE_HAS_SLOW_SELECTOR);
|
|
do {
|
|
child = aElement->GetChildAt(++index);
|
|
} while (child &&
|
|
(!nsStyleUtil::IsSignificantChild(child, true, false) ||
|
|
(child->GetNameSpaceID() == aElement->GetNameSpaceID() &&
|
|
child->NodeInfo()->NameAtom()->Equals(
|
|
nsDependentString(pseudoClass->u.mString)))));
|
|
if (child != nullptr) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::lang: {
|
|
NS_ASSERTION(nullptr != pseudoClass->u.mString,
|
|
"null lang parameter");
|
|
if (!pseudoClass->u.mString || !*pseudoClass->u.mString) {
|
|
return false;
|
|
}
|
|
|
|
// We have to determine the language of the current element. Since
|
|
// this is currently no property and since the language is inherited
|
|
// from the parent we have to be prepared to look at all parent
|
|
// nodes. The language itself is encoded in the LANG attribute.
|
|
nsAutoString language;
|
|
if (aElement->GetLang(language)) {
|
|
if (!nsStyleUtil::DashMatchCompare(
|
|
language,
|
|
nsDependentString(pseudoClass->u.mString),
|
|
nsASCIICaseInsensitiveStringComparator())) {
|
|
return false;
|
|
}
|
|
// This pseudo-class matched; move on to the next thing
|
|
break;
|
|
}
|
|
|
|
nsIDocument* doc = aTreeMatchContext.mDocument;
|
|
if (doc) {
|
|
// Try to get the language from the HTTP header or if this
|
|
// is missing as well from the preferences.
|
|
// The content language can be a comma-separated list of
|
|
// language codes.
|
|
doc->GetContentLanguage(language);
|
|
|
|
nsDependentString langString(pseudoClass->u.mString);
|
|
language.StripWhitespace();
|
|
int32_t begin = 0;
|
|
int32_t len = language.Length();
|
|
while (begin < len) {
|
|
int32_t end = language.FindChar(char16_t(','), begin);
|
|
if (end == kNotFound) {
|
|
end = len;
|
|
}
|
|
if (nsStyleUtil::DashMatchCompare(
|
|
Substring(language, begin, end - begin),
|
|
langString,
|
|
nsASCIICaseInsensitiveStringComparator())) {
|
|
break;
|
|
}
|
|
begin = end + 1;
|
|
}
|
|
if (begin < len) {
|
|
// This pseudo-class matched
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case CSSPseudoClassType::mozBoundElement:
|
|
if (aTreeMatchContext.mScopedRoot != aElement) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::root:
|
|
if (aElement != aElement->OwnerDoc()->GetRootElement()) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::is:
|
|
case CSSPseudoClassType::matches:
|
|
case CSSPseudoClassType::any:
|
|
case CSSPseudoClassType::where: {
|
|
if (!SelectorListMatches(aElement,
|
|
pseudoClass,
|
|
aNodeMatchContext,
|
|
aTreeMatchContext,
|
|
true)) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozAny: {
|
|
// XXX: For compatibility, we retain :-moz-any()'s original behavior,
|
|
// which is to be unforgiving and reject complex selectors in
|
|
// its selector list argument.
|
|
if (!SelectorListMatches(aElement,
|
|
pseudoClass,
|
|
aNodeMatchContext,
|
|
aTreeMatchContext,
|
|
false,
|
|
true)) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozAnyPrivate: {
|
|
if (!SelectorListMatches(
|
|
aElement, pseudoClass, aNodeMatchContext, aTreeMatchContext)) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::slotted: {
|
|
if (aTreeMatchContext.mForAssignedSlot) {
|
|
aTreeMatchContext.mForAssignedSlot = false;
|
|
}
|
|
|
|
// Slottables cannot be matched from the outer tree.
|
|
if (isOutsideShadowTree) {
|
|
return false;
|
|
}
|
|
|
|
// Slot elements cannot be matched.
|
|
if (aElement->IsHTMLElement(nsGkAtoms::slot)) {
|
|
return false;
|
|
}
|
|
|
|
// The current element must have an assigned slot.
|
|
if (!aElement->GetAssignedSlot()) {
|
|
return false;
|
|
}
|
|
|
|
if (!SelectorListMatches(
|
|
aElement, pseudoClass, aNodeMatchContext, aTreeMatchContext)) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::part:
|
|
break;
|
|
|
|
case CSSPseudoClassType::host: {
|
|
ShadowRoot* shadow = aElement->GetShadowRoot();
|
|
// In order to match :host, the element must be a shadow root host,
|
|
// we must be matching only against host pseudo selectors, and the
|
|
// selector's context must be the shadow root (the selector must be
|
|
// featureless, the left-most selector, and be in a shadow root
|
|
// style).
|
|
if (!shadow || aSelector->HasFeatureSelectors() ||
|
|
isOutsideShadowTree) {
|
|
return false;
|
|
}
|
|
|
|
// Check if the element has the same shadow root.
|
|
if (aTreeMatchContext.mScopedRoot) {
|
|
if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Reject if the next selector is an explicit universal selector.
|
|
if (aSelector->mNext && aSelector->mNext->mExplicitUniversal) {
|
|
return false;
|
|
}
|
|
|
|
// The :host selector may also be be functional, with a compound
|
|
// selector. If this is the case, then also ensure that the host
|
|
// element matches against the compound selector.
|
|
if (!pseudoClass->u.mSelectorList) {
|
|
break;
|
|
}
|
|
|
|
// Match if any selector in the argument list matches.
|
|
// FIXME: What this effectively does is bypass the "featureless"
|
|
// selector check under SelectorMatches.
|
|
NodeMatchContext nodeContext(aNodeMatchContext.mStateMask,
|
|
aNodeMatchContext.mIsRelevantLink);
|
|
if (!SelectorListMatches(
|
|
aElement, pseudoClass, nodeContext, aTreeMatchContext)) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::hostContext: {
|
|
// In order to match host-context, the element must be a
|
|
// shadow root host and the selector's context must be the
|
|
// shadow root (aTreeMatchContext.mScopedRoot is set to the
|
|
// host of the shadow root where the style is contained,
|
|
// thus the element must be mScopedRoot). If the UNKNOWN
|
|
// selector flag is set, relax the shadow root host
|
|
// requirement because this pseudo class walks through
|
|
// ancestors looking for a match, thus the selector can be
|
|
// dependant on aElement even though it is not the host. The
|
|
// dependency would otherwise be missed because when UNKNOWN
|
|
// is set, selector matching may not have started from the top.
|
|
if (!((aElement->GetShadowRoot() &&
|
|
aElement == aTreeMatchContext.mScopedRoot) ||
|
|
aSelectorFlags & SelectorMatchesFlags::UNKNOWN)) {
|
|
return false;
|
|
}
|
|
|
|
Element* currentElement = aElement;
|
|
while (currentElement) {
|
|
NodeMatchContext nodeContext(
|
|
EventStates(), nsCSSRuleUtils::IsLink(currentElement));
|
|
if (SelectorListMatches(currentElement,
|
|
pseudoClass,
|
|
nodeContext,
|
|
aTreeMatchContext)) {
|
|
break;
|
|
}
|
|
|
|
nsIContent* flattenedParent =
|
|
currentElement->GetFlattenedTreeParent();
|
|
currentElement = flattenedParent && flattenedParent->IsElement()
|
|
? flattenedParent->AsElement()
|
|
: nullptr;
|
|
}
|
|
if (!currentElement) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::firstChild:
|
|
if (!edgeChildMatches(aElement, aTreeMatchContext, true, false)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::firstNode: {
|
|
nsIContent* firstNode = nullptr;
|
|
nsIContent* parent = aElement->GetParent();
|
|
if (parent) {
|
|
if (aTreeMatchContext.mForStyling)
|
|
parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR);
|
|
|
|
int32_t index = -1;
|
|
do {
|
|
firstNode = parent->GetChildAt(++index);
|
|
// stop at first non-comment and non-whitespace node
|
|
} while (firstNode &&
|
|
!nsStyleUtil::IsSignificantChild(firstNode, true, false));
|
|
}
|
|
if (aElement != firstNode) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::lastChild:
|
|
if (!edgeChildMatches(aElement, aTreeMatchContext, false, true)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::lastNode: {
|
|
nsIContent* lastNode = nullptr;
|
|
nsIContent* parent = aElement->GetParent();
|
|
if (parent) {
|
|
if (aTreeMatchContext.mForStyling)
|
|
parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR);
|
|
|
|
uint32_t index = parent->GetChildCount();
|
|
do {
|
|
lastNode = parent->GetChildAt(--index);
|
|
// stop at first non-comment and non-whitespace node
|
|
} while (lastNode &&
|
|
!nsStyleUtil::IsSignificantChild(lastNode, true, false));
|
|
}
|
|
if (aElement != lastNode) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::onlyChild:
|
|
if (!edgeChildMatches(aElement, aTreeMatchContext, true, true)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::firstOfType:
|
|
if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, false)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::lastOfType:
|
|
if (!edgeOfTypeMatches(aElement, aTreeMatchContext, false, true)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::onlyOfType:
|
|
if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, true)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::nthChild:
|
|
if (!nthChildGenericMatches(
|
|
aElement, aTreeMatchContext, pseudoClass, false, false)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::nthLastChild:
|
|
if (!nthChildGenericMatches(
|
|
aElement, aTreeMatchContext, pseudoClass, false, true)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::nthOfType:
|
|
if (!nthChildGenericMatches(
|
|
aElement, aTreeMatchContext, pseudoClass, true, false)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::nthLastOfType:
|
|
if (!nthChildGenericMatches(
|
|
aElement, aTreeMatchContext, pseudoClass, true, true)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::mozIsHTML:
|
|
if (!aTreeMatchContext.mIsHTMLDocument ||
|
|
!aElement->IsHTMLElement()) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::mozNativeAnonymous:
|
|
if (!aElement->IsInNativeAnonymousSubtree()) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::mozSystemMetric: {
|
|
nsCOMPtr<nsIAtom> metric = NS_Atomize(pseudoClass->u.mString);
|
|
if (!nsCSSRuleUtils::HasSystemMetric(metric)) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozLocaleDir: {
|
|
bool docIsRTL =
|
|
aTreeMatchContext.mDocument->GetDocumentState().HasState(
|
|
NS_DOCUMENT_STATE_RTL_LOCALE);
|
|
|
|
nsDependentString dirString(pseudoClass->u.mString);
|
|
|
|
if (dirString.EqualsLiteral("rtl")) {
|
|
if (!docIsRTL) {
|
|
return false;
|
|
}
|
|
} else if (dirString.EqualsLiteral("ltr")) {
|
|
if (docIsRTL) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// Selectors specifying other directions never match.
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozLWTheme: {
|
|
if (aTreeMatchContext.mDocument->GetDocumentLWTheme() <=
|
|
nsIDocument::Doc_Theme_None) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozLWThemeBrightText: {
|
|
if (aTreeMatchContext.mDocument->GetDocumentLWTheme() !=
|
|
nsIDocument::Doc_Theme_Bright) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozLWThemeDarkText: {
|
|
if (aTreeMatchContext.mDocument->GetDocumentLWTheme() !=
|
|
nsIDocument::Doc_Theme_Dark) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozWindowInactive:
|
|
if (!aTreeMatchContext.mDocument->GetDocumentState().HasState(
|
|
NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CSSPseudoClassType::mozTableBorderNonzero: {
|
|
if (!aElement->IsHTMLElement(nsGkAtoms::table)) {
|
|
return false;
|
|
}
|
|
const nsAttrValue* val = aElement->GetParsedAttr(nsGkAtoms::border);
|
|
if (!val || (val->Type() == nsAttrValue::eInteger &&
|
|
val->GetIntegerValue() == 0)) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozBrowserFrame: {
|
|
nsCOMPtr<nsIMozBrowserFrame> browserFrame =
|
|
do_QueryInterface(aElement);
|
|
if (!browserFrame || !browserFrame->GetReallyIsBrowser()) {
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::mozDir:
|
|
case CSSPseudoClassType::dir: {
|
|
if (aDependence) {
|
|
EventStates states =
|
|
nsCSSPseudoClasses::sPseudoClassStateDependences[static_cast<CSSPseudoClassTypeBase>(
|
|
pseudoClass->mType)];
|
|
if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) {
|
|
*aDependence = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If we only had to consider HTML, directionality would be
|
|
// exclusively LTR or RTL.
|
|
//
|
|
// However, in markup languages where there is no direction attribute
|
|
// we have to consider the possibility that neither dir(rtl) nor
|
|
// dir(ltr) matches.
|
|
EventStates state = aElement->StyleState();
|
|
nsDependentString dirString(pseudoClass->u.mString);
|
|
|
|
if (dirString.EqualsLiteral("rtl")) {
|
|
if (!state.HasState(NS_EVENT_STATE_RTL)) {
|
|
return false;
|
|
}
|
|
} else if (dirString.EqualsLiteral("ltr")) {
|
|
if (!state.HasState(NS_EVENT_STATE_LTR)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// Selectors specifying other directions never match.
|
|
return false;
|
|
}
|
|
} break;
|
|
|
|
case CSSPseudoClassType::scope:
|
|
if (aTreeMatchContext.mForScopedStyle) {
|
|
if (aTreeMatchContext.mCurrentStyleScope) {
|
|
// If mCurrentStyleScope is null, aElement must be the style
|
|
// scope root. This is because the PopStyleScopeForSelectorMatching
|
|
// call in SelectorMatchesTree sets mCurrentStyleScope to null
|
|
// as soon as we visit the style scope element, and we won't
|
|
// progress further up the tree after this call to
|
|
// SelectorMatches. Thus if mCurrentStyleScope is still set,
|
|
// we know the selector does not match.
|
|
return false;
|
|
}
|
|
} else if (aTreeMatchContext.HasSpecifiedScope()) {
|
|
if (!aTreeMatchContext.IsScopeElement(aElement)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (aElement != aElement->OwnerDoc()->GetRootElement()) {
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT(false, "How did that happen?");
|
|
}
|
|
} else {
|
|
if (!StateSelectorMatches(aElement,
|
|
aSelector,
|
|
aNodeMatchContext,
|
|
aTreeMatchContext,
|
|
aSelectorFlags,
|
|
aDependence,
|
|
statesToCheck)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool result = true;
|
|
if (aSelector->mAttrList) {
|
|
// test for attribute match
|
|
if (!targetElement->HasAttrs()) {
|
|
// if no attributes on the content, no match
|
|
return false;
|
|
} else {
|
|
result = true;
|
|
nsAttrSelector* attr = aSelector->mAttrList;
|
|
nsIAtom* matchAttribute;
|
|
|
|
do {
|
|
bool isHTML =
|
|
(aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement());
|
|
matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr;
|
|
if (attr->mNameSpace == kNameSpaceID_Unknown) {
|
|
// Attr selector with a wildcard namespace. We have to examine all
|
|
// the attributes on our content node.... This sort of selector is
|
|
// essentially a boolean OR, over all namespaces, of equivalent attr
|
|
// selectors with those namespaces. So to evaluate whether it
|
|
// matches, evaluate for each namespace (the only namespaces that
|
|
// have a chance at matching, of course, are ones that the element
|
|
// actually has attributes in), short-circuiting if we ever match.
|
|
result = false;
|
|
const nsAttrName* attrName;
|
|
for (uint32_t i = 0; (attrName = targetElement->GetAttrNameAt(i));
|
|
++i) {
|
|
if (attrName->LocalName() != matchAttribute) {
|
|
continue;
|
|
}
|
|
if (attr->mFunction == NS_ATTR_FUNC_SET) {
|
|
result = true;
|
|
} else {
|
|
nsAutoString value;
|
|
#ifdef DEBUG
|
|
bool hasAttr =
|
|
#endif
|
|
targetElement->GetAttr(
|
|
attrName->NamespaceID(), attrName->LocalName(), value);
|
|
NS_ASSERTION(hasAttr, "GetAttrNameAt lied");
|
|
result = AttrMatchesValue(attr, value, isHTML);
|
|
}
|
|
|
|
// At this point |result| has been set by us
|
|
// explicitly in this loop. If it's false, we may still match
|
|
// -- the content may have another attribute with the same name but
|
|
// in a different namespace. But if it's true, we are done (we
|
|
// can short-circuit the boolean OR described above).
|
|
if (result) {
|
|
break;
|
|
}
|
|
}
|
|
} else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) {
|
|
result = targetElement->AttrValueIs(
|
|
attr->mNameSpace,
|
|
matchAttribute,
|
|
attr->mValue,
|
|
attr->IsValueCaseSensitive(isHTML) ? eCaseMatters : eIgnoreCase);
|
|
} else if (!targetElement->HasAttr(attr->mNameSpace, matchAttribute)) {
|
|
result = false;
|
|
} else if (attr->mFunction != NS_ATTR_FUNC_SET) {
|
|
nsAutoString value;
|
|
#ifdef DEBUG
|
|
bool hasAttr =
|
|
#endif
|
|
targetElement->GetAttr(attr->mNameSpace, matchAttribute, value);
|
|
NS_ASSERTION(hasAttr, "HasAttr lied");
|
|
result = AttrMatchesValue(attr, value, isHTML);
|
|
}
|
|
|
|
attr = attr->mNext;
|
|
} while (attr && result);
|
|
}
|
|
}
|
|
|
|
// apply SelectorMatches to the negated selectors in the chain
|
|
if (!isNegated) {
|
|
for (nsCSSSelector* negation = aSelector->mNegations; result && negation;
|
|
negation = negation->mNegations) {
|
|
bool dependence = false;
|
|
result = !SelectorMatches(targetElement,
|
|
negation,
|
|
aNodeMatchContext,
|
|
aTreeMatchContext,
|
|
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT,
|
|
&dependence);
|
|
// If the selector does match due to the dependence on
|
|
// aNodeMatchContext.mStateMask, then we want to keep result true
|
|
// so that the final result of SelectorMatches is true. Doing so
|
|
// tells StateEnumFunc that there is a dependence on the state.
|
|
result = result || dependence;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#undef STATE_CHECK
|
|
|
|
#ifdef DEBUG
|
|
/* static */ bool
|
|
nsCSSRuleUtils::HasPseudoClassSelectorArgsWithCombinators(nsCSSSelector* aSelector)
|
|
{
|
|
for (nsPseudoClassList* p = aSelector->mPseudoClassList; p; p = p->mNext) {
|
|
if (nsCSSPseudoClasses::HasSelectorListArg(p->mType)) {
|
|
for (nsCSSSelectorList* l = p->u.mSelectorList; l; l = l->mNext) {
|
|
if (l->mSelectors->mNext) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (nsCSSSelector* n = aSelector->mNegations; n; n = n->mNegations) {
|
|
if (n->mNext) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::SelectorMatchesTree(Element* aPrevElement,
|
|
nsCSSSelector* aSelector,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
SelectorMatchesTreeFlags aFlags)
|
|
{
|
|
MOZ_ASSERT(!aSelector || !aSelector->IsPseudoElement());
|
|
nsCSSSelector* selector = aSelector;
|
|
Element* prevElement = aPrevElement;
|
|
bool crossedShadowRootBoundary = false;
|
|
while (selector) { // check compound selectors
|
|
bool contentIsFeatureless = false;
|
|
NS_ASSERTION(!selector->mNext || selector->mNext->mOperator != char16_t(0),
|
|
"compound selector without combinator");
|
|
|
|
// If after the previous selector match we are now outside the
|
|
// current style scope, we don't need to match any further.
|
|
if (aTreeMatchContext.mForScopedStyle &&
|
|
!aTreeMatchContext.IsWithinStyleScopeForSelectorMatching()) {
|
|
return false;
|
|
}
|
|
|
|
// for adjacent sibling combinators, the content to test against the
|
|
// selector is the previous sibling *element*
|
|
Element* element = nullptr;
|
|
if (char16_t('+') == selector->mOperator ||
|
|
char16_t('~') == selector->mOperator) {
|
|
// The relevant link must be an ancestor of the node being matched.
|
|
aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink);
|
|
nsIContent* parent = prevElement->GetParent();
|
|
// Operate on the flattened element tree when matching the
|
|
// ::slotted() pseudo-element.
|
|
if (aTreeMatchContext.mRestrictToSlottedPseudo) {
|
|
parent = prevElement->GetFlattenedTreeParent();
|
|
}
|
|
if (parent) {
|
|
if (aTreeMatchContext.mForStyling)
|
|
parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
|
|
|
|
element = prevElement->GetPreviousElementSibling();
|
|
}
|
|
}
|
|
// for descendant combinators and child combinators, the element
|
|
// to test against is the parent
|
|
else {
|
|
nsIContent* content = prevElement->GetParent();
|
|
// Operate on the flattened element tree when matching the
|
|
// ::slotted() pseudo-element.
|
|
if (aTreeMatchContext.mRestrictToSlottedPseudo) {
|
|
content = prevElement->GetFlattenedTreeParent();
|
|
}
|
|
|
|
// In the shadow tree, the shadow host behaves as if it
|
|
// is a featureless parent of top-level elements of the shadow
|
|
// tree. Only cross shadow root boundary when the selector is the
|
|
// left most selector because ancestors of the host are not in
|
|
// the selector match list.
|
|
ShadowRoot* shadowRoot =
|
|
content ? ShadowRoot::FromNode(content) : nullptr;
|
|
if (shadowRoot && !selector->mNext && !crossedShadowRootBoundary) {
|
|
content = shadowRoot->GetHost();
|
|
crossedShadowRootBoundary = true;
|
|
contentIsFeatureless = true;
|
|
}
|
|
|
|
// GetParent could return a document fragment; we only want
|
|
// element parents.
|
|
if (content && content->IsElement()) {
|
|
element = content->AsElement();
|
|
if (aTreeMatchContext.mForScopedStyle) {
|
|
// We are moving up to the parent element; tell the
|
|
// TreeMatchContext, so that in case this element is the
|
|
// style scope element, selector matching stops before we
|
|
// traverse further up the tree.
|
|
aTreeMatchContext.PopStyleScopeForSelectorMatching(element);
|
|
}
|
|
|
|
// Compatibility hack: First try matching this selector as though the
|
|
// <xbl:children> element wasn't in the tree to allow old selectors
|
|
// were written before <xbl:children> participated in CSS selector
|
|
// matching to work.
|
|
if (selector->mOperator == '>' && element->IsActiveChildrenElement()) {
|
|
Element* styleScope = aTreeMatchContext.mCurrentStyleScope;
|
|
if (SelectorMatchesTree(
|
|
element, selector, aTreeMatchContext, aFlags)) {
|
|
// It matched, don't try matching on the <xbl:children> element at
|
|
// all.
|
|
return true;
|
|
}
|
|
// We want to reset mCurrentStyleScope on aTreeMatchContext
|
|
// back to its state before the SelectorMatchesTree call, in
|
|
// case that call happens to traverse past the style scope element
|
|
// and sets it to null.
|
|
aTreeMatchContext.mCurrentStyleScope = styleScope;
|
|
}
|
|
}
|
|
}
|
|
if (!element) {
|
|
return false;
|
|
}
|
|
if ((aFlags & eMatchOnConditionalRestyleAncestor) &&
|
|
element->HasFlag(ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)) {
|
|
// If we're looking at an element that we already generated an
|
|
// eRestyle_SomeDescendants restyle hint for, then we should pretend
|
|
// that we matched here, because we don't know what the values of
|
|
// attributes on |element| were at the time we generated the
|
|
// eRestyle_SomeDescendants. This causes AttributeEnumFunc and
|
|
// HasStateDependentStyle below to generate a restyle hint for the
|
|
// change we're currently looking at, as we don't know whether the LHS
|
|
// of the selector we looked up matches or not. (We only pass in aFlags
|
|
// to cause us to look for eRestyle_SomeDescendants here under
|
|
// AttributeEnumFunc and HasStateDependentStyle.)
|
|
return true;
|
|
}
|
|
const bool isRelevantLink =
|
|
(aFlags & eLookForRelevantLink) && nsCSSRuleUtils::IsLink(element);
|
|
|
|
NodeMatchContext nodeContext(
|
|
EventStates(), isRelevantLink, contentIsFeatureless);
|
|
if (isRelevantLink) {
|
|
// If we find an ancestor of the matched node that is a link
|
|
// during the matching process, then it's the relevant link (see
|
|
// constructor call above).
|
|
// Since we are still matching against selectors that contain
|
|
// :visited (they'll just fail), we will always find such a node
|
|
// during the selector matching process if there is a relevant
|
|
// link that can influence selector matching.
|
|
aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink);
|
|
aTreeMatchContext.SetHaveRelevantLink();
|
|
}
|
|
if (SelectorMatches(element,
|
|
selector,
|
|
nodeContext,
|
|
aTreeMatchContext,
|
|
SelectorMatchesFlags::NONE)) {
|
|
// to avoid greedy matching, we need to recur if this is a
|
|
// descendant or general sibling combinator and the next
|
|
// combinator is different, but we can make an exception for
|
|
// sibling, then parent, since a sibling's parent is always the
|
|
// same.
|
|
if (NS_IS_GREEDY_OPERATOR(selector->mOperator) && selector->mNext &&
|
|
selector->mNext->mOperator != selector->mOperator &&
|
|
!(selector->mOperator == '~' &&
|
|
NS_IS_ANCESTOR_OPERATOR(selector->mNext->mOperator))) {
|
|
|
|
// pretend the selector didn't match, and step through content
|
|
// while testing the same selector
|
|
|
|
// This approach is slightly strange in that when it recurs
|
|
// it tests from the top of the content tree, down. This
|
|
// doesn't matter much for performance since most selectors
|
|
// don't match. (If most did, it might be faster...)
|
|
Element* styleScope = aTreeMatchContext.mCurrentStyleScope;
|
|
if (SelectorMatchesTree(element, selector, aTreeMatchContext, aFlags)) {
|
|
return true;
|
|
}
|
|
// We want to reset mCurrentStyleScope on aTreeMatchContext
|
|
// back to its state before the SelectorMatchesTree call, in
|
|
// case that call happens to traverse past the style scope element
|
|
// and sets it to null.
|
|
aTreeMatchContext.mCurrentStyleScope = styleScope;
|
|
}
|
|
selector = selector->mNext;
|
|
if (!selector && !aTreeMatchContext.mIsTopmostScope &&
|
|
aTreeMatchContext.mRestrictToSlottedPseudo &&
|
|
aTreeMatchContext.mScopedRoot != element) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// for adjacent sibling and child combinators, if we didn't find
|
|
// a match, we're done
|
|
if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) {
|
|
return false; // parent was required to match
|
|
}
|
|
}
|
|
prevElement = element;
|
|
}
|
|
return true; // all the selectors matched.
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::SelectorListMatches(Element* aElement,
|
|
nsCSSSelectorList* aList,
|
|
NodeMatchContext& aNodeMatchContext,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
SelectorMatchesFlags aSelectorFlags,
|
|
bool aIsForgiving,
|
|
bool aPreventComplexSelectors)
|
|
{
|
|
while (aList) {
|
|
nsCSSSelector* selector = aList->mSelectors;
|
|
// Forgiving selector lists are allowed to be empty, but they
|
|
// don't match anything.
|
|
if (!selector && aIsForgiving) {
|
|
return false;
|
|
}
|
|
NS_ASSERTION(selector, "Should have *some* selectors");
|
|
NS_ASSERTION(!selector->IsPseudoElement(), "Shouldn't have been called");
|
|
if (aPreventComplexSelectors) {
|
|
NS_ASSERTION(!selector->mNext, "Shouldn't have complex selectors");
|
|
}
|
|
if (SelectorMatches(aElement,
|
|
selector,
|
|
aNodeMatchContext,
|
|
aTreeMatchContext,
|
|
aSelectorFlags)) {
|
|
nsCSSSelector* next = selector->mNext;
|
|
SelectorMatchesTreeFlags selectorTreeFlags = SelectorMatchesTreeFlags(0);
|
|
// Try to look for the closest ancestor link element if we're processing
|
|
// the selector list argument of a pseudo-class, but only if for a new style context (see SelectorMatches).
|
|
if (!aNodeMatchContext.mIsRelevantLink && aTreeMatchContext.mForStyling &&
|
|
(aSelectorFlags & SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
|
|
selectorTreeFlags = eLookForRelevantLink;
|
|
}
|
|
|
|
if (!next || SelectorMatchesTree(
|
|
aElement, next, aTreeMatchContext, selectorTreeFlags)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
aList = aList->mNext;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* static */ bool
|
|
nsCSSRuleUtils::SelectorListMatches(Element* aElement,
|
|
nsPseudoClassList* aList,
|
|
NodeMatchContext& aNodeMatchContext,
|
|
TreeMatchContext& aTreeMatchContext,
|
|
bool aIsForgiving,
|
|
bool aPreventComplexSelectors)
|
|
{
|
|
return SelectorListMatches(aElement,
|
|
aList->u.mSelectorList,
|
|
aNodeMatchContext,
|
|
aTreeMatchContext,
|
|
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT,
|
|
aIsForgiving,
|
|
aPreventComplexSelectors);
|
|
}
|
|
|
|
// TreeMatchContext and AncestorFilter out of line methods
|
|
void
|
|
TreeMatchContext::InitAncestors(Element* aElement)
|
|
{
|
|
MOZ_ASSERT(!mAncestorFilter.mFilter);
|
|
MOZ_ASSERT(mAncestorFilter.mHashes.IsEmpty());
|
|
MOZ_ASSERT(mStyleScopes.IsEmpty());
|
|
|
|
mAncestorFilter.mFilter = new AncestorFilter::Filter();
|
|
|
|
if (MOZ_LIKELY(aElement)) {
|
|
MOZ_ASSERT(aElement->GetUncomposedDoc() ||
|
|
aElement->HasFlag(NODE_IS_IN_SHADOW_TREE),
|
|
"aElement must be in the document or in shadow tree "
|
|
"for the assumption that GetParentNode() is non-null "
|
|
"on all element ancestors of aElement to be true");
|
|
// Collect up the ancestors
|
|
AutoTArray<Element*, 50> ancestors;
|
|
Element* cur = aElement;
|
|
do {
|
|
ancestors.AppendElement(cur);
|
|
cur = cur->GetParentElementCrossingShadowRoot();
|
|
} while (cur);
|
|
|
|
// Now push them in reverse order.
|
|
for (uint32_t i = ancestors.Length(); i-- != 0;) {
|
|
mAncestorFilter.PushAncestor(ancestors[i]);
|
|
PushStyleScope(ancestors[i]);
|
|
PushShadowHost(ancestors[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TreeMatchContext::InitStyleScopes(Element* aElement)
|
|
{
|
|
MOZ_ASSERT(mStyleScopes.IsEmpty());
|
|
|
|
if (MOZ_LIKELY(aElement)) {
|
|
// Collect up the ancestors
|
|
AutoTArray<Element*, 50> ancestors;
|
|
Element* cur = aElement;
|
|
do {
|
|
ancestors.AppendElement(cur);
|
|
cur = cur->GetParentElementCrossingShadowRoot();
|
|
} while (cur);
|
|
|
|
// Now push them in reverse order.
|
|
for (uint32_t i = ancestors.Length(); i-- != 0;) {
|
|
PushStyleScope(ancestors[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AncestorFilter::PushAncestor(Element* aElement)
|
|
{
|
|
MOZ_ASSERT(mFilter);
|
|
|
|
uint32_t oldLength = mHashes.Length();
|
|
|
|
mPopTargets.AppendElement(oldLength);
|
|
#ifdef DEBUG
|
|
mElements.AppendElement(aElement);
|
|
#endif
|
|
mHashes.AppendElement(aElement->NodeInfo()->NameAtom()->hash());
|
|
nsIAtom* id = aElement->GetID();
|
|
if (id) {
|
|
mHashes.AppendElement(id->hash());
|
|
}
|
|
const nsAttrValue* classes = aElement->GetClasses();
|
|
if (classes) {
|
|
uint32_t classCount = classes->GetAtomCount();
|
|
for (uint32_t i = 0; i < classCount; ++i) {
|
|
mHashes.AppendElement(classes->AtomAt(i)->hash());
|
|
}
|
|
}
|
|
|
|
uint32_t newLength = mHashes.Length();
|
|
for (uint32_t i = oldLength; i < newLength; ++i) {
|
|
mFilter->add(mHashes[i]);
|
|
}
|
|
}
|
|
|
|
void
|
|
AncestorFilter::PopAncestor()
|
|
{
|
|
MOZ_ASSERT(!mPopTargets.IsEmpty());
|
|
MOZ_ASSERT(mPopTargets.Length() == mElements.Length());
|
|
|
|
uint32_t popTargetLength = mPopTargets.Length();
|
|
uint32_t newLength = mPopTargets[popTargetLength - 1];
|
|
|
|
mPopTargets.TruncateLength(popTargetLength - 1);
|
|
#ifdef DEBUG
|
|
mElements.TruncateLength(popTargetLength - 1);
|
|
#endif
|
|
|
|
uint32_t oldLength = mHashes.Length();
|
|
for (uint32_t i = newLength; i < oldLength; ++i) {
|
|
mFilter->remove(mHashes[i]);
|
|
}
|
|
mHashes.TruncateLength(newLength);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
AncestorFilter::AssertHasAllAncestors(Element* aElement) const
|
|
{
|
|
Element* cur = aElement->GetParentElementCrossingShadowRoot();
|
|
while (cur) {
|
|
MOZ_ASSERT(mElements.Contains(cur));
|
|
cur = cur->GetParentElementCrossingShadowRoot();
|
|
}
|
|
}
|
|
|
|
void
|
|
TreeMatchContext::AssertHasAllStyleScopes(Element* aElement) const
|
|
{
|
|
if (aElement->IsInNativeAnonymousSubtree()) {
|
|
// Document style sheets are never applied to native anonymous content,
|
|
// so it's not possible for them to be in a <style scoped> scope.
|
|
return;
|
|
}
|
|
Element* cur = aElement->GetParentElementCrossingShadowRoot();
|
|
while (cur) {
|
|
if (cur->IsScopedStyleRoot()) {
|
|
MOZ_ASSERT(mStyleScopes.Contains(cur));
|
|
}
|
|
cur = cur->GetParentElementCrossingShadowRoot();
|
|
}
|
|
}
|
|
#endif
|