Bug 1330843 - Allow JS to create NAC pseudo-elements

Tag #1375
This commit is contained in:
Matt A. Tobin 2020-04-17 05:01:17 -04:00 committed by Roy Tam
commit c37b4d0c63
8 changed files with 115 additions and 4 deletions

View file

@ -9871,3 +9871,16 @@ nsContentUtils::IsLocalRefURL(const nsString& aString)
return false;
}
/* static */ Element*
nsContentUtils::GetClosestNonNativeAnonymousAncestor(Element* aElement)
{
MOZ_ASSERT(aElement);
MOZ_ASSERT(aElement->IsNativeAnonymous());
Element* e = aElement;
while (e && e->IsNativeAnonymous()) {
e = e->GetParentElement();
}
return e;
}

View file

@ -2762,6 +2762,12 @@ public:
static bool
IsWebComponentsEnabled() { return sIsWebComponentsEnabled; }
/**
* Walks up the tree from aElement until it finds an element that is
* not native anonymous content. aElement must be NAC itself.
*/
static Element* GetClosestNonNativeAnonymousAncestor(Element* aElement);
static bool
IsCustomElementsEnabled() { return sIsCustomElementsEnabled; }

View file

@ -5378,6 +5378,20 @@ nsDocument::GetCustomElementRegistry()
return registry.forget();
}
// We only support pseudo-elements with two colons in this function.
static CSSPseudoElementType
GetPseudoElementType(const nsString& aString, ErrorResult& aRv)
{
MOZ_ASSERT(!aString.IsEmpty(), "GetPseudoElementType aString should be non-null");
if (aString.Length() <= 2 || aString[0] != ':' || aString[1] != ':') {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return CSSPseudoElementType::NotPseudo;
}
nsCOMPtr<nsIAtom> pseudo = NS_Atomize(Substring(aString, 1));
return nsCSSPseudoElements::GetPseudoType(pseudo,
nsCSSProps::EnabledState::eInUASheets);
}
already_AddRefed<Element>
nsDocument::CreateElement(const nsAString& aTagName,
const ElementCreationOptionsOrString& aOptions,
@ -5395,10 +5409,36 @@ nsDocument::CreateElement(const nsAString& aTagName,
}
const nsString* is = nullptr;
CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
if (aOptions.IsElementCreationOptions()) {
const ElementCreationOptions& options =
aOptions.GetAsElementCreationOptions();
if (CustomElementRegistry::IsCustomElementEnabled() &&
options.mIs.WasPassed()) {
is = &options.mIs.Value();
}
// Check 'pseudo' and throw an exception if it's not one allowed
// with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
if (options.mPseudo.WasPassed()) {
pseudoType = GetPseudoElementType(options.mPseudo.Value(), rv);
if (rv.Failed() ||
pseudoType == CSSPseudoElementType::NotPseudo ||
!nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(pseudoType)) {
rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
}
}
RefPtr<Element> elem = CreateElem(
needsLowercase ? lcTagName : aTagName, nullptr, mDefaultElementType, is);
if (pseudoType != CSSPseudoElementType::NotPseudo) {
elem->SetPseudoElementType(pseudoType);
}
if (is) {
elem->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *is, true);
}
@ -5413,8 +5453,8 @@ nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
{
*aReturn = nullptr;
ElementCreationOptionsOrString options;
options.SetAsString();
options.SetAsString();
ErrorResult rv;
nsCOMPtr<Element> element =
CreateElementNS(aNamespaceURI, aQualifiedName, options, rv);
@ -5439,6 +5479,13 @@ nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
}
const nsString* is = nullptr;
if (CustomElementRegistry::IsCustomElementEnabled() &&
aOptions.IsElementCreationOptions()) {
const ElementCreationOptions& options = aOptions.GetAsElementCreationOptions();
if (options.mIs.WasPassed()) {
is = &options.mIs.Value();
}
}
nsCOMPtr<Element> element;
rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),

View file

@ -17,6 +17,9 @@ enum VisibilityState { "hidden", "visible", "prerender" };
/* https://dom.spec.whatwg.org/#dictdef-elementcreationoptions */
dictionary ElementCreationOptions {
DOMString is;
[ChromeOnly]
DOMString pseudo;
};
/* http://dom.spec.whatwg.org/#interface-document */

View file

@ -1062,6 +1062,25 @@ ElementForStyleContext(nsIContent* aParentContent,
return f->GetContent()->AsElement();
}
Element* frameElement = aFrame->GetContent()->AsElement();
if (frameElement->IsNativeAnonymous() &&
nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(aPseudoType)) {
// NAC-implemented pseudos use the closest non-NAC element as their
// element to inherit from.
//
// FIXME(heycam): In theory we shouldn't need to limit this only to
// JS-created pseudo-implementing NAC, as all pseudo-implementing
// should use the closest non-native anonymous ancestor element as
// its originating element. But removing that part of the condition
// reveals some bugs in style resultion with display:contents and
// XBL. See bug 1345809.
Element* originatingElement =
nsContentUtils::GetClosestNonNativeAnonymousAncestor(frameElement);
if (originatingElement) {
return originatingElement;
}
}
if (aParentContent) {
return aParentContent->AsElement();
}

View file

@ -5041,8 +5041,19 @@ nsCSSFrameConstructor::ResolveStyleContext(nsStyleContext* aParentStyleContext,
aParentStyleContext);
}
} else {
MOZ_ASSERT(aOriginatingElementOrNull);
MOZ_ASSERT(aContent->IsInNativeAnonymousSubtree());
if (!aOriginatingElementOrNull) {
// For pseudo-implementing NAC created by JS using the ChromeOnly
// document.createElement(..., { pseudo: ... }) API, we find the
// originating element by lookup the tree until we find a non-NAC
// ancestor. (These are the correct semantics for C++-generated pseudo-
// implementing NAC as well, but for those cases we already have a
// correct originating element passed in.)
MOZ_ASSERT(nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(pseudoType));
aOriginatingElementOrNull =
nsContentUtils::GetClosestNonNativeAnonymousAncestor(aContent->AsElement());
}
MOZ_ASSERT(aOriginatingElementOrNull);
result = styleSet->ResolvePseudoElementStyle(aOriginatingElementOrNull,
pseudoType,
aParentStyleContext,

View file

@ -36,6 +36,10 @@
#define CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE (1<<3)
// Is content prevented from parsing selectors containing this pseudo-element?
#define CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY (1<<4)
// Can we use the ChromeOnly document.createElement(..., { pseudo: "::foo" })
// API for creating pseudo-implementing native anonymous content in JS with this
// pseudo-element?
#define CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC (1<<5)
namespace mozilla {
@ -98,6 +102,11 @@ public:
static bool PseudoElementSupportsUserActionState(const Type aType);
static bool PseudoElementIsJSCreatedNAC(Type aType)
{
return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC);
}
static bool IsEnabled(Type aType, EnabledState aEnabledState)
{
return !PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY) ||

View file

@ -717,7 +717,9 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
// We want to check that going through this path because of
// HasPseudoElementData is rare, because it slows us down a good
// bit. So check that we're really inside something associated
// with a pseudo-element that contains elements.
// with a pseudo-element that contains elements. (We also allow
// the element to be NAC, just in case some chrome JS calls
// getComputedStyle on a NAC-implemented pseudo.)
nsStyleContext* topWithPseudoElementData = mStyleContext;
while (topWithPseudoElementData->GetParent()->HasPseudoElementData()) {
topWithPseudoElementData = topWithPseudoElementData->GetParent();
@ -728,7 +730,8 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
NS_LITERAL_STRING("we should be in a pseudo-element that is expected to contain elements ("));
assertMsg.Append(nsDependentString(pseudoAtom->GetUTF16String()));
assertMsg.Append(')');
NS_ASSERTION(nsCSSPseudoElements::PseudoElementContainsElements(pseudo),
NS_ASSERTION(nsCSSPseudoElements::PseudoElementContainsElements(pseudo) ||
mElement->IsNativeAnonymous(),
NS_LossyConvertUTF16toASCII(assertMsg).get());
}
#endif