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 "ChildIterator.h"
#include "nsContentUtils.h"
#include "nsLayoutUtils.h"
#include "nsDOMClassInfoID.h"
#include "nsIDOMHTMLElement.h"
#include "nsIStyleSheetLinkingElement.h"
@ -162,6 +163,19 @@ ShadowRoot::AddSlot(HTMLSlotElement* aSlot)
oldSlot->RemoveAssignedNode(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;
}

View file

@ -8,6 +8,9 @@
</head>
<body>
<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>
@ -293,10 +296,163 @@ function testSynthesizedKeyLocation()
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()
{
testInitializingUntrustedEvent();
testSynthesizedKeyLocation();
testEnterKeyPressEvent();
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,
aOldValue, aNotify);
}

View file

@ -681,6 +681,47 @@ nsBindingManager::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc,
aData->mElementIsFeatureless = 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
// content, up till we run out of scopes or we get cut off.
nsIContent *content = aData->mElement;

View file

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

View file

@ -318,7 +318,8 @@ nsCSSSelector::nsCSSSelector(void)
mNext(nullptr),
mNameSpace(kNameSpaceID_Unknown),
mOperator(0),
mPseudoType(CSSPseudoElementType::NotPseudo)
mPseudoType(CSSPseudoElementType::NotPseudo),
mHybridPseudoType(CSSPseudoElementType::NotPseudo)
{
MOZ_COUNT_CTOR(nsCSSSelector);
}
@ -915,6 +916,11 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
// Append each pseudo-class in the linked list
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);
// This should not be escaped since (a) the pseudo-class string
// has a ":" that can't be escaped and (b) all pseudo-classes at

View file

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

View file

@ -119,7 +119,8 @@ enum class SelectorParsingFlags {
eIsForgiving = 1 << 1,
eDisallowCombinators = 1 << 2,
eDisallowPseudoElements = 1 << 3,
eInheritNamespace = 1 << 4
eInheritNamespace = 1 << 4,
eForceEmptyList = 1 << 5
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorParsingFlags)
@ -776,7 +777,7 @@ protected:
// aFlags is not set.
nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags aFlags,
SelectorParsingFlags& aFlags,
nsIAtom** aPseudoElement,
nsAtomList** aPseudoElementArgs,
CSSPseudoElementType* aPseudoElementType);
@ -784,9 +785,9 @@ protected:
nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask,
nsCSSSelector& aSelector);
nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags aFlags);
nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags& aFlags);
nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType);
@ -796,22 +797,22 @@ protected:
nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType,
SelectorParsingFlags aFlags);
SelectorParsingFlags& aFlags);
nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags aFlags);
nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags& aFlags);
// If aStopChar is non-zero, the selector list is done when we hit
// aStopChar. Otherwise, it's done when we hit EOF.
bool ParseSelectorList(nsCSSSelectorList*& aListHead,
char16_t aStopChar,
SelectorParsingFlags aFlags = SelectorParsingFlags::eNone);
SelectorParsingFlags& aFlags);
bool ParseSelectorGroup(nsCSSSelectorList*& aListHead,
SelectorParsingFlags aFlags);
SelectorParsingFlags& aFlags);
bool ParseSelector(nsCSSSelectorList* aList,
char16_t aPrevCombinator,
SelectorParsingFlags aFlags);
SelectorParsingFlags& aFlags);
enum {
eParseDeclaration_InBraces = 1 << 0,
@ -2351,7 +2352,8 @@ CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString,
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
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
// 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
nsCSSSelectorList* slist = nullptr;
SelectorParsingFlags flags = SelectorParsingFlags::eNone;
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!ParseSelectorList(slist, char16_t('{'))) {
!ParseSelectorList(slist, char16_t('{'), flags)) {
REPORT_UNEXPECTED(PEBadSelectorRSIgnored);
OUTPUT_ERROR();
SkipRuleSet(aInsideBraces);
@ -5504,7 +5507,7 @@ CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
bool
CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
char16_t aStopChar,
SelectorParsingFlags aFlags)
SelectorParsingFlags& aFlags)
{
nsCSSSelectorList* list = nullptr;
if (! ParseSelectorGroup(list, aFlags)) {
@ -5588,7 +5591,7 @@ static bool IsUniversalSelector(const nsCSSSelector& aSelector)
bool
CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList,
SelectorParsingFlags aFlags)
SelectorParsingFlags& aFlags)
{
char16_t combinator = 0;
nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList());
@ -5689,9 +5692,9 @@ CSSParserImpl::ParseClassSelector(int32_t& aDataMask,
// namespace|type or namespace|* or *|* or *
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags aFlags)
CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags& aFlags)
{
nsAutoString buffer;
if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace
@ -6056,7 +6059,7 @@ CSSParserImpl::ParseAttributeSelector(int32_t& aDataMask,
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags aFlags,
SelectorParsingFlags& aFlags,
nsIAtom** aPseudoElement,
nsAtomList** aPseudoElementArgs,
CSSPseudoElementType* aPseudoElementType)
@ -6103,10 +6106,14 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
CSSEnabledState enabledState = EnabledState();
CSSPseudoElementType pseudoElementType =
nsCSSPseudoElements::GetPseudoType(pseudo, enabledState);
bool pseudoElementIsTreeAbiding =
nsCSSPseudoElements::IsTreeAbidingPseudoElement(pseudoElementType);
CSSPseudoClassType pseudoClassType =
nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState);
bool pseudoClassIsUserAction =
nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType);
bool pseudoClassHasForgivingSelectorListArg =
nsCSSPseudoClasses::HasForgivingSelectorListArg(pseudoClassType);
if (nsCSSAnonBoxes::IsNonElement(pseudo)) {
// 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
isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree);
// If a tree pseudo-element is using the function syntax, it will
@ -6186,33 +6210,74 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
return eSelectorParsingStatus_Error;
}
if (aSelector.IsPseudoElement()) {
CSSPseudoElementType type = aSelector.PseudoType();
bool forceEmptyList = false;
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 ||
!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
// We only allow user action pseudo-classes on certain pseudo-elements.
(!supportsTreeAbiding && !supportsUserAction)) {
// We only allow user action pseudo-classes and/or tree-abiding
// pseudo-elements on certain pseudo-elements.
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
UngetToken();
return eSelectorParsingStatus_Error;
}
if (!isPseudoClass || !pseudoClassIsUserAction) {
// CSS 4 Selectors says that pseudo-elements can only be followed by
// a user action pseudo-class.
if (isPseudoClass) {
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);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
bool disallowPseudoElements =
!!(aFlags & SelectorParsingFlags::eDisallowPseudoElements);
if (!parsingPseudoElement && isPseudoClass) {
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) {
nsSelectorParsingStatus parsingStatus;
// Only the combinators restriction should be passed down the chain.
SelectorParsingFlags flags =
(aFlags & SelectorParsingFlags::eDisallowCombinators) ?
SelectorParsingFlags::eDisallowCombinators :
SelectorParsingFlags::eNone;
// Pass only a few parsing flags down the chain.
SelectorParsingFlags flags = SelectorParsingFlags::eNone;
if (aFlags & SelectorParsingFlags::eDisallowCombinators) {
flags |= SelectorParsingFlags::eDisallowCombinators;
}
if (aFlags & SelectorParsingFlags::eForceEmptyList ||
forceEmptyList) {
flags |= SelectorParsingFlags::eForceEmptyList;
}
if (sLegacyNegationPseudoClassEnabled &&
CSSPseudoClassType::negation == pseudoClassType) {
// :not() can't be itself negated
@ -6228,6 +6293,13 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
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)) {
parsingStatus =
ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
@ -6239,6 +6311,14 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
else {
MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
"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,
pseudoClassType,
flags);
@ -6265,7 +6345,7 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
}
// Pseudo-elements might not be allowed from appearing
// (e.g. as an argument to the functional part of a pseudo-class).
if (aFlags & SelectorParsingFlags::eDisallowPseudoElements) {
if (disallowPseudoElements) {
UngetToken();
return eSelectorParsingStatus_Error;
}
@ -6344,9 +6424,9 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
// Parse the argument of a negation pseudo-class :not()
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags aFlags)
CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
SelectorParsingFlags& aFlags)
{
aFlags |= SelectorParsingFlags::eIsNegated;
@ -6624,7 +6704,7 @@ CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType,
SelectorParsingFlags aFlags)
SelectorParsingFlags& aFlags)
{
bool isSingleSelector =
nsCSSPseudoClasses::HasSingleSelectorArg(aType);
@ -6692,7 +6772,7 @@ CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
bool
CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
char16_t aPrevCombinator,
SelectorParsingFlags aFlags)
SelectorParsingFlags& aFlags)
{
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESelectorEOF);
@ -6727,7 +6807,8 @@ CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
selector->mClassList = pseudoElementArgs.forget();
selector->SetPseudoType(pseudoElementType);
}
} else if (selector->IsPseudoElement()) {
} else if (selector->IsPseudoElement() ||
selector->IsHybridPseudoElement()) {
// Once we parsed a pseudo-element, we can only parse
// pseudo-classes (and only a limited set, which
// 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) {
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(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
CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "")

View file

@ -106,7 +106,8 @@ bool
nsCSSPseudoClasses::HasSingleSelectorArg(Type aType)
{
return aType == Type::host ||
aType == Type::hostContext;
aType == Type::hostContext ||
aType == Type::slotted;
}
bool
@ -126,7 +127,8 @@ nsCSSPseudoClasses::HasSelectorListArg(Type aType)
aType == Type::mozAny ||
aType == Type::mozAnyPrivate ||
aType == Type::host ||
aType == Type::hostContext;
aType == Type::hostContext ||
aType == Type::slotted;
}
bool
@ -169,3 +171,9 @@ nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType)
aType == Type::active ||
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 IsHiddenFromSerialization(Type aType);
static bool IsUserActionPseudoClass(Type aType);
static bool IsHybridPseudoElement(Type aType);
// Should only be used on types other than Count and NotPseudoClass
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(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(firstLetter, ":first-letter",

View file

@ -75,6 +75,22 @@ nsCSSPseudoElements::IsCSS2PseudoElement(nsIAtom *aAtom)
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
nsCSSPseudoElements::GetPseudoType(nsIAtom *aAtom, EnabledState aEnabledState)
{

View file

@ -40,6 +40,10 @@
// API for creating pseudo-implementing native anonymous content in JS with this
// pseudo-element?
#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 {
@ -80,6 +84,10 @@ public:
static bool IsCSS2PseudoElement(nsIAtom *aAtom);
static bool IsHybridPseudoElement(Type aType);
static bool IsTreeAbidingPseudoElement(Type aType);
#define CSS_PSEUDO_ELEMENT(_name, _value, _flags) \
static nsICSSPseudoElement* _name;
#include "nsCSSPseudoElementList.h"
@ -107,6 +115,12 @@ public:
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)
{
return !PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY) ||

View file

@ -48,6 +48,7 @@
#include "nsCSSRules.h"
#include "nsStyleSet.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/ShadowRoot.h"
#include "nsNthIndexCache.h"
#include "mozilla/ArrayUtils.h"
@ -684,6 +685,7 @@ void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcesso
filter->AssertHasAllAncestors(aElement);
}
#endif
bool isForAssignedSlot = aData->mTreeMatchContext.mForAssignedSlot;
// Merge the lists while there are still multiple lists to merge.
while (valueCount > 1) {
int32_t valueIndex = 0;
@ -696,6 +698,7 @@ void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcesso
}
}
const RuleValue *cur = mEnumList[valueIndex].mCurValue;
aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot;
ContentEnumFunc(*cur, cur->mSelector, aData, aNodeContext, filter);
cur++;
if (cur == mEnumList[valueIndex].mEnd) {
@ -709,6 +712,7 @@ void RuleHash::EnumerateAllRules(Element* aElement, ElementDependentRuleProcesso
for (const RuleValue *value = mEnumList[0].mCurValue,
*end = mEnumList[0].mEnd;
value != end; ++value) {
aData->mTreeMatchContext.mForAssignedSlot = isForAssignedSlot;
ContentEnumFunc(*value, value->mSelector, aData, aNodeContext, filter);
}
}
@ -1380,8 +1384,9 @@ enum class SelectorMatchesFlags : uint8_t {
// pseudo-element.
IS_PSEUDO_CLASS_ARGUMENT = 1 << 2,
// The selector should be blocked from matching the :host pseudo-class.
IS_HOST_INACCESSIBLE = 1 << 3
// The selector should be blocked from matching because it is called
// from outside the shadow tree.
IS_OUTSIDE_SHADOW_TREE = 1 << 3
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorMatchesFlags)
@ -1393,13 +1398,14 @@ static inline bool ActiveHoverQuirkMatches(nsCSSSelector* aSelector,
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_HOST_INACCESSIBLE)) {
SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE)) {
return false;
}
@ -1725,24 +1731,29 @@ static bool SelectorMatches(Element* aElement,
return false;
}
Element* targetElement = aElement;
if (aTreeMatchContext.mForAssignedSlot) {
targetElement = aElement->GetAssignedSlot()->AsElement();
}
// namespace/tag match
// optimization : bail out early if we can
if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
aElement->GetNameSpaceID() != aSelector->mNameSpace))
targetElement->GetNameSpaceID() != aSelector->mNameSpace))
return false;
if (aSelector->mLowercaseTag) {
nsIAtom* selectorTag =
(aTreeMatchContext.mIsHTMLDocument && aElement->IsHTMLElement()) ?
(aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement()) ?
aSelector->mLowercaseTag : aSelector->mCasedTag;
if (selectorTag != aElement->NodeInfo()->NameAtom()) {
if (selectorTag != targetElement->NodeInfo()->NameAtom()) {
return false;
}
}
nsAtomList* IDList = aSelector->mIDList;
if (IDList) {
nsIAtom* id = aElement->GetID();
nsIAtom* id = targetElement->GetID();
if (id) {
// case sensitivity: bug 93371
const bool isCaseSensitive =
@ -1777,7 +1788,7 @@ static bool SelectorMatches(Element* aElement,
nsAtomList* classList = aSelector->mClassList;
if (classList) {
// test for class match
const nsAttrValue *elementClasses = aElement->GetClasses();
const nsAttrValue *elementClasses = targetElement->GetClasses();
if (!elementClasses) {
// Element has no classes but we have a class selector
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);
// The selectors for which we set node bits are, unfortunately, early
// in this function (because they're pseudo-classes, which are
@ -1963,6 +1976,38 @@ static bool SelectorMatches(Element* aElement,
}
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:
{
ShadowRoot* shadow = aElement->GetShadowRoot();
@ -1973,7 +2018,7 @@ static bool SelectorMatches(Element* aElement,
// style).
if (!shadow ||
aSelector->HasFeatureSelectors() ||
aSelectorFlags & SelectorMatchesFlags::IS_HOST_INACCESSIBLE) {
isOutsideShadowTree) {
return false;
}
@ -2338,7 +2383,7 @@ static bool SelectorMatches(Element* aElement,
bool result = true;
if (aSelector->mAttrList) {
// test for attribute match
if (!aElement->HasAttrs()) {
if (!targetElement->HasAttrs()) {
// if no attributes on the content, no match
return false;
} else {
@ -2348,7 +2393,7 @@ static bool SelectorMatches(Element* aElement,
do {
bool isHTML =
(aTreeMatchContext.mIsHTMLDocument && aElement->IsHTMLElement());
(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
@ -2360,7 +2405,7 @@ static bool SelectorMatches(Element* aElement,
// actually has attributes in), short-circuiting if we ever match.
result = false;
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) {
continue;
}
@ -2371,7 +2416,7 @@ static bool SelectorMatches(Element* aElement,
#ifdef DEBUG
bool hasAttr =
#endif
aElement->GetAttr(attrName->NamespaceID(),
targetElement->GetAttr(attrName->NamespaceID(),
attrName->LocalName(), value);
NS_ASSERTION(hasAttr, "GetAttrNameAt lied");
result = AttrMatchesValue(attr, value, isHTML);
@ -2389,12 +2434,12 @@ static bool SelectorMatches(Element* aElement,
}
else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) {
result =
aElement->
targetElement->
AttrValueIs(attr->mNameSpace, matchAttribute, attr->mValue,
attr->IsValueCaseSensitive(isHTML) ? eCaseMatters
: eIgnoreCase);
}
else if (!aElement->HasAttr(attr->mNameSpace, matchAttribute)) {
else if (!targetElement->HasAttr(attr->mNameSpace, matchAttribute)) {
result = false;
}
else if (attr->mFunction != NS_ATTR_FUNC_SET) {
@ -2402,7 +2447,7 @@ static bool SelectorMatches(Element* aElement,
#ifdef DEBUG
bool hasAttr =
#endif
aElement->GetAttr(attr->mNameSpace, matchAttribute, value);
targetElement->GetAttr(attr->mNameSpace, matchAttribute, value);
NS_ASSERTION(hasAttr, "HasAttr lied");
result = AttrMatchesValue(attr, value, isHTML);
}
@ -2417,7 +2462,7 @@ static bool SelectorMatches(Element* aElement,
for (nsCSSSelector *negation = aSelector->mNegations;
result && negation; negation = negation->mNegations) {
bool dependence = false;
result = !SelectorMatches(aElement, negation, aNodeMatchContext,
result = !SelectorMatches(targetElement, negation, aNodeMatchContext,
aTreeMatchContext,
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT,
&dependence);
@ -2542,6 +2587,11 @@ SelectorMatchesTree(Element* aPrevElement,
// 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);
@ -2552,7 +2602,12 @@ SelectorMatchesTree(Element* aPrevElement,
// for descendant combinators and child combinators, the element
// to test against is the parent
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
// 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
// the selector match list.
ShadowRoot* shadowRoot = content ?
ShadowRoot::FromNode(content) : nullptr;
ShadowRoot::FromNode(content) :
nullptr;
if (shadowRoot && !selector->mNext && !crossedShadowRootBoundary) {
content = shadowRoot->GetHost();
crossedShadowRootBoundary = true;
contentIsFeatureless = true;
content = shadowRoot->GetHost();
crossedShadowRootBoundary = true;
contentIsFeatureless = true;
}
// GetParent could return a document fragment; we only want
@ -2662,6 +2718,12 @@ SelectorMatchesTree(Element* aPrevElement,
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
@ -2740,6 +2802,52 @@ static bool SelectorListMatches(Element* aElement,
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
void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
ElementDependentRuleProcessorData* data, NodeMatchContext& nodeContext,
@ -2748,35 +2856,19 @@ void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
if (nodeContext.mIsRelevantLink) {
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>(
value.mAncestorSelectorHashes)) {
// We won't match; nothing else to do here
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;
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
seenHostPseudo = true;
break;
}
}
if (!seenHostPseudo) {
return;
}
if (!LookForTargetPseudo(aSelector, &data->mTreeMatchContext, nullptr)) {
return;
}
if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement,
data->mScope)) {
// 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(),
nsCSSRuleProcessor::IsLink(aData->mElement),
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 =
RestyleHintForSelectorWithAttributeChange(aData->change,
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;
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList;
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 (!LookForTargetPseudo(aSelector, &data->mTreeMatchContext, &possibleChange)) {
return;
}
// If, ignoring eRestyle_SomeDescendants, enumData->change already includes
// all the bits of possibleChange, don't bother calling SelectorMatches, since
// even if it returns false enumData->change won't change. If possibleChange
@ -4187,7 +4261,7 @@ nsCSSRuleProcessor::RestrictedSelectorListMatches(Element* aElement,
NodeMatchContext nodeContext(EventStates(), false);
SelectorMatchesFlags flags = aElement->IsInShadowTree() ?
SelectorMatchesFlags::NONE :
SelectorMatchesFlags::IS_HOST_INACCESSIBLE;
SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE;
return SelectorListMatches(aElement,
aSelectorList,
nodeContext,

View file

@ -371,6 +371,9 @@ struct MOZ_STACK_CLASS TreeMatchContext {
// match.
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
// scoped stylesheet).
nsIContent* mScopedRoot;
@ -403,6 +406,13 @@ struct MOZ_STACK_CLASS TreeMatchContext {
// for an HTML5 scoped style sheet.
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 {
eNeverMatchVisited,
eMatchVisitedDefault
@ -429,6 +439,7 @@ struct MOZ_STACK_CLASS TreeMatchContext {
, mVisitedHandling(aVisitedHandling)
, mDocument(aDocument)
, mOnlyMatchHostPseudo(false)
, mRestrictToSlottedPseudo(false)
, mScopedRoot(nullptr)
, mIsHTMLDocument(aDocument->IsHTMLDocument())
, mCompatMode(aDocument->GetCompatibilityMode())
@ -436,6 +447,8 @@ struct MOZ_STACK_CLASS TreeMatchContext {
, mSkippingParentDisplayBasedStyleFixup(false)
, mForScopedStyle(false)
, mCurrentStyleScope(nullptr)
, mIsTopmostScope(false)
, mForAssignedSlot(false)
{
if (aMatchVisited != eNeverMatchVisited) {
nsILoadContext* loadContext = mDocument->GetLoadContext();

View file

@ -893,3 +893,9 @@ rtc > rt {
ruby, rb, rt, rtc {
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%;
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?
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?
pref("layout.css.scope-pseudo.enabled", true);

View file

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

View file

@ -207,6 +207,36 @@ public:
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
{
MOZ_ASSERT(mClass == eKeyboardEventClass,