Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2023-03-24 09:14:11 +08:00
commit 3298c8907d
21 changed files with 619 additions and 124 deletions

View file

@ -9,6 +9,7 @@
#include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/DocumentFragment.h"
#include "ChildIterator.h" #include "ChildIterator.h"
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsLayoutUtils.h"
#include "nsDOMClassInfoID.h" #include "nsDOMClassInfoID.h"
#include "nsIDOMHTMLElement.h" #include "nsIDOMHTMLElement.h"
#include "nsIStyleSheetLinkingElement.h" #include "nsIStyleSheetLinkingElement.h"
@ -162,6 +163,19 @@ ShadowRoot::AddSlot(HTMLSlotElement* aSlot)
oldSlot->RemoveAssignedNode(assignedNode); oldSlot->RemoveAssignedNode(assignedNode);
currentSlot->AppendAssignedNode(assignedNode); currentSlot->AppendAssignedNode(assignedNode);
Element* restyleElement;
if (assignedNode->IsElement()) {
restyleElement = assignedNode->AsElement();
} else {
// This is likely a text node. Use the host instead.
restyleElement = GetHost();
}
if (restyleElement) {
nsLayoutUtils::PostRestyleEvent(
restyleElement, eRestyle_Subtree, nsChangeHint(0));
}
doEnqueueSlotChange = true; doEnqueueSlotChange = true;
} }

View file

@ -8,6 +8,9 @@
</head> </head>
<body> <body>
<p id="display"></p> <p id="display"></p>
<p><input type="text" id="input"></p>
<p><input type="text" id="input_readonly" readonly></p>
<p><textarea id="textarea"></textarea></p>
<div id="content" style="display: none"> <div id="content" style="display: none">
</div> </div>
@ -293,10 +296,163 @@ function testSynthesizedKeyLocation()
window.removeEventListener("keyup", handler, true); window.removeEventListener("keyup", handler, true);
} }
// We're using TextEventDispatcher to decide if we should keypress event
// on content in the default event group. So, we can test if keypress
// event is NOT fired unexpectedly with synthesizeKey().
function testEnterKeyPressEvent()
{
let keydownFired, keypressFired, beforeinputFired;
function onEvent(aEvent) {
switch (aEvent.type) {
case "keydown":
keydownFired = true;
return;
case "keypress":
keypressFired = true;
return;
case "beforeinput":
beforeinputFired = true;
return;
}
}
for (let targetId of ["input", "textarea", "input_readonly"]) {
let target = document.getElementById(targetId);
function reset() {
keydownFired = keypressFired = beforeinputFired = false;
target.value = "";
}
target.addEventListener("keydown", onEvent);
target.addEventListener("keypress", onEvent);
target.addEventListener("beforeinput", onEvent);
const kDescription = "<" + targetId.replace("_", " ") + ">: ";
let isEditable = kDescription.includes("readonly");
let isTextarea = kDescription.includes("textarea");
target.focus();
reset();
synthesizeKey("KEY_Enter", {});
is(keydownFired, true,
kDescription + "keydown event should be fired when Enter key is pressed");
is(keypressFired, true,
kDescription + "keypress event should be fired when Enter key is pressed");
if (isEditable) {
todo_is(beforeinputFired, true,
kDescription + "beforeinput event should be fired when Enter key is pressed");
} else {
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Enter key is pressed");
}
if (isTextarea) {
is(target.value, "\n",
kDescription + "Enter key should cause inputting a line break in <textarea>");
} else {
is(target.value, "",
kDescription + "Enter key should not cause inputting a line break");
}
reset();
synthesizeKey("KEY_Enter", {shiftKey: true});
is(keydownFired, true,
kDescription + "keydown event should be fired when Shift + Enter key is pressed");
is(keypressFired, true,
kDescription + "keypress event should be fired when Shift + Enter key is pressed");
if (isEditable) {
todo_is(beforeinputFired, true,
kDescription + "beforeinput event should be fired when Shift + Enter key is pressed");
} else {
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Shift + Enter key is pressed");
}
if (isTextarea) {
is(target.value, "\n",
kDescription + "Shift + Enter key should cause inputting a line break in <textarea>");
} else {
is(target.value, "",
kDescription + "Shift + Enter key should not cause inputting a line break");
}
reset();
synthesizeKey("KEY_Enter", {ctrlKey: true});
is(keydownFired, true,
kDescription + "keydown event should be fired when Ctrl + Enter key is pressed");
is(keypressFired, true,
kDescription + "keypress event should be fired when Ctrl + Enter key is pressed");
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Ctrl + Enter key is pressed");
is(target.value, "",
kDescription + "Ctrl + Enter key should not cause inputting a line break");
reset();
synthesizeKey("KEY_Enter", {altKey: true});
is(keydownFired, true,
kDescription + "keydown event should be fired when Alt + Enter key is pressed");
is(keypressFired, !kStrictKeyPressEvents,
kDescription + "keypress event shouldn't be fired when Alt + Enter key is pressed in strict keypress dispatching mode");
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Alt + Enter key is pressed");
is(target.value, "",
kDescription + "Alt + Enter key should not cause inputting a line break");
reset();
synthesizeKey("KEY_Enter", {metaKey: true});
is(keydownFired, true,
kDescription + "keydown event should be fired when Meta + Enter key is pressed");
is(keypressFired, !kStrictKeyPressEvents,
kDescription + "keypress event shouldn't be fired when Meta + Enter key is pressed in strict keypress dispatching mode");
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Meta + Enter key is pressed");
is(target.value, "",
kDescription + "Meta + Enter key should not cause inputting a line break");
reset();
synthesizeKey("KEY_Enter", {shiftKey: true, ctrlKey: true});
is(keydownFired, true,
kDescription + "keydown event should be fired when Ctrl + Shift + Enter key is pressed");
is(keypressFired, !kStrictKeyPressEvents,
kDescription + "keypress event shouldn't be fired when Ctrl + Shift + Enter key is pressed in strict keypress dispatching mode");
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Ctrl + Shift + Enter key is pressed");
is(target.value, "",
kDescription + "Ctrl + Shift + Enter key should not cause inputting a line break");
reset();
synthesizeKey("KEY_Enter", {shiftKey: true, altKey: true});
is(keydownFired, true,
kDescription + "keydown event should be fired when Alt + Shift + Enter key is pressed");
is(keypressFired, !kStrictKeyPressEvents,
kDescription + "keypress event shouldn't be fired when Alt + Shift + Enter key is pressed in strict keypress dispatching mode");
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Alt + Shift + Enter key is pressed");
is(target.value, "",
kDescription + "Alt + Shift + Enter key should not cause inputting a line break");
reset();
synthesizeKey("KEY_Enter", {shiftKey: true, metaKey: true});
is(keydownFired, true,
kDescription + "keydown event should be fired when Meta + Shift + Enter key is pressed");
is(keypressFired, !kStrictKeyPressEvents,
kDescription + "keypress event shouldn't be fired when Meta + Shift + Enter key is pressed in strict keypress dispatching mode");
is(beforeinputFired, false,
kDescription + "beforeinput event shouldn't be fired when Meta + Shift + Enter key is pressed");
is(target.value, "",
kDescription + "Meta + Shift + Enter key should not cause inputting a line break");
target.removeEventListener("keydown", onEvent);
target.removeEventListener("keypress", onEvent);
target.removeEventListener("beforeinput", onEvent);
}
}
function runTests() function runTests()
{ {
testInitializingUntrustedEvent(); testInitializingUntrustedEvent();
testSynthesizedKeyLocation(); testSynthesizedKeyLocation();
testEnterKeyPressEvent();
SimpleTest.finish(); SimpleTest.finish();
} }

View file

@ -110,6 +110,13 @@ HTMLSlotElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
} }
} }
if (nsIContent* parent = GetParent()) {
if (parent->IsElement()) {
nsLayoutUtils::PostRestyleEvent(
parent->AsElement(), eRestyle_Subtree, nsChangeHint(0));
}
}
return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
aOldValue, aNotify); aOldValue, aNotify);
} }

View file

@ -681,6 +681,47 @@ nsBindingManager::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc,
aData->mElementIsFeatureless = false; aData->mElementIsFeatureless = false;
aData->mTreeMatchContext.mOnlyMatchHostPseudo = false; aData->mTreeMatchContext.mOnlyMatchHostPseudo = false;
// Walk the rules in shadow root for ::slotted() pseudo-element rules
// if we have an assigned slot.
if (aData->mElement->GetAssignedSlot()) {
aData->mTreeMatchContext.mRestrictToSlottedPseudo = true;
AutoTArray<nsXBLBinding*, 2> stack;
bool foundTopmostScope = false;
for (nsIContent* parent = aData->mElement->GetFlattenedTreeParent();
parent;
parent = parent->GetFlattenedTreeParent()) {
ShadowRoot* currentShadow = parent->GetShadowRoot();
if (!currentShadow) {
continue;
}
nsXBLBinding* binding = currentShadow->GetAssociatedBinding();
if (!binding) {
continue;
}
stack.AppendElement(binding);
if (!foundTopmostScope) {
aData->mTreeMatchContext.mScopedRoot = parent;
foundTopmostScope = true;
}
}
while (!stack.IsEmpty()) {
uint32_t index = stack.Length() - 1;
nsXBLBinding* binding = stack.ElementAt(index);
stack.RemoveElementAt(index);
aData->mTreeMatchContext.mForAssignedSlot = true;
aData->mTreeMatchContext.mIsTopmostScope = (index == 0);
binding->WalkRules(aFunc, aData);
}
aData->mTreeMatchContext.mForAssignedSlot = false;
aData->mTreeMatchContext.mRestrictToSlottedPseudo = false;
}
// Walk the binding scope chain, starting with the binding attached to our // Walk the binding scope chain, starting with the binding attached to our
// content, up till we run out of scopes or we get cut off. // content, up till we run out of scopes or we get cut off.
nsIContent *content = aData->mElement; nsIContent *content = aData->mElement;

View file

@ -3839,8 +3839,13 @@ RestyleManager::ComputeAndProcessStyleChange(nsStyleContext* aNewContext,
MOZ_ASSERT(mReframingStyleContexts, "should have rsc"); MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
MOZ_ASSERT(aNewContext->StyleDisplay()->mDisplay == StyleDisplay::Contents); MOZ_ASSERT(aNewContext->StyleDisplay()->mDisplay == StyleDisplay::Contents);
nsIFrame* frame = GetNearestAncestorFrame(aElement); nsIFrame* frame = GetNearestAncestorFrame(aElement);
MOZ_ASSERT(frame, "display:contents node in map although it's a " // Return early if we don't have a frame.
"display:none descendant?"); if (!frame) {
NS_ASSERTION(frame,
"display:contents node in map although it's a "
"display:none descendant?");
return;
}
TreeMatchContext treeMatchContext(true, TreeMatchContext treeMatchContext(true,
nsRuleWalker::eRelevantLinkUnvisited, nsRuleWalker::eRelevantLinkUnvisited,
frame->PresContext()->Document()); frame->PresContext()->Document());

View file

@ -318,7 +318,8 @@ nsCSSSelector::nsCSSSelector(void)
mNext(nullptr), mNext(nullptr),
mNameSpace(kNameSpaceID_Unknown), mNameSpace(kNameSpaceID_Unknown),
mOperator(0), mOperator(0),
mPseudoType(CSSPseudoElementType::NotPseudo) mPseudoType(CSSPseudoElementType::NotPseudo),
mHybridPseudoType(CSSPseudoElementType::NotPseudo)
{ {
MOZ_COUNT_CTOR(nsCSSSelector); MOZ_COUNT_CTOR(nsCSSSelector);
} }
@ -915,6 +916,11 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
// Append each pseudo-class in the linked list // Append each pseudo-class in the linked list
for (nsPseudoClassList* list = mPseudoClassList; list; list = list->mNext) { for (nsPseudoClassList* list = mPseudoClassList; list; list = list->mNext) {
// Serialize pseudo-elements that were treated as if they were a
// pseudo-class to the two colon syntax.
if (nsCSSPseudoClasses::IsHybridPseudoElement(list->mType)) {
aString.Append(char16_t(':'));
}
nsCSSPseudoClasses::PseudoTypeToString(list->mType, temp); nsCSSPseudoClasses::PseudoTypeToString(list->mType, temp);
// This should not be escaped since (a) the pseudo-class string // This should not be escaped since (a) the pseudo-class string
// has a ":" that can't be escaped and (b) all pseudo-classes at // has a ":" that can't be escaped and (b) all pseudo-classes at

View file

@ -177,6 +177,10 @@ public:
return mLowercaseTag && !mCasedTag; return mLowercaseTag && !mCasedTag;
} }
inline bool IsHybridPseudoElement() const {
return HybridPseudoType() != mozilla::CSSPseudoElementType::NotPseudo;
}
// Calculate the specificity of this selector (not including its mNext!). // Calculate the specificity of this selector (not including its mNext!).
int32_t CalcWeight() const; int32_t CalcWeight() const;
@ -218,6 +222,12 @@ public:
void SetPseudoType(mozilla::CSSPseudoElementType aType) { void SetPseudoType(mozilla::CSSPseudoElementType aType) {
mPseudoType = aType; mPseudoType = aType;
} }
mozilla::CSSPseudoElementType HybridPseudoType() const {
return mHybridPseudoType;
}
void SetHybridPseudoType(mozilla::CSSPseudoElementType aType) {
mHybridPseudoType = aType;
}
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
@ -241,6 +251,7 @@ private:
// The underlying type of CSSPseudoElementType is uint8_t and // The underlying type of CSSPseudoElementType is uint8_t and
// it packs well with mOperator. (char16_t + uint8_t is less than 32bits.) // it packs well with mOperator. (char16_t + uint8_t is less than 32bits.)
mozilla::CSSPseudoElementType mPseudoType; mozilla::CSSPseudoElementType mPseudoType;
mozilla::CSSPseudoElementType mHybridPseudoType;
nsCSSSelector(const nsCSSSelector& aCopy) = delete; nsCSSSelector(const nsCSSSelector& aCopy) = delete;
nsCSSSelector& operator=(const nsCSSSelector& aCopy) = delete; nsCSSSelector& operator=(const nsCSSSelector& aCopy) = delete;

View file

@ -119,7 +119,8 @@ enum class SelectorParsingFlags {
eIsForgiving = 1 << 1, eIsForgiving = 1 << 1,
eDisallowCombinators = 1 << 2, eDisallowCombinators = 1 << 2,
eDisallowPseudoElements = 1 << 3, eDisallowPseudoElements = 1 << 3,
eInheritNamespace = 1 << 4 eInheritNamespace = 1 << 4,
eForceEmptyList = 1 << 5
}; };
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorParsingFlags) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorParsingFlags)
@ -776,7 +777,7 @@ protected:
// aFlags is not set. // aFlags is not set.
nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask, nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask,
nsCSSSelector& aSelector, nsCSSSelector& aSelector,
SelectorParsingFlags aFlags, SelectorParsingFlags& aFlags,
nsIAtom** aPseudoElement, nsIAtom** aPseudoElement,
nsAtomList** aPseudoElementArgs, nsAtomList** aPseudoElementArgs,
CSSPseudoElementType* aPseudoElementType); CSSPseudoElementType* aPseudoElementType);
@ -784,9 +785,9 @@ protected:
nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask, nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask,
nsCSSSelector& aSelector); nsCSSSelector& aSelector);
nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask, nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector, nsCSSSelector& aSelector,
SelectorParsingFlags aFlags); SelectorParsingFlags& aFlags);
nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector, nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType); CSSPseudoClassType aType);
@ -796,22 +797,22 @@ protected:
nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector, nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType, CSSPseudoClassType aType,
SelectorParsingFlags aFlags); SelectorParsingFlags& aFlags);
nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask, nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector, nsCSSSelector& aSelector,
SelectorParsingFlags aFlags); SelectorParsingFlags& aFlags);
// If aStopChar is non-zero, the selector list is done when we hit // If aStopChar is non-zero, the selector list is done when we hit
// aStopChar. Otherwise, it's done when we hit EOF. // aStopChar. Otherwise, it's done when we hit EOF.
bool ParseSelectorList(nsCSSSelectorList*& aListHead, bool ParseSelectorList(nsCSSSelectorList*& aListHead,
char16_t aStopChar, char16_t aStopChar,
SelectorParsingFlags aFlags = SelectorParsingFlags::eNone); SelectorParsingFlags& aFlags);
bool ParseSelectorGroup(nsCSSSelectorList*& aListHead, bool ParseSelectorGroup(nsCSSSelectorList*& aListHead,
SelectorParsingFlags aFlags); SelectorParsingFlags& aFlags);
bool ParseSelector(nsCSSSelectorList* aList, bool ParseSelector(nsCSSSelectorList* aList,
char16_t aPrevCombinator, char16_t aPrevCombinator,
SelectorParsingFlags aFlags); SelectorParsingFlags& aFlags);
enum { enum {
eParseDeclaration_InBraces = 1 << 0, eParseDeclaration_InBraces = 1 << 0,
@ -2351,7 +2352,8 @@ CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString,
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr); InitScanner(scanner, reporter, aURI, aURI, nullptr);
bool success = ParseSelectorList(*aSelectorList, char16_t(0)); SelectorParsingFlags flags = SelectorParsingFlags::eNone;
bool success = ParseSelectorList(*aSelectorList, char16_t(0), flags);
// We deliberately do not call OUTPUT_ERROR here, because all our // We deliberately do not call OUTPUT_ERROR here, because all our
// callers map a failure return to a JS exception, and if that JS // callers map a failure return to a JS exception, and if that JS
@ -5465,9 +5467,10 @@ CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
{ {
// First get the list of selectors for the rule // First get the list of selectors for the rule
nsCSSSelectorList* slist = nullptr; nsCSSSelectorList* slist = nullptr;
SelectorParsingFlags flags = SelectorParsingFlags::eNone;
uint32_t linenum, colnum; uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) || if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!ParseSelectorList(slist, char16_t('{'))) { !ParseSelectorList(slist, char16_t('{'), flags)) {
REPORT_UNEXPECTED(PEBadSelectorRSIgnored); REPORT_UNEXPECTED(PEBadSelectorRSIgnored);
OUTPUT_ERROR(); OUTPUT_ERROR();
SkipRuleSet(aInsideBraces); SkipRuleSet(aInsideBraces);
@ -5504,7 +5507,7 @@ CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
bool bool
CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead, CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
char16_t aStopChar, char16_t aStopChar,
SelectorParsingFlags aFlags) SelectorParsingFlags& aFlags)
{ {
nsCSSSelectorList* list = nullptr; nsCSSSelectorList* list = nullptr;
if (! ParseSelectorGroup(list, aFlags)) { if (! ParseSelectorGroup(list, aFlags)) {
@ -5588,7 +5591,7 @@ static bool IsUniversalSelector(const nsCSSSelector& aSelector)
bool bool
CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList, CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList,
SelectorParsingFlags aFlags) SelectorParsingFlags& aFlags)
{ {
char16_t combinator = 0; char16_t combinator = 0;
nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList()); nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList());
@ -5689,9 +5692,9 @@ CSSParserImpl::ParseClassSelector(int32_t& aDataMask,
// namespace|type or namespace|* or *|* or * // namespace|type or namespace|* or *|* or *
// //
CSSParserImpl::nsSelectorParsingStatus CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask, CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector, nsCSSSelector& aSelector,
SelectorParsingFlags aFlags) SelectorParsingFlags& aFlags)
{ {
nsAutoString buffer; nsAutoString buffer;
if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace
@ -6056,7 +6059,7 @@ CSSParserImpl::ParseAttributeSelector(int32_t& aDataMask,
CSSParserImpl::nsSelectorParsingStatus CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask, CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
nsCSSSelector& aSelector, nsCSSSelector& aSelector,
SelectorParsingFlags aFlags, SelectorParsingFlags& aFlags,
nsIAtom** aPseudoElement, nsIAtom** aPseudoElement,
nsAtomList** aPseudoElementArgs, nsAtomList** aPseudoElementArgs,
CSSPseudoElementType* aPseudoElementType) CSSPseudoElementType* aPseudoElementType)
@ -6103,10 +6106,14 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
CSSEnabledState enabledState = EnabledState(); CSSEnabledState enabledState = EnabledState();
CSSPseudoElementType pseudoElementType = CSSPseudoElementType pseudoElementType =
nsCSSPseudoElements::GetPseudoType(pseudo, enabledState); nsCSSPseudoElements::GetPseudoType(pseudo, enabledState);
bool pseudoElementIsTreeAbiding =
nsCSSPseudoElements::IsTreeAbidingPseudoElement(pseudoElementType);
CSSPseudoClassType pseudoClassType = CSSPseudoClassType pseudoClassType =
nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState); nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState);
bool pseudoClassIsUserAction = bool pseudoClassIsUserAction =
nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType); nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType);
bool pseudoClassHasForgivingSelectorListArg =
nsCSSPseudoClasses::HasForgivingSelectorListArg(pseudoClassType);
if (nsCSSAnonBoxes::IsNonElement(pseudo)) { if (nsCSSAnonBoxes::IsNonElement(pseudo)) {
// Non-element anonymous boxes should not match any rule. // Non-element anonymous boxes should not match any rule.
@ -6127,6 +6134,23 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
} }
} }
// We handle certain pseudo-elements as if they were a pseudo-class.
// Our platform does not have the mechanism to handle multiple
// pseudo-elements and proper storage if they have an argument.
CSSPseudoElementType hybridPseudoElementType =
CSSPseudoElementType::NotPseudo;
if (parsingPseudoElement &&
nsCSSPseudoElements::IsHybridPseudoElement(pseudoElementType)) {
hybridPseudoElementType = pseudoElementType;
pseudoElementType = CSSPseudoElementType::NotPseudo;
parsingPseudoElement = false;
if (hybridPseudoElementType == CSSPseudoElementType::slotted) {
pseudoClassType = CSSPseudoClassType::slotted;
aFlags |= SelectorParsingFlags::eDisallowCombinators;
}
}
#ifdef MOZ_XUL #ifdef MOZ_XUL
isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree); isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree);
// If a tree pseudo-element is using the function syntax, it will // If a tree pseudo-element is using the function syntax, it will
@ -6186,33 +6210,74 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
return eSelectorParsingStatus_Error; return eSelectorParsingStatus_Error;
} }
if (aSelector.IsPseudoElement()) { bool forceEmptyList = false;
CSSPseudoElementType type = aSelector.PseudoType(); if (aSelector.IsPseudoElement() || aSelector.IsHybridPseudoElement()) {
CSSPseudoElementType type = aSelector.IsPseudoElement() ?
aSelector.PseudoType() :
aSelector.HybridPseudoType();
bool supportsTreeAbiding =
nsCSSPseudoElements::PseudoElementSupportsTreeAbiding(type);
bool supportsUserAction =
nsCSSPseudoElements::PseudoElementSupportsUserActionState(type);
if (type >= CSSPseudoElementType::Count || if (type >= CSSPseudoElementType::Count ||
!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) { (!supportsTreeAbiding && !supportsUserAction)) {
// We only allow user action pseudo-classes on certain pseudo-elements. // We only allow user action pseudo-classes and/or tree-abiding
// pseudo-elements on certain pseudo-elements.
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC); REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
UngetToken(); UngetToken();
return eSelectorParsingStatus_Error; return eSelectorParsingStatus_Error;
} }
if (!isPseudoClass || !pseudoClassIsUserAction) {
// CSS 4 Selectors says that pseudo-elements can only be followed by if (isPseudoClass) {
// a user action pseudo-class. if (pseudoClassHasForgivingSelectorListArg) {
// XXX: Pseudo-classes with a forgiving selector list argument are
// allowed to follow a pseudo-element, but must treat any selector
// that is not of the same type as invalid. It doesn't make any
// sense, but that's the behavior of other tainted browsers.
forceEmptyList = true;
} else if (!supportsUserAction || !pseudoClassIsUserAction) {
// CSS 4 Selectors says that pseudo-elements can only be followed by
// a user action pseudo-class.
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
UngetToken();
return eSelectorParsingStatus_Error;
}
} else if (isPseudoElement &&
(!supportsTreeAbiding || !pseudoElementIsTreeAbiding)) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction); REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
UngetToken(); UngetToken();
return eSelectorParsingStatus_Error; return eSelectorParsingStatus_Error;
} }
} }
bool disallowPseudoElements =
!!(aFlags & SelectorParsingFlags::eDisallowPseudoElements);
if (!parsingPseudoElement && isPseudoClass) { if (!parsingPseudoElement && isPseudoClass) {
aDataMask |= SEL_MASK_PCLASS; aDataMask |= SEL_MASK_PCLASS;
// Only pseudo-classes with a forgiving selector list argument
// are allowed if we're forced to be empty.
if ((aFlags & SelectorParsingFlags::eForceEmptyList) &&
!pseudoClassHasForgivingSelectorListArg) {
if (eCSSToken_Function == mToken.mType) {
SkipUntil(')');
}
return eSelectorParsingStatus_Continue;
}
if (eCSSToken_Function == mToken.mType) { if (eCSSToken_Function == mToken.mType) {
nsSelectorParsingStatus parsingStatus; nsSelectorParsingStatus parsingStatus;
// Only the combinators restriction should be passed down the chain.
SelectorParsingFlags flags = // Pass only a few parsing flags down the chain.
(aFlags & SelectorParsingFlags::eDisallowCombinators) ? SelectorParsingFlags flags = SelectorParsingFlags::eNone;
SelectorParsingFlags::eDisallowCombinators : if (aFlags & SelectorParsingFlags::eDisallowCombinators) {
SelectorParsingFlags::eNone; flags |= SelectorParsingFlags::eDisallowCombinators;
}
if (aFlags & SelectorParsingFlags::eForceEmptyList ||
forceEmptyList) {
flags |= SelectorParsingFlags::eForceEmptyList;
}
if (sLegacyNegationPseudoClassEnabled && if (sLegacyNegationPseudoClassEnabled &&
CSSPseudoClassType::negation == pseudoClassType) { CSSPseudoClassType::negation == pseudoClassType) {
// :not() can't be itself negated // :not() can't be itself negated
@ -6228,6 +6293,13 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
return parsingStatus; return parsingStatus;
} }
} }
else if (nsCSSPseudoClasses::IsHybridPseudoElement(pseudoClassType) &&
hybridPseudoElementType == CSSPseudoElementType::NotPseudo) {
// Reject the single colon syntax for hybrid pseudo-elements.
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly);
UngetToken();
return eSelectorParsingStatus_Error;
}
else if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) { else if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
parsingStatus = parsingStatus =
ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
@ -6239,6 +6311,14 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
else { else {
MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType), MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
"unexpected pseudo with function token"); "unexpected pseudo with function token");
if (hybridPseudoElementType != CSSPseudoElementType::NotPseudo) {
aSelector.SetHybridPseudoType(hybridPseudoElementType);
// Ensure hybrid pseudo-elements are rejected if they're not allowed.
if (disallowPseudoElements) {
UngetToken();
return eSelectorParsingStatus_Error;
}
}
parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector, parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector,
pseudoClassType, pseudoClassType,
flags); flags);
@ -6265,7 +6345,7 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
} }
// Pseudo-elements might not be allowed from appearing // Pseudo-elements might not be allowed from appearing
// (e.g. as an argument to the functional part of a pseudo-class). // (e.g. as an argument to the functional part of a pseudo-class).
if (aFlags & SelectorParsingFlags::eDisallowPseudoElements) { if (disallowPseudoElements) {
UngetToken(); UngetToken();
return eSelectorParsingStatus_Error; return eSelectorParsingStatus_Error;
} }
@ -6344,9 +6424,9 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
// Parse the argument of a negation pseudo-class :not() // Parse the argument of a negation pseudo-class :not()
// //
CSSParserImpl::nsSelectorParsingStatus CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask, CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector, nsCSSSelector& aSelector,
SelectorParsingFlags aFlags) SelectorParsingFlags& aFlags)
{ {
aFlags |= SelectorParsingFlags::eIsNegated; aFlags |= SelectorParsingFlags::eIsNegated;
@ -6624,7 +6704,7 @@ CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
CSSParserImpl::nsSelectorParsingStatus CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector, CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType, CSSPseudoClassType aType,
SelectorParsingFlags aFlags) SelectorParsingFlags& aFlags)
{ {
bool isSingleSelector = bool isSingleSelector =
nsCSSPseudoClasses::HasSingleSelectorArg(aType); nsCSSPseudoClasses::HasSingleSelectorArg(aType);
@ -6692,7 +6772,7 @@ CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
bool bool
CSSParserImpl::ParseSelector(nsCSSSelectorList* aList, CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
char16_t aPrevCombinator, char16_t aPrevCombinator,
SelectorParsingFlags aFlags) SelectorParsingFlags& aFlags)
{ {
if (! GetToken(true)) { if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESelectorEOF); REPORT_UNEXPECTED_EOF(PESelectorEOF);
@ -6727,7 +6807,8 @@ CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
selector->mClassList = pseudoElementArgs.forget(); selector->mClassList = pseudoElementArgs.forget();
selector->SetPseudoType(pseudoElementType); selector->SetPseudoType(pseudoElementType);
} }
} else if (selector->IsPseudoElement()) { } else if (selector->IsPseudoElement() ||
selector->IsHybridPseudoElement()) {
// Once we parsed a pseudo-element, we can only parse // Once we parsed a pseudo-element, we can only parse
// pseudo-classes (and only a limited set, which // pseudo-classes (and only a limited set, which
// ParsePseudoSelector knows how to handle). // ParsePseudoSelector knows how to handle).
@ -6761,6 +6842,14 @@ CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
} }
} }
// Treat every other selector as invalid.
if ((aFlags & SelectorParsingFlags::eForceEmptyList) &&
(selector->mIDList || selector->mClassList ||
selector->mAttrList || selector->mNegations ||
!selector->mPseudoClassList)) {
return false;
}
if (parsingStatus == eSelectorParsingStatus_Error) { if (parsingStatus == eSelectorParsingStatus_Error) {
return false; return false;
} }

View file

@ -93,6 +93,9 @@ CSS_PSEUDO_CLASS(nthLastChild, ":nth-last-child", 0, "")
CSS_PSEUDO_CLASS(nthOfType, ":nth-of-type", 0, "") CSS_PSEUDO_CLASS(nthOfType, ":nth-of-type", 0, "")
CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type", 0, "") CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type", 0, "")
// Match slot nodes.
CSS_PSEUDO_CLASS(slotted, ":slotted", 0, "layout.css.slotted-pseudo.enabled")
// Match nodes that are HTML but not XHTML // Match nodes that are HTML but not XHTML
CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "") CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "")

View file

@ -106,7 +106,8 @@ bool
nsCSSPseudoClasses::HasSingleSelectorArg(Type aType) nsCSSPseudoClasses::HasSingleSelectorArg(Type aType)
{ {
return aType == Type::host || return aType == Type::host ||
aType == Type::hostContext; aType == Type::hostContext ||
aType == Type::slotted;
} }
bool bool
@ -126,7 +127,8 @@ nsCSSPseudoClasses::HasSelectorListArg(Type aType)
aType == Type::mozAny || aType == Type::mozAny ||
aType == Type::mozAnyPrivate || aType == Type::mozAnyPrivate ||
aType == Type::host || aType == Type::host ||
aType == Type::hostContext; aType == Type::hostContext ||
aType == Type::slotted;
} }
bool bool
@ -169,3 +171,9 @@ nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType)
aType == Type::active || aType == Type::active ||
aType == Type::focus; aType == Type::focus;
} }
/* static */ bool
nsCSSPseudoClasses::IsHybridPseudoElement(Type aType)
{
return aType == Type::slotted;
}

View file

@ -64,6 +64,7 @@ public:
static bool HasOptionalSelectorListArg(Type aType); static bool HasOptionalSelectorListArg(Type aType);
static bool IsHiddenFromSerialization(Type aType); static bool IsHiddenFromSerialization(Type aType);
static bool IsUserActionPseudoClass(Type aType); static bool IsUserActionPseudoClass(Type aType);
static bool IsHybridPseudoElement(Type aType);
// Should only be used on types other than Count and NotPseudoClass // Should only be used on types other than Count and NotPseudoClass
static void PseudoTypeToString(Type aType, nsAString& aString); static void PseudoTypeToString(Type aType, nsAString& aString);

View file

@ -28,6 +28,11 @@
CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2) CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2)
CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2) CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2)
// XXX: ::slotted() is treated as if it were a pseudo-class, and
// is never parsed as a pseudo-element.
CSS_PSEUDO_ELEMENT(slotted, ":slotted",
CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING)
CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0) CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0)
CSS_PSEUDO_ELEMENT(firstLetter, ":first-letter", CSS_PSEUDO_ELEMENT(firstLetter, ":first-letter",

View file

@ -75,6 +75,22 @@ nsCSSPseudoElements::IsCSS2PseudoElement(nsIAtom *aAtom)
return result; return result;
} }
/* static */ bool
nsCSSPseudoElements::IsHybridPseudoElement(CSSPseudoElementType aType)
{
return aType == CSSPseudoElementType::slotted;
}
/* static */ bool
nsCSSPseudoElements::IsTreeAbidingPseudoElement(CSSPseudoElementType aType)
{
// TODO: ::marker should be added here once we have support for it.
return aType == CSSPseudoElementType::after ||
aType == CSSPseudoElementType::before ||
aType == CSSPseudoElementType::mozPlaceholder ||
aType == CSSPseudoElementType::placeholder;
}
/* static */ CSSPseudoElementType /* static */ CSSPseudoElementType
nsCSSPseudoElements::GetPseudoType(nsIAtom *aAtom, EnabledState aEnabledState) nsCSSPseudoElements::GetPseudoType(nsIAtom *aAtom, EnabledState aEnabledState)
{ {

View file

@ -40,6 +40,10 @@
// API for creating pseudo-implementing native anonymous content in JS with this // API for creating pseudo-implementing native anonymous content in JS with this
// pseudo-element? // pseudo-element?
#define CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC (1<<5) #define CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC (1<<5)
// Is this pseudo-element a pseudo-element that supports a tree-abiding
// pseudo-element following it, such as ::after or ::before? See
// https://w3c.github.io/csswg-drafts/css-pseudo-4/#tree-abiding.
#define CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING (1<<6)
namespace mozilla { namespace mozilla {
@ -80,6 +84,10 @@ public:
static bool IsCSS2PseudoElement(nsIAtom *aAtom); static bool IsCSS2PseudoElement(nsIAtom *aAtom);
static bool IsHybridPseudoElement(Type aType);
static bool IsTreeAbidingPseudoElement(Type aType);
#define CSS_PSEUDO_ELEMENT(_name, _value, _flags) \ #define CSS_PSEUDO_ELEMENT(_name, _value, _flags) \
static nsICSSPseudoElement* _name; static nsICSSPseudoElement* _name;
#include "nsCSSPseudoElementList.h" #include "nsCSSPseudoElementList.h"
@ -107,6 +115,12 @@ public:
return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC); return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC);
} }
static bool PseudoElementSupportsTreeAbiding(const Type aType)
{
return PseudoElementHasFlags(aType,
CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING);
}
static bool IsEnabled(Type aType, EnabledState aEnabledState) static bool IsEnabled(Type aType, EnabledState aEnabledState)
{ {
return !PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY) || return !PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY) ||

View file

@ -48,6 +48,7 @@
#include "nsCSSRules.h" #include "nsCSSRules.h"
#include "nsStyleSet.h" #include "nsStyleSet.h"
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/ShadowRoot.h"
#include "nsNthIndexCache.h" #include "nsNthIndexCache.h"
#include "mozilla/ArrayUtils.h" #include "mozilla/ArrayUtils.h"
@ -684,6 +685,7 @@ void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcesso
filter->AssertHasAllAncestors(aElement); filter->AssertHasAllAncestors(aElement);
} }
#endif #endif
bool isForAssignedSlot = aData->mTreeMatchContext.mForAssignedSlot;
// Merge the lists while there are still multiple lists to merge. // Merge the lists while there are still multiple lists to merge.
while (valueCount > 1) { while (valueCount > 1) {
int32_t valueIndex = 0; int32_t valueIndex = 0;
@ -696,6 +698,7 @@ void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcesso
} }
} }
const RuleValue *cur = mEnumList[valueIndex].mCurValue; const RuleValue *cur = mEnumList[valueIndex].mCurValue;
aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot;
ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter); ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter);
cur++; cur++;
if (cur == mEnumList[valueIndex].mEnd) { if (cur == mEnumList[valueIndex].mEnd) {
@ -709,6 +712,7 @@ void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcesso
for (const RuleValue *value = mEnumList[0].mCurValue, for (const RuleValue *value = mEnumList[0].mCurValue,
*end = mEnumList[0].mEnd; *end = mEnumList[0].mEnd;
value != end; ++value) { value != end; ++value) {
aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot;
ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter); ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter);
} }
} }
@ -1380,8 +1384,9 @@ enum class SelectorMatchesFlags : uint8_t {
// pseudo-element. // pseudo-element.
IS_PSEUDO_CLASS_ARGUMENT = 1 << 2, IS_PSEUDO_CLASS_ARGUMENT = 1 << 2,
// The selector should be blocked from matching the :host pseudo-class. // The selector should be blocked from matching because it is called
IS_HOST_INACCESSIBLE = 1 << 3 // from outside the shadow tree.
IS_OUTSIDE_SHADOW_TREE = 1 << 3
}; };
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorMatchesFlags) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorMatchesFlags)
@ -1393,13 +1398,14 @@ static inline bool ActiveHoverQuirkMatches(nsCSSSelector* aSelector,
if (aSelector->HasTagSelector() || aSelector->mAttrList || if (aSelector->HasTagSelector() || aSelector->mAttrList ||
aSelector->mIDList || aSelector->mClassList || aSelector->mIDList || aSelector->mClassList ||
aSelector->IsPseudoElement() || aSelector->IsPseudoElement() ||
aSelector->IsHybridPseudoElement() ||
// Having this quirk means that some selectors will no longer match, // 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 // so it's better to return false when we aren't sure (i.e., the
// flags are unknown). // flags are unknown).
aSelectorFlags & (SelectorMatchesFlags::UNKNOWN | aSelectorFlags & (SelectorMatchesFlags::UNKNOWN |
SelectorMatchesFlags::HAS_PSEUDO_ELEMENT | SelectorMatchesFlags::HAS_PSEUDO_ELEMENT |
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT | SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT |
SelectorMatchesFlags::IS_HOST_INACCESSIBLE)) { SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE)) {
return false; return false;
} }
@ -1725,24 +1731,29 @@ static bool SelectorMatches(Element* aElement,
return false; return false;
} }
Element* targetElement = aElement;
if (aTreeMatchContext.mForAssignedSlot) {
targetElement = aElement->GetAssignedSlot()->AsElement();
}
// namespace/tag match // namespace/tag match
// optimization : bail out early if we can // optimization : bail out early if we can
if ((kNameSpaceID_Unknown != aSelector->mNameSpace && if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
aElement->GetNameSpaceID() != aSelector->mNameSpace)) targetElement->GetNameSpaceID() != aSelector->mNameSpace))
return false; return false;
if (aSelector->mLowercaseTag) { if (aSelector->mLowercaseTag) {
nsIAtom* selectorTag = nsIAtom* selectorTag =
(aTreeMatchContext.mIsHTMLDocument && aElement->IsHTMLElement()) ? (aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement()) ?
aSelector->mLowercaseTag : aSelector->mCasedTag; aSelector->mLowercaseTag : aSelector->mCasedTag;
if (selectorTag != aElement->NodeInfo()->NameAtom()) { if (selectorTag != targetElement->NodeInfo()->NameAtom()) {
return false; return false;
} }
} }
nsAtomList* IDList = aSelector->mIDList; nsAtomList* IDList = aSelector->mIDList;
if (IDList) { if (IDList) {
nsIAtom* id = aElement->GetID(); nsIAtom* id = targetElement->GetID();
if (id) { if (id) {
// case sensitivity: bug 93371 // case sensitivity: bug 93371
const bool isCaseSensitive = const bool isCaseSensitive =
@ -1777,7 +1788,7 @@ static bool SelectorMatches(Element* aElement,
nsAtomList* classList = aSelector->mClassList; nsAtomList* classList = aSelector->mClassList;
if (classList) { if (classList) {
// test for class match // test for class match
const nsAttrValue *elementClasses = aElement->GetClasses(); const nsAttrValue *elementClasses = targetElement->GetClasses();
if (!elementClasses) { if (!elementClasses) {
// Element has no classes but we have a class selector // Element has no classes but we have a class selector
return false; return false;
@ -1797,6 +1808,8 @@ static bool SelectorMatches(Element* aElement,
} }
} }
const bool isOutsideShadowTree =
!!(aSelectorFlags & SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE);
const bool isNegated = (aDependence != nullptr); const bool isNegated = (aDependence != nullptr);
// The selectors for which we set node bits are, unfortunately, early // The selectors for which we set node bits are, unfortunately, early
// in this function (because they're pseudo-classes, which are // in this function (because they're pseudo-classes, which are
@ -1963,6 +1976,38 @@ static bool SelectorMatches(Element* aElement,
} }
break; 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;
}
NodeMatchContext nodeContext(EventStates(),
aNodeMatchContext.mIsRelevantLink);
if (!SelectorListMatches(aElement,
pseudoClass,
nodeContext,
aTreeMatchContext)) {
return false;
}
}
break;
case CSSPseudoClassType::host: case CSSPseudoClassType::host:
{ {
ShadowRoot* shadow = aElement->GetShadowRoot(); ShadowRoot* shadow = aElement->GetShadowRoot();
@ -1973,7 +2018,7 @@ static bool SelectorMatches(Element* aElement,
// style). // style).
if (!shadow || if (!shadow ||
aSelector->HasFeatureSelectors() || aSelector->HasFeatureSelectors() ||
aSelectorFlags & SelectorMatchesFlags::IS_HOST_INACCESSIBLE) { isOutsideShadowTree) {
return false; return false;
} }
@ -2338,7 +2383,7 @@ static bool SelectorMatches(Element* aElement,
bool result = true; bool result = true;
if (aSelector->mAttrList) { if (aSelector->mAttrList) {
// test for attribute match // test for attribute match
if (!aElement->HasAttrs()) { if (!targetElement->HasAttrs()) {
// if no attributes on the content, no match // if no attributes on the content, no match
return false; return false;
} else { } else {
@ -2348,7 +2393,7 @@ static bool SelectorMatches(Element* aElement,
do { do {
bool isHTML = bool isHTML =
(aTreeMatchContext.mIsHTMLDocument && aElement->IsHTMLElement()); (aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement());
matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr; matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr;
if (attr->mNameSpace == kNameSpaceID_Unknown) { if (attr->mNameSpace == kNameSpaceID_Unknown) {
// Attr selector with a wildcard namespace. We have to examine all // Attr selector with a wildcard namespace. We have to examine all
@ -2360,7 +2405,7 @@ static bool SelectorMatches(Element* aElement,
// actually has attributes in), short-circuiting if we ever match. // actually has attributes in), short-circuiting if we ever match.
result = false; result = false;
const nsAttrName* attrName; const nsAttrName* attrName;
for (uint32_t i = 0; (attrName = aElement->GetAttrNameAt(i)); ++i) { for (uint32_t i = 0; (attrName = targetElement->GetAttrNameAt(i)); ++i) {
if (attrName->LocalName() != matchAttribute) { if (attrName->LocalName() != matchAttribute) {
continue; continue;
} }
@ -2371,7 +2416,7 @@ static bool SelectorMatches(Element* aElement,
#ifdef DEBUG #ifdef DEBUG
bool hasAttr = bool hasAttr =
#endif #endif
aElement->GetAttr(attrName->NamespaceID(), targetElement->GetAttr(attrName->NamespaceID(),
attrName->LocalName(), value); attrName->LocalName(), value);
NS_ASSERTION(hasAttr, "GetAttrNameAt lied"); NS_ASSERTION(hasAttr, "GetAttrNameAt lied");
result = AttrMatchesValue(attr, value, isHTML); result = AttrMatchesValue(attr, value, isHTML);
@ -2389,12 +2434,12 @@ static bool SelectorMatches(Element* aElement,
} }
else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) { else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) {
result = result =
aElement-> targetElement->
AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue, AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue,
attr->IsValueCaseSensitive(isHTML) ? eCaseMatters attr->IsValueCaseSensitive(isHTML) ? eCaseMatters
: eIgnoreCase); : eIgnoreCase);
} }
else if (!aElement->HasAttr(attr->mNameSpace, matchAttribute)) { else if (!targetElement->HasAttr(attr->mNameSpace, matchAttribute)) {
result = false; result = false;
} }
else if (attr->mFunction != NS_ATTR_FUNC_SET) { else if (attr->mFunction != NS_ATTR_FUNC_SET) {
@ -2402,7 +2447,7 @@ static bool SelectorMatches(Element* aElement,
#ifdef DEBUG #ifdef DEBUG
bool hasAttr = bool hasAttr =
#endif #endif
aElement->GetAttr(attr->mNameSpace, matchAttribute, value); targetElement->GetAttr(attr->mNameSpace, matchAttribute, value);
NS_ASSERTION(hasAttr, "HasAttr lied"); NS_ASSERTION(hasAttr, "HasAttr lied");
result = AttrMatchesValue(attr, value, isHTML); result = AttrMatchesValue(attr, value, isHTML);
} }
@ -2417,7 +2462,7 @@ static bool SelectorMatches(Element* aElement,
for (nsCSSSelector *negation = aSelector->mNegations; for (nsCSSSelector *negation = aSelector->mNegations;
result && negation; negation = negation->mNegations) { result && negation; negation = negation->mNegations) {
bool dependence = false; bool dependence = false;
result = !SelectorMatches(aElement, negation, aNodeMatchContext, result = !SelectorMatches(targetElement, negation, aNodeMatchContext,
aTreeMatchContext, aTreeMatchContext,
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT, SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT,
&dependence); &dependence);
@ -2542,6 +2587,11 @@ SelectorMatchesTree(Element* aPrevElement,
// The relevant link must be an ancestor of the node being matched. // The relevant link must be an ancestor of the node being matched.
aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink); aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink);
nsIContent* parent = prevElement->GetParent(); 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 (parent) {
if (aTreeMatchContext.mForStyling) if (aTreeMatchContext.mForStyling)
parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
@ -2552,7 +2602,12 @@ SelectorMatchesTree(Element* aPrevElement,
// for descendant combinators and child combinators, the element // for descendant combinators and child combinators, the element
// to test against is the parent // to test against is the parent
else { else {
nsIContent *content = prevElement->GetParent(); 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 // In the shadow tree, the shadow host behaves as if it
// is a featureless parent of top-level elements of the shadow // is a featureless parent of top-level elements of the shadow
@ -2560,11 +2615,12 @@ SelectorMatchesTree(Element* aPrevElement,
// left most selector because ancestors of the host are not in // left most selector because ancestors of the host are not in
// the selector match list. // the selector match list.
ShadowRoot* shadowRoot = content ? ShadowRoot* shadowRoot = content ?
ShadowRoot::FromNode(content) : nullptr; ShadowRoot::FromNode(content) :
nullptr;
if (shadowRoot && !selector->mNext && !crossedShadowRootBoundary) { if (shadowRoot && !selector->mNext && !crossedShadowRootBoundary) {
content = shadowRoot->GetHost(); content = shadowRoot->GetHost();
crossedShadowRootBoundary = true; crossedShadowRootBoundary = true;
contentIsFeatureless = true; contentIsFeatureless = true;
} }
// GetParent could return a document fragment; we only want // GetParent could return a document fragment; we only want
@ -2662,6 +2718,12 @@ SelectorMatchesTree(Element* aPrevElement,
aTreeMatchContext.mCurrentStyleScope = styleScope; aTreeMatchContext.mCurrentStyleScope = styleScope;
} }
selector = selector->mNext; selector = selector->mNext;
if (!selector &&
!aTreeMatchContext.mIsTopmostScope &&
aTreeMatchContext.mRestrictToSlottedPseudo &&
aTreeMatchContext.mScopedRoot != element) {
return false;
}
} }
else { else {
// for adjacent sibling and child combinators, if we didn't find // for adjacent sibling and child combinators, if we didn't find
@ -2740,6 +2802,52 @@ static bool SelectorListMatches(Element* aElement,
aPreventComplexSelectors); aPreventComplexSelectors);
} }
static
inline bool LookForTargetPseudo(nsCSSSelector* aSelector,
TreeMatchContext* aMatchContext,
nsRestyleHint* possibleChange) {
if (aMatchContext->mOnlyMatchHostPseudo) {
while (aSelector && aSelector->mNext != nullptr) {
aSelector = aSelector->mNext;
}
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
if (possibleChange) {
// :host-context will walk ancestors looking for a match of a
// compound selector, thus any changes to ancestors may require
// restyling the subtree.
*possibleChange |= eRestyle_Subtree;
}
return true;
}
}
return false;
}
else if (aMatchContext->mRestrictToSlottedPseudo) {
for (nsCSSSelector* selector = aSelector;
selector;
selector = selector->mNext) {
if (!selector->mPseudoClassList) {
continue;
}
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::slotted) {
return true;
}
}
}
return false;
}
// We're not restricted to a specific pseudo-class.
return true;
}
static inline static inline
void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector, void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext, ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext,
@ -2748,35 +2856,19 @@ void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
if (nodeContext.mIsRelevantLink) { if (nodeContext.mIsRelevantLink) {
data->mTreeMatchContext.SetHaveRelevantLink(); data->mTreeMatchContext.SetHaveRelevantLink();
} }
if (ancestorFilter && // XXX: Ignore the ancestor filter if we're testing the assigned slot.
bool useAncestorFilter = !(data->mTreeMatchContext.mForAssignedSlot);
if (useAncestorFilter && ancestorFilter &&
!ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>( !ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>(
value.mAncestorSelectorHashes)) { value.mAncestorSelectorHashes)) {
// We won't match; nothing else to do here // We won't match; nothing else to do here
return; return;
} }
// If mOnlyMatchHostPseudo is set, then we only want to match against
// selectors that contain a :host-context pseudo class.
if (data->mTreeMatchContext.mOnlyMatchHostPseudo) {
nsCSSSelector* selector = aSelector;
while (selector && selector->mNext != nullptr) {
selector = selector->mNext;
}
bool seenHostPseudo = false; if (!LookForTargetPseudo(aSelector, &data->mTreeMatchContext, nullptr)) {
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList; return;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
seenHostPseudo = true;
break;
}
}
if (!seenHostPseudo) {
return;
}
} }
if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement, if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement,
data->mScope)) { data->mScope)) {
// The selector is for a rule in a scoped style sheet, and the subject // The selector is for a rule in a scoped style sheet, and the subject
@ -2838,7 +2930,13 @@ nsCSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData)
NodeMatchContext nodeContext(EventStates(), NodeMatchContext nodeContext(EventStates(),
nsCSSRuleProcessor::IsLink(aData->mElement), nsCSSRuleProcessor::IsLink(aData->mElement),
aData->mElementIsFeatureless); aData->mElementIsFeatureless);
cascade->mRuleHash.EnumerateAllRules(aData->mElement, aData, nodeContext); // Test against the assigned slot rather than the slottable if we're
// matching the ::slotted() pseudo.
Element* targetElement = aData->mElement;
if (aData->mTreeMatchContext.mForAssignedSlot) {
targetElement = aData->mElement->GetAssignedSlot()->AsElement();
}
cascade->mRuleHash.EnumerateAllRules(targetElement, aData, nodeContext);
} }
} }
@ -3121,35 +3219,11 @@ AttributeEnumFunc(nsCSSSelector* aSelector,
nsRestyleHint possibleChange = nsRestyleHint possibleChange =
RestyleHintForSelectorWithAttributeChange(aData->change, RestyleHintForSelectorWithAttributeChange(aData->change,
aSelector, aRightmostSelector); aSelector, aRightmostSelector);
// If mOnlyMatchHostPseudo is set, then we only want to match against
// selectors that contain a :host-context pseudo class.
if (data->mTreeMatchContext.mOnlyMatchHostPseudo) {
nsCSSSelector* selector = aSelector;
while (selector && selector->mNext != nullptr) {
selector = selector->mNext;
}
bool seenHostPseudo = false; if (!LookForTargetPseudo(aSelector, &data->mTreeMatchContext, &possibleChange)) {
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList; return;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
// :host-context will walk ancestors looking for a match of a compound
// selector, thus any changes to ancestors may require restyling the
// subtree.
possibleChange |= eRestyle_Subtree;
seenHostPseudo = true;
break;
}
}
if (!seenHostPseudo) {
return;
}
} }
// If, ignoring eRestyle_SomeDescendants, enumData->change already includes // If, ignoring eRestyle_SomeDescendants, enumData->change already includes
// all the bits of possibleChange, don't bother calling SelectorMatches, since // all the bits of possibleChange, don't bother calling SelectorMatches, since
// even if it returns false enumData->change won't change. If possibleChange // even if it returns false enumData->change won't change. If possibleChange
@ -4187,7 +4261,7 @@ nsCSSRuleProcessor::RestrictedSelectorListMatches(Element* aElement,
NodeMatchContext nodeContext(EventStates(), false); NodeMatchContext nodeContext(EventStates(), false);
SelectorMatchesFlags flags = aElement->IsInShadowTree() ? SelectorMatchesFlags flags = aElement->IsInShadowTree() ?
SelectorMatchesFlags::NONE : SelectorMatchesFlags::NONE :
SelectorMatchesFlags::IS_HOST_INACCESSIBLE; SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE;
return SelectorListMatches(aElement, return SelectorListMatches(aElement,
aSelectorList, aSelectorList,
nodeContext, nodeContext,

View file

@ -371,6 +371,9 @@ struct MOZ_STACK_CLASS TreeMatchContext {
// match. // match.
bool mOnlyMatchHostPseudo; bool mOnlyMatchHostPseudo;
// Restrict matching to selectors that contain a :slotted() pseudo-class.
bool mRestrictToSlottedPseudo;
// Root of scoped stylesheet (set and unset by the supplier of the // Root of scoped stylesheet (set and unset by the supplier of the
// scoped stylesheet). // scoped stylesheet).
nsIContent* mScopedRoot; nsIContent* mScopedRoot;
@ -403,6 +406,13 @@ struct MOZ_STACK_CLASS TreeMatchContext {
// for an HTML5 scoped style sheet. // for an HTML5 scoped style sheet.
bool mForScopedStyle; bool mForScopedStyle;
// Whether we're currently in the topmost scope for shadow DOM.
bool mIsTopmostScope;
// Whether we're testing for the assigned slot instead of the slottable
// when matching type/class/ID/attribute.
bool mForAssignedSlot;
enum MatchVisited { enum MatchVisited {
eNeverMatchVisited, eNeverMatchVisited,
eMatchVisitedDefault eMatchVisitedDefault
@ -429,6 +439,7 @@ struct MOZ_STACK_CLASS TreeMatchContext {
, mVisitedHandling(aVisitedHandling) , mVisitedHandling(aVisitedHandling)
, mDocument(aDocument) , mDocument(aDocument)
, mOnlyMatchHostPseudo(false) , mOnlyMatchHostPseudo(false)
, mRestrictToSlottedPseudo(false)
, mScopedRoot(nullptr) , mScopedRoot(nullptr)
, mIsHTMLDocument(aDocument->IsHTMLDocument()) , mIsHTMLDocument(aDocument->IsHTMLDocument())
, mCompatMode(aDocument->GetCompatibilityMode()) , mCompatMode(aDocument->GetCompatibilityMode())
@ -436,6 +447,8 @@ struct MOZ_STACK_CLASS TreeMatchContext {
, mSkippingParentDisplayBasedStyleFixup(false) , mSkippingParentDisplayBasedStyleFixup(false)
, mForScopedStyle(false) , mForScopedStyle(false)
, mCurrentStyleScope(nullptr) , mCurrentStyleScope(nullptr)
, mIsTopmostScope(false)
, mForAssignedSlot(false)
{ {
if (aMatchVisited != eNeverMatchVisited) { if (aMatchVisited != eNeverMatchVisited) {
nsILoadContext* loadContext = mDocument->GetLoadContext(); nsILoadContext* loadContext = mDocument->GetLoadContext();

View file

@ -893,3 +893,9 @@ rtc > rt {
ruby, rb, rt, rtc { ruby, rb, rt, rtc {
unicode-bidi: isolate; unicode-bidi: isolate;
} }
/* Shadow DOM v1
* https://drafts.csswg.org/css-scoping/#slots-in-shadow-tree */
slot {
display: contents;
}

View file

@ -474,9 +474,3 @@ div:-moz-native-anonymous.moz-custom-content-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
/* Shadow DOM v1
* https://drafts.csswg.org/css-scoping/#slots-in-shadow-tree */
slot {
display: contents;
}

View file

@ -2562,6 +2562,9 @@ pref("layout.css.legacy-negation-pseudo.enabled", false);
// Is support for the :is() and :where() selectors enabled? // Is support for the :is() and :where() selectors enabled?
pref("layout.css.is-where-pseudo.enabled", true); pref("layout.css.is-where-pseudo.enabled", true);
// Is support for the ::slotted() selector enabled?
pref("layout.css.slotted-pseudo.enabled", true);
// Is support for the :scope selector enabled? // Is support for the :scope selector enabled?
pref("layout.css.scope-pseudo.enabled", true); pref("layout.css.scope-pseudo.enabled", true);

View file

@ -538,8 +538,7 @@ TextEventDispatcher::DispatchKeyboardEventInternal(
if (!sDispatchKeyPressEventNonPrintableInContent && if (!sDispatchKeyPressEventNonPrintableInContent &&
keyEvent.mMessage == eKeyPress && keyEvent.mMessage == eKeyPress &&
!keyEvent.IsInputtingText() && !keyEvent.ShouldKeyPressEventBeFiredOnContent()) {
!keyEvent.IsInputtingLineBreak()) {
keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true; keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true;
} }

View file

@ -207,6 +207,36 @@ public:
MODIFIER_OS)); MODIFIER_OS));
} }
/**
* ShouldKeyPressEventBeFiredOnContent() should be called only when the
* instance is eKeyPress event. This returns true when the eKeyPress
* event should be fired even on content in the default event group.
*/
bool ShouldKeyPressEventBeFiredOnContent() const
{
MOZ_DIAGNOSTIC_ASSERT(mMessage == eKeyPress);
// Case 1: Inputting text or a line break: always fire keypress event.
if (IsInputtingText() || IsInputtingLineBreak()) {
return true;
}
// Case 2: Ctrl + Enter
// Ctrl + Enter won't cause actual input in our editor so should not fire the event if people would be consistent
// in setting rules for themselves (foreshadowing... ;P).
// However, the other browsers fire the keypress event in this case (but not with other modifiers).
// So, for compatibility with them, we should fire the keypress event for Ctrl + Enter too.
if (mMessage == eKeyPress &&
mKeyNameIndex == KEY_NAME_INDEX_Enter &&
!(mModifiers & (MODIFIER_ALT |
MODIFIER_META |
MODIFIER_OS |
MODIFIER_SHIFT))) {
return true;
}
// Default: dont't fire in the default event group.
return false;
}
virtual WidgetEvent* Duplicate() const override virtual WidgetEvent* Duplicate() const override
{ {
MOZ_ASSERT(mClass == eKeyboardEventClass, MOZ_ASSERT(mClass == eKeyboardEventClass,