mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-29 11:57:32 +09:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
commit
3298c8907d
21 changed files with 619 additions and 124 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, "")
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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) ||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -538,8 +538,7 @@ TextEventDispatcher::DispatchKeyboardEventInternal(
|
|||
|
||||
if (!sDispatchKeyPressEventNonPrintableInContent &&
|
||||
keyEvent.mMessage == eKeyPress &&
|
||||
!keyEvent.IsInputtingText() &&
|
||||
!keyEvent.IsInputtingLineBreak()) {
|
||||
!keyEvent.ShouldKeyPressEventBeFiredOnContent()) {
|
||||
keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue