Bug 1281158 - Parse alternative text for the content property. r=dshin

This doesn't yet expose it to a11y but that will be done by the a11y
folks, since this blocks some of the a11y interop test-cases.

Modify the tests to not hit the network, and make -moz-alt-content not
exposed to content (we only need it for UA stylesheets).

Differential Revision: https://phabricator.services.mozilla.com/D209690
This commit is contained in:
Emilio Cobos Álvarez 2024-05-08 16:06:47 +00:00
parent dc9b47aecf
commit 4d5aee49f3
16 changed files with 125 additions and 327 deletions

View File

@ -1537,12 +1537,11 @@ already_AddRefed<nsIContent> nsCSSFrameConstructor::CreateGenConTextNode(
void nsCSSFrameConstructor::CreateGeneratedContent(
nsFrameConstructorState& aState, Element& aOriginatingElement,
ComputedStyle& aPseudoStyle, uint32_t aContentIndex,
const FunctionRef<void(nsIContent*)> aAddChild) {
ComputedStyle& aPseudoStyle, const StyleContentItem& aItem,
size_t aContentIndex, const FunctionRef<void(nsIContent*)> aAddChild) {
using Type = StyleContentItem::Tag;
// Get the content value
const auto& item = aPseudoStyle.StyleContent()->ContentAt(aContentIndex);
const Type type = item.tag;
const Type type = aItem.tag;
switch (type) {
case Type::Image: {
@ -1552,7 +1551,7 @@ void nsCSSFrameConstructor::CreateGeneratedContent(
}
case Type::String: {
const auto string = item.AsString().AsString();
const auto string = aItem.AsString().AsString();
if (string.IsEmpty()) {
return;
}
@ -1563,7 +1562,7 @@ void nsCSSFrameConstructor::CreateGeneratedContent(
}
case Type::Attr: {
const auto& attr = item.AsAttr();
const auto& attr = aItem.AsAttr();
RefPtr<nsAtom> attrName = attr.attribute.AsAtom();
int32_t attrNameSpace = kNameSpaceID_None;
RefPtr<nsAtom> ns = attr.namespace_url.AsAtom();
@ -1592,11 +1591,11 @@ void nsCSSFrameConstructor::CreateGeneratedContent(
CounterStylePtr ptr;
nsString separator;
if (type == Type::Counter) {
auto& counter = item.AsCounter();
auto& counter = aItem.AsCounter();
name = counter._0.AsAtom();
ptr = CounterStylePtr::FromStyle(counter._1);
} else {
auto& counters = item.AsCounters();
auto& counters = aItem.AsCounters();
name = counters._0.AsAtom();
CopyUTF8toUTF16(counters._1.AsString(), separator);
ptr = CounterStylePtr::FromStyle(counters._2);
@ -1947,13 +1946,14 @@ void nsCSSFrameConstructor::CreateGeneratedContentItem(
mPresShell->StyleSet()->StyleNewSubtree(childElement);
}
};
const uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount();
for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) {
CreateGeneratedContent(aState, aOriginatingElement, *pseudoStyle,
contentIndex, AppendChild);
auto items = pseudoStyle->StyleContent()->NonAltContentItems();
size_t index = 0;
for (const auto& item : items) {
CreateGeneratedContent(aState, aOriginatingElement, *pseudoStyle, item,
index++, AppendChild);
}
// If a ::marker has no 'content' then generate it from its 'list-style-*'.
if (contentCount == 0 && aPseudoElement == PseudoStyleType::marker) {
if (index == 0 && aPseudoElement == PseudoStyleType::marker) {
CreateGeneratedContentFromListStyle(aState, aOriginatingElement,
*pseudoStyle, AppendChild);
}

View File

@ -477,7 +477,8 @@ class nsCSSFrameConstructor final : public nsFrameManager {
*/
void CreateGeneratedContent(
nsFrameConstructorState& aState, Element& aOriginatingElement,
ComputedStyle& aPseudoStyle, uint32_t aContentIndex,
ComputedStyle& aPseudoStyle, const mozilla::StyleContentItem& aItem,
size_t aContentIndex,
const mozilla::FunctionRef<void(nsIContent*)> aAddChild);
/**

View File

@ -12,12 +12,13 @@
#include "nsIFrame.h"
void nsGenConNode::CheckFrameAssertions() {
NS_ASSERTION(
mContentIndex < int32_t(mPseudoFrame->StyleContent()->ContentCount()) ||
// Special-case for the USE node created for the legacy markers,
// which don't use the content property.
mContentIndex == 0,
"index out of range");
NS_ASSERTION(mContentIndex < int32_t(mPseudoFrame->StyleContent()
->NonAltContentItems()
.Length()) ||
// Special-case for the USE node created for the legacy
// markers, which don't use the content property.
mContentIndex == 0,
"index out of range");
// We allow negative values of mContentIndex for 'counter-reset' and
// 'counter-increment'.

View File

@ -900,7 +900,7 @@ void nsLayoutUtils::GetMarkerSpokenText(const nsIContent* aContent,
return;
}
if (frame->StyleContent()->ContentCount() > 0) {
if (!frame->StyleContent()->NonAltContentItems().IsEmpty()) {
for (nsIFrame* child : frame->PrincipalChildList()) {
nsIFrame::RenderedText text = child->GetRenderedText();
aText += text.mString;

View File

@ -7980,7 +7980,7 @@ bool nsBlockFrame::MarkerIsEmpty() const {
const nsStyleList* list = marker->StyleList();
return marker->StyleContent()->mContent.IsNone() ||
(list->mCounterStyle.IsNone() && list->mListStyleImage.IsNone() &&
marker->StyleContent()->ContentCount() == 0);
marker->StyleContent()->NonAltContentItems().IsEmpty());
}
void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame,

View File

@ -645,8 +645,9 @@ const StyleImage* nsImageFrame::GetImageFromStyle() const {
nonAnonymousParent->GetContent());
styleContent = nonAnonymousParent->StyleContent();
}
MOZ_RELEASE_ASSERT(contentIndex < styleContent->ContentCount());
auto& contentItem = styleContent->ContentAt(contentIndex);
auto items = styleContent->NonAltContentItems();
MOZ_RELEASE_ASSERT(contentIndex < items.Length());
const auto& contentItem = items[contentIndex];
MOZ_RELEASE_ASSERT(contentItem.IsImage());
return &contentItem.AsImage();
}
@ -1055,11 +1056,8 @@ bool nsImageFrame::ShouldCreateImageFrameForContentProperty(
if (aElement.IsRootOfNativeAnonymousSubtree()) {
return false;
}
const auto& content = aStyle.StyleContent()->mContent;
if (!content.IsItems()) {
return false;
}
Span<const StyleContentItem> items = content.AsItems().AsSpan();
Span<const StyleContentItem> items =
aStyle.StyleContent()->NonAltContentItems();
return items.Length() == 1 && items[0].IsImage();
}

View File

@ -767,7 +767,7 @@ bool ServoStyleSet::GeneratedContentPseudoExists(
if (!aPseudoStyle.StyleContent()->mContent.IsItems()) {
return false;
}
MOZ_ASSERT(aPseudoStyle.StyleContent()->ContentCount() > 0,
MOZ_ASSERT(!aPseudoStyle.StyleContent()->NonAltContentItems().IsEmpty(),
"IsItems() implies we have at least one item");
// display:none is equivalent to not having a pseudo at all.
if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {

View File

@ -2698,12 +2698,11 @@ void nsStyleContent::TriggerImageLoads(Document& aDoc,
}
Span<const StyleContentItem> oldItems;
if (aOld && aOld->mContent.IsItems()) {
oldItems = aOld->mContent.AsItems().AsSpan();
if (aOld) {
oldItems = aOld->NonAltContentItems();
}
auto items = mContent.AsItems().AsSpan();
auto items = NonAltContentItems();
for (size_t i = 0; i < items.Length(); ++i) {
const auto& item = items[i];
if (!item.IsImage()) {

View File

@ -1603,12 +1603,22 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleContent {
using CounterPair = mozilla::StyleGenericCounterPair<int32_t>;
size_t ContentCount() const {
return mContent.IsItems() ? mContent.AsItems().Length() : 0;
/// Returns the content items that aren't alternative content.
mozilla::Span<const mozilla::StyleContentItem> NonAltContentItems() const {
if (!mContent.IsItems()) {
return {};
}
const auto& items = mContent.AsItems();
return mozilla::Span(items.items).To(items.alt_start);
}
const mozilla::StyleContentItem& ContentAt(size_t aIndex) const {
return mContent.AsItems().AsSpan()[aIndex];
/// Returns the content items that /are/ alternative content.
mozilla::Span<const mozilla::StyleContentItem> AltContentItems() const {
if (!mContent.IsItems()) {
return {};
}
const auto& items = mContent.AsItems();
return mozilla::Span(items.items).From(items.alt_start);
}
mozilla::StyleContent mContent;

View File

@ -5382,7 +5382,6 @@ var gCSSProperties = {
"counter(\\()",
"counters(a\\+b, '.')",
"counter(\\}, upper-alpha)",
"-moz-alt-content",
"counter(foo, symbols('*'))",
"counter(foo, symbols(numeric '0' '1'))",
"counters(foo, '.', symbols('*'))",
@ -5400,6 +5399,7 @@ var gCSSProperties = {
"attr(-2)",
"counter(2)",
"counters(-2, '.')",
"-moz-alt-content",
"-moz-alt-content 'foo'",
"'foo' -moz-alt-content",
"counter(one, two, three) 'foo'",

View File

@ -8623,6 +8623,13 @@
mirror: always
rust: true
# Whether alt text in content is enabled.
- name: layout.css.content.alt-text.enabled
type: RelaxedAtomicBool
value: @IS_NIGHTLY_BUILD@
mirror: always
rust: true
# Should stray control characters be rendered visibly?
- name: layout.css.control-characters.visible
type: RelaxedAtomicBool

View File

@ -203,6 +203,41 @@ fn is_decimal(counter_type: &CounterStyleType) -> bool {
*counter_type == CounterStyle::decimal()
}
/// The non-normal, non-none values of the content property.
#[derive(
Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToShmem,
)]
#[repr(C)]
pub struct GenericContentItems<Image> {
/// The actual content items. Note that, past the alt marker, only some subset (strings,
/// attr(), counter())
pub items: thin_vec::ThinVec<GenericContentItem<Image>>,
/// The index at which alt text starts, always non-zero. If equal to items.len(), no alt text
/// exists.
pub alt_start: usize,
}
impl<Image> ToCss for GenericContentItems<Image>
where
Image: ToCss,
{
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
for (i, item) in self.items.iter().enumerate() {
if i == self.alt_start {
dest.write_str(" /")?;
}
if i != 0 {
dest.write_str(" ")?;
}
item.to_css(dest)?;
}
Ok(())
}
}
/// The specified value for the `content` property.
///
/// https://drafts.csswg.org/css-content/#propdef-content
@ -216,7 +251,7 @@ pub enum GenericContent<Image> {
/// `none` reserved keyword.
None,
/// Content items.
Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<Image>>),
Items(GenericContentItems<Image>),
}
pub use self::GenericContent as Content;

View File

@ -192,29 +192,33 @@ impl Parse for Content {
return Ok(generics::Content::None);
}
let mut content = vec![];
let mut has_alt_content = false;
let mut items = thin_vec::ThinVec::new();
let mut alt_start = None;
loop {
{
if alt_start.is_none() {
if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) {
content.push(generics::ContentItem::Image(image));
items.push(generics::ContentItem::Image(image));
continue;
}
}
match input.next() {
Ok(&Token::QuotedString(ref value)) => {
content.push(generics::ContentItem::String(
let Ok(t) = input.next() else { break };
match *t {
Token::QuotedString(ref value) => {
items.push(generics::ContentItem::String(
value.as_ref().to_owned().into(),
));
},
Ok(&Token::Function(ref name)) => {
Token::Function(ref name) => {
// FIXME(emilio): counter() / counters() should be valid per spec past
// the alt marker, but it's likely non-trivial to support and other
// browsers don't support it either, so restricting it for now.
let result = match_ignore_ascii_case! { &name,
"counter" => input.parse_nested_block(|input| {
"counter" if alt_start.is_none() => input.parse_nested_block(|input| {
let name = CustomIdent::parse(input, &[])?;
let style = Content::parse_counter_style(context, input);
Ok(generics::ContentItem::Counter(name, style))
}),
"counters" => input.parse_nested_block(|input| {
"counters" if alt_start.is_none() => input.parse_nested_block(|input| {
let name = CustomIdent::parse(input, &[])?;
input.expect_comma()?;
let separator = input.expect_string()?.as_ref().to_owned().into();
@ -232,17 +236,16 @@ impl Parse for Content {
))
}
}?;
content.push(result);
items.push(result);
},
Ok(&Token::Ident(ref ident)) => {
content.push(match_ignore_ascii_case! { &ident,
Token::Ident(ref ident) if alt_start.is_none() => {
items.push(match_ignore_ascii_case! { &ident,
"open-quote" => generics::ContentItem::OpenQuote,
"close-quote" => generics::ContentItem::CloseQuote,
"no-open-quote" => generics::ContentItem::NoOpenQuote,
"no-close-quote" => generics::ContentItem::NoCloseQuote,
#[cfg(feature = "gecko")]
"-moz-alt-content" => {
has_alt_content = true;
"-moz-alt-content" if context.in_ua_sheet() => {
generics::ContentItem::MozAltContent
},
"-moz-label-content" if context.chrome_rules_enabled() => {
@ -256,17 +259,26 @@ impl Parse for Content {
}
});
},
Err(_) => break,
Ok(t) => {
Token::Delim('/')
if alt_start.is_none() &&
!items.is_empty() &&
static_prefs::pref!("layout.css.content.alt-text.enabled") =>
{
alt_start = Some(items.len());
},
ref t => {
let t = t.clone();
return Err(input.new_unexpected_token_error(t));
},
}
}
// We don't allow to parse `-moz-alt-content` in multiple positions.
if content.is_empty() || (has_alt_content && content.len() != 1) {
if items.is_empty() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(generics::Content::Items(content.into()))
let alt_start = alt_start.unwrap_or(items.len());
Ok(generics::Content::Items(generics::GenericContentItems {
items,
alt_start,
}))
}
}

View File

@ -1,58 +0,0 @@
[content-computed.html]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1281158
[Property content value 'open-quote / "alt text"']
expected: FAIL
[Property content value 'close-quote / "alt text"']
expected: FAIL
[Property content value 'no-open-quote / "alt text"']
expected: FAIL
[Property content value 'no-close-quote / "alt text"']
expected: FAIL
[Property content value 'counter(counter-name) / "alt text"']
expected: FAIL
[Property content value 'counter(counter-name, counter-style) / "alt text"']
expected: FAIL
[Property content value 'counter(counter-name, dECiMaL) / "alt text"']
expected: FAIL
[Property content value 'counter(counter-name, DECIMAL) / "alt text"']
expected: FAIL
[Property content value 'counters(counter-name, ".") / "alt text"']
expected: FAIL
[Property content value 'counters(counter-name, ".", counter-style) / "alt text"']
expected: FAIL
[Property content value 'counters(counter-name, ".", dECiMaL) / "alt text"']
expected: FAIL
[Property content value 'counters(counter-name, ".", DECIMAL) / "alt text"']
expected: FAIL
[Property content value 'url("https://www.example.com/picture.svg") / "alt text"']
expected: FAIL
[Property content value '"hello" / "alt text"']
expected: FAIL
[Property content value 'counter(counter-name) "potato" / "alt text"']
expected: FAIL
[Property content value 'counters(counter-name, ".") "potato" / "alt text"']
expected: FAIL
[Property content value '"(" counters(counter-name, ".", counter-style) ")" / "alt text"']
expected: FAIL
[Property content value 'open-quote "hello" "world" close-quote / "alt text"']
expected: FAIL
[Property content value 'url("https://www.example.com/picture.svg") "hello" / "alt text"']
expected: FAIL

View File

@ -1,207 +0,0 @@
[content-valid.html]
[e.style['content'\] = "open-quote / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "open-quote / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "open-quote / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "close-quote / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "close-quote / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "close-quote / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "no-open-quote / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "no-open-quote / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "no-open-quote / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "no-close-quote / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "no-close-quote / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "no-close-quote / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "attr(alt) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "attr(alt) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "attr(alt) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "attr(data-foo) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "attr(data-foo) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "attr(data-foo) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, counter-style) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, counter-style) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, counter-style) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, dECiMaL) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, dECiMaL) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, dECiMaL) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, DECIMAL) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, DECIMAL) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name, DECIMAL) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\") / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\") / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\") / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", counter-style) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", counter-style) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", counter-style) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", dECiMaL) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", dECiMaL) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", dECiMaL) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", DECIMAL) / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", DECIMAL) / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\", DECIMAL) / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "url(\\"https://www.example.com/picture.svg\\") / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "url(\\"https://www.example.com/picture.svg\\") / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "url(\\"https://www.example.com/picture.svg\\") / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" \\"world\\" / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" \\"world\\" / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" \\"world\\" / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" attr(alt) \\"world\\" / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" attr(alt) \\"world\\" / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"hello\\" attr(alt) \\"world\\" / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name) \\"potato\\" / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name) \\"potato\\" / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counter(counter-name) \\"potato\\" / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\") \\"potato\\" / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\") \\"potato\\" / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "counters(counter-name, \\".\\") \\"potato\\" / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"(\\" counters(counter-name, \\".\\", counter-style) \\")\\" / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"(\\" counters(counter-name, \\".\\", counter-style) \\")\\" / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "\\"(\\" counters(counter-name, \\".\\", counter-style) \\")\\" / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "open-quote \\"hello\\" \\"world\\" close-quote / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "open-quote \\"hello\\" \\"world\\" close-quote / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "open-quote \\"hello\\" \\"world\\" close-quote / attr(foo)" should set the property value]
expected: FAIL
[e.style['content'\] = "url(\\"https://www.example.com/picture.svg\\") \\"hello\\" / \\"alt text\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "url(\\"https://www.example.com/picture.svg\\") \\"hello\\" / \\"alt text\\" attr(foo) \\"bar\\"" should set the property value]
expected: FAIL
[e.style['content'\] = "url(\\"https://www.example.com/picture.svg\\") \\"hello\\" / attr(foo)" should set the property value]
expected: FAIL

View File

@ -42,7 +42,7 @@ test_valid_value_combinations("content", `counters(counter-name, ".", counter-st
test_valid_value_combinations("content", `counters(counter-name, ".", dECiMaL)`, `counters(counter-name, ".")`);
test_valid_value_combinations("content", `counters(counter-name, ".", DECIMAL)`, `counters(counter-name, ".")`);
test_valid_value_combinations("content", `url("https://www.example.com/picture.svg")`);
test_valid_value_combinations("content", `url("picture.svg")`);
test_valid_value_combinations("content", `"hello"`);
@ -52,7 +52,7 @@ test_valid_value_combinations("content", `counter(counter-name) "potato"`);
test_valid_value_combinations("content", `counters(counter-name, ".") "potato"`);
test_valid_value_combinations("content", `"(" counters(counter-name, ".", counter-style) ")"`);
test_valid_value_combinations("content", `open-quote "hello" "world" close-quote`);
test_valid_value_combinations("content", `url("https://www.example.com/picture.svg") "hello"`);
test_valid_value_combinations("content", `url("picture.svg") "hello"`);
</script>
</body>
</html>