forked from mirrors/gecko-dev
		
	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:
		
							parent
							
								
									dc9b47aecf
								
							
						
					
					
						commit
						4d5aee49f3
					
				
					 16 changed files with 125 additions and 327 deletions
				
			
		|  | @ -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); | ||||
|   } | ||||
|  |  | |||
|  | @ -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); | ||||
| 
 | ||||
|   /**
 | ||||
|  |  | |||
|  | @ -12,10 +12,11 @@ | |||
| #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.
 | ||||
|   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
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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()) { | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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'", | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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, | ||||
|         })) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -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 | ||||
|  | @ -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> | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Emilio Cobos Álvarez
						Emilio Cobos Álvarez