7

Considering the following JavaScript, CSS, and HTML codes:

console.log(getComputedStyle(document.querySelector('p'), '::after').getPropertyValue('content'));
p::after {
    content: "Hello" attr(data-after);
}
<p data-after=" World"></p>

Both Firefox and IE11 return the raw value defined in CSS: "Hello" attr(data-after), which is what I need.

But Chrome returns "Hello World", the parsed value.

When I use the Chrome DevTools to inspect the element, I can see it showing the following information in the "Styles" panel:

p::after {
    content: "Hello" attr(data-after);
}

So it looks like Chrome still has the ability to know the raw value.

Is there any JavaScript solution to make Chrome return the raw value defined in CSS like Firefox and IE11 do? Even Chrome's exclusive method is fine, as long as it can be used in JavaScript.


Situation Explained

In response to a member's question about the reason, here is the situation:

I'm trying to create a "polyfill" for CSS speak: never on pseudo-elements. The speak property is not being supported by any browser at the moment.

CSS pseudo-elements can be read by screen readers, and it's currently not possible to hide them easily from screen readers. The commonly approach to that issue is using HTML code such as <span aria-hidden="true">...</span> instead of the convenient CSS pseudo-elements and speak: never, and such an approach is inconvenient and disappointing, in my opinion.

So this polyfill is mainly about web accessibility.

How this polyfill works will be requiring CSS developers to write a bit of extra CSS code as an indicator, such as attr(speak-never):

p::after {
    content: "please make me inaudible" attr(speak-never);
    }

And then in JavaScript, the polyfill loops through every element on the page and checks if its pseudo-elements ::before and ::after's values of CSS content property contains attr(speak-never). If that indicator string is found, then the polyfill fix the pseudo-elements (by adding custom elements <before aria-hidden="true">...</before> and <after aria-hidden="true">...</after> programatically).

Now the problem is that Chrome cannot return the said attr(speak-never) in JavaScript. Although the polyfill can also work by requiring CSS developers to add one more indicator such as --speak: never for Chrome, it is better that the polyfill keeps CSS developers' works as simple as possible and does not require that extra --speak: never indicator.

So that's why this question was created.


Update

I had decided to use counter-reset: speak-never as the indicator instead because it can be read in JavaScript by all browsers.

Ian Y.
  • 2,293
  • 6
  • 39
  • 55

2 Answers2

2

Not sure that adding something like attr(speak-never) is any cleaner than going through the HTML markup...

But this question points to an interesting thing: Chrome has started the implementation of CSSTypedOM, that we should have been able to use in order to find the original values set, as demonstrated in this answer.

However, to target pseudo-elements, it is planned that there will be a PseudoElement interface, and an extension to the Element one so that we can call Element.pseudo(::type) in order to target its pseudo-elements. But this part of the specs has not yet been implemented by Chrome, so there is actually currently no way to access the TypedValues of pseudo-elements in Chrome.

So this leaves us with parsing the stylesheets ourselves, with all the caveats that made the w3c develop a real API: no access to cross-origin stylesheets, no direct way to know if a rule is active on the element or if an other one has more importance and the like...

Kaiido
  • 123,334
  • 13
  • 219
  • 285
1

You could lookup the rule via document.styleSheets. The property facilitates a list of stylesheets consisting of multiple rules consisting of multiple properties. You could then traverse the stylesheets and rules and extract the raw property value from there.

This is a basic approach that respects rules directly defined in <style> and <link> elements:

for (let sheet of document.styleSheets) {
    for (let rule of sheet.rules) {
        if (/:(before|after)$/.test(rule.selectorText)) {
            console.log(rule.style.content);
        }
    }
}

Note that @include-ed stylesheets or @media-queries are not handled here. They can be accessed by checking for rule.type being 3 or 5 respectively and traversing the styleSheet attribute of that rule. See the list of types.

try-catch-finally
  • 7,436
  • 6
  • 46
  • 67
  • Cool. I didn't know that CSS rules can be looked up with selectors. Thank you. However, my situation is that selectors are unknown in advance. I had explained the situation in detail. – Ian Y. Mar 17 '19 at 09:38
  • And how do you determine there isn't an other rule that will override this one? e.g `p#some-id::after { content: "something else"; }` – Kaiido Mar 17 '19 at 11:49
  • @Kaiido The polyfill will not do that. Instead, the CSS developers will be responsible for that. – Ian Y. Mar 17 '19 at 12:44
  • @Kaiido, since we've to test all rules anyway we could test them against a RegExp instead matching `/:(before|after)$/` basically. From the order and specificity we could also determine the effective rule, I think. I'll change the code example to use a matcher. – try-catch-finally Mar 17 '19 at 13:21
  • So you'd have to write a full css engine, able to determine state changes like :hover, or :visited (which is not accessible to js). @IanY., css is live, as a css author, user of your script (which is not a polyfill) I would expect `elem::before{content:"foo"attr(speak-never)} .active elem::before{content:"bar"}` to make elem'pseudo element readable when its parent has the class `active`. This means that your parser would have to run continously... – Kaiido Mar 17 '19 at 23:24
  • @Kaiido Thanks for your advice. Btw, I had decided to use `counter-reset: speak-never` as the indicator instead. – Ian Y. Mar 18 '19 at 02:46
  • Hi @Kaiido I hope you could share your insights on the nature of the script. You mentioned that it "is not a polyfill". Could you explain why it is not one? Thanks. – Ian Y. Mar 19 '19 at 11:06
  • 1
    @IanY. Because a real polyfill allows to use the real syntax in environments that don't support it. Once they will support it, your polyfill should not be needed anymore. Here your script is using a workaround to achieve almost the same goal. But since you are binding on different values, if a browser decides to implement this speak property, your users would have to update their code. – Kaiido Mar 19 '19 at 11:32
  • @Kaiido Thanks. I will try to make it so users only need to include the standard syntax `speak: never` and does not need to include the `counter-reset: speak-never` indicator manually. – Ian Y. Mar 20 '19 at 09:12