无效的CSS选择器导致规则被删除:这是什么原因?

29

我更希望能够获得邮件列表讨论等链接,而不是猜测。

有人可以帮我找到CSS选择器级别3规范中引用的错误处理规则背后的理由吗?Selectors Level 3 规范。

用户代理必须遵守处理解析错误的规则:

  • 包含未声明命名空间前缀的简单选择器是无效的
  • 包含无效的简单选择器、无效的组合器或无效的标记的选择器是无效的。
  • 包含无效选择器的选择器组是无效的。

重用 Selectors 的规范必须定义如何处理解析错误。(在 CSS 的情况下,使用选择器的整个规则将被删除。)

我有以下规则:

#menu li.last, #menu li:last-child {
  ...
}

为了弥补IE8缺乏last-child支持的问题,我使用了一个类和JavaScript shims。然而,这并没有起作用,因为IE8在错误处理方面遵循CSS规范,并且会丢弃整个规则,因为它无法识别一个选择器。可以通过将两个选择器分开成单独的规则来解决这个问题。

为什么这样做是可取的?为什么规范不建议简单地丢弃无法识别的选择器,但保留其他部分的规则?

我想知道这背后的原理,因为当前的规则似乎是违反直觉的。

1个回答

41
这为什么是可取的呢?为什么规范不建议简单地丢弃未被识别的选择器,而保留其余的规则呢?
简短的答案是因为实现过程中很难确定到底什么构成了“其余的规则”(或同样地,“其余的选择器列表”),避免出错并无意中破坏布局,同时也为了错误处理的一致性和与未来规范的前向兼容性。
我将用一篇我的另一个答案来作为长篇回答的前言,该答案讲述了如何处理无效选择器。该答案的评论直接指向CSS2.1规范的4.1.7节,该节讨论了在规则集中处理选择器错误的方法,其中提到了选择器中的逗号作为一个例子。我认为它总结得很好:

CSS 2.1赋予逗号(,)在选择器中特殊的含义。然而,由于不确定逗号在未来的CSS更新中是否会获得其他含义,如果选择器中有任何错误,则应忽略整个语句,即使选择器的其余部分在CSS 2.1中看起来合理。

虽然逗号在选择器方面仍然意味着将两个或更多选择器分组,但是Selectors 4引入了新的功能伪类,接受选择器组(或选择器列表)作为参数,例如:matches()(它甚至更改了:not(),使其接受列表,使其类似于:matches(),而在level 3中,它只接受单个简单选择器)。
这意味着您不仅会在规则中找到逗号分隔的选择器组,而且还会在功能伪类中找到它们(请注意,这仅在样式表内部;在CSS之外,选择器可以出现在JavaScript代码中,由选择器库和本机Selectors API使用)。
尽管这不是唯一的原因,但它足以潜在地使解析器的错误处理规则变得过于复杂,存在着破坏选择器、规则集甚至布局的巨大风险。如果出现逗号的解析错误,解析器将难以确定此选择器组是否对应于整个规则集或另一个选择器组的一部分,以及如何相应地处理其余的选择器及其相关的规则集。与其去猜测、冒险猜错并以某种方式破坏规则(例如匹配和设置所有错误的元素),最安全的做法是放弃该规则并继续进行。
例如,考虑以下规则,其选择器在级别4中有效但在级别3中无效,取自我的这个问题
#sectors > div:not(.alpha, .beta, .gamma) {
    color: #808080;
    background-color: #e9e9e9;
    opacity: 0.5;
}

一个不理解Selectors 4的天真解析器可能会仅仅根据逗号将其分为三个不同的选择器,而不是基于伪类接受列表的单个选择器与相同声明块。
#sectors > div:not(.alpha
.beta
.gamma)

如果浏览器简单地丢弃明显无效的第一个和最后一个选择器,只留下有效的第二个选择器,那么它是否应该尝试将规则应用于任何具有class为beta的元素?很明显,这不是作者想要做的,所以如果浏览器这样做,它会对这个布局进行一些意外的操作。通过丢弃具有无效选择器的规则,布局看起来更加合理, 但这只是一个过度简化的例子;具有布局更改样式的规则如果被错误地应用,可能会导致更大的问题。
当然,选择器解析中可能还会出现其他歧义,这可能导致以下情况:
  • 不知道复杂选择器的结束位置
  • 不知道选择器列表的结束位置
  • 不知道声明块的开始位置
  • 以上几种情况的组合
再次强调,解决所有这些问题最简单的方法是丢弃规则集而不是猜测。
在你的例子中,对于看似格式良好但无法识别的选择器(例如:last-child作为伪类),规范没有区分无法识别的选择器和明显格式错误的选择器。两者都会导致解析错误。从您链接到的同一部分中可以看出:

无效性是由解析错误引起的,例如无法识别的标记或当前解析点不允许的标记。

通过对:last-child进行上述陈述,我假设浏览器能够首先解析一个冒号后跟任意标识符作为伪类;实际上,您不能假设实现将正确解析像:last-child这样的伪类,或者使用函数表示法的:lang():not()之类的东西,因为函数伪类直到CSS2才出现。

选择器定义了一组特定的伪类和伪元素,它们的名称在每个实现中都很可能是硬编码的。最简单的解析器拥有每个伪类和伪元素的整个符号表示,包括单/双冒号,这些都是硬编码的(如果主要浏览器实际上将:before:after:first-letter:first-line作为特殊情况来处理,那么我不会感到惊讶)。因此,对于一个实现来说似乎是伪类的内容,在另一个实现中很可能是无用的。

由于实现方式有很多失败的可能性,规范没有做出区分,使得错误处理更加可预测。如果选择器无法识别,无论是因为不支持还是格式不正确,规则都会被丢弃。简单明了,容易理解。


所有这些都说了,至少在www-style公共邮件列表中有一个讨论建议更改规范,因为分割选择器以实现错误处理可能并不那么困难。

我还应该提到一些布局引擎的行为不同,例如WebKit忽略非WebKit前缀的选择器,应用自己的前缀,而其他浏览器完全忽略规则(您可以在Stack Overflow上找到更多示例;这里是稍微不同的一个)。从某种意义上说,你可以说WebKit正在绕过规则,尽管它确实试图聪明地解析逗号分隔的选择器组,尽管存在这些前缀选择器。

I don't think the working group has a compelling reason to change this behavior yet. In fact, if anything, they have a compelling reason not to change it, and that's because sites have been relying on this behavior for many years. In the past, we had selector hacks for filtering older versions of IE; today, we have prefixed selectors for filtering other browsers. These hacks all rely on the same behavior of certain browsers discarding rules they don't recognize, with other browsers applying them if they think they're correct, e.g. by recognizing prefixes (or throwing only the unrecognized ones out, as WebKit does). Sites could break in newer versions of those browsers if this rule were to change, which absolutely cannot happen in such a diversified (read: fragmented) Web as ours.
As of April 2013, it was decided in a telecon that this behavior remain unchanged for the reason I've postulated above:
   - RESOLVED: Do not adopt MQ-style invalidation for Selectors
               due to Web-compat concerns.
Media query-style invalidation refers to invalid media queries in a comma-separated list not breaking the entire @media rule.

虽然我同意 #sectors > div:not(.alpha, .beta, .gamma) 的例子,但我不明白为什么用户代理不能解析像 #menu li.last, #menu li:unknown-pseudo-class 这样的内容。在第一种情况下,会出现意外字符,从而导致解析错误;在第二种情况下,解析将完全正常工作,并且 li:unknown-pseudo-class 选择器只是永远不匹配,例如 @media print 中的 :hover - Tobias
@Tobias:我终于更新了我的答案来解决那个问题。还请参考这个相关的问题:当浏览器不支持CSS伪类时会发生什么? - BoltClock
我认为有一些非常简单的解决方案可以绕过未知选择器,甚至是复杂的选择器!例如,当查找处理像div:not(.alpha, .beta, .gamma)这样的选择器时,解析器应该找到括号对。即使在最简单的情况下,浏览器也可以忽略第一个未知选择器后面的整个选择器,使用{...}模式作为规则块,并将规则应用于前面的选择器。请注意,浏览器目前能够明显地检测到{...}块,因为它们需要跳过整个规则并继续下一个规则。 - S.Serpooshan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接