2

I'm building a utility into a style guide generator that automatically gathers the CSS for each element and displays it adjacent to the element in the output. The script I'm using to gather and parse the CSS is based on an answer from SO and uses the element.matches() web API.

Under most circumstances the code works perfectly, but in cases where there is a 'vendor prefix'-specific pseudo-element selector (e.g. ::-webkit-inner-spin-button as in Bootstrap 4.0), Safari throws an error at the most nested if statement (i.e. if (a.matches(rules[r].selectorText)) {):

SyntaxError (DOM Exception 12): The string did not match the expected pattern.

I've tried searching for this error specifically on SO and I found this question that talks about missing array endings, but I don't think the answer pertains to my issue.

I have a regex workaround that will remove the offending rules so the function can at least run, but as you can see, the properties in that rule ({background-color:black;}) are completely ignored in the output even though they're applied to the rendered element.

I could modify my regex to parse the strings and slice out problematic selectors while leaving the parsable rules, but overall this type of very specific hack feels inelegant to me, and I'm concerned it may cause problems down the road if my team ends up adding rules that rely on those types of vendor-prefixed pseudo-selectors.

Any ideas on how I can be a little more precise about working around (or solving) this issue?

Working Snippet

window.css = function (a) {
var sheets = document.styleSheets, o = [];
  a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;

  for (var i in sheets) {
    var rules = sheets[i].rules || sheets[i].cssRules;
    for (var r in rules) {
      if (a.matches(rules[r].selectorText)) {
          o.push(rules[r].cssText);
      }
    }
  }
  return o;
}
$(document).ready(function(){
  $('button').on('click',function(){
    $('#styles').text(css( $(  $('input').val()  )[0] ));
  });
});
div * {border-left:2px solid black;margin:1em 0;padding:.5em;}

a {text-decoration:none;display:block;cursor:pointer;}
#red {color:red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<input type="text" value="#red"><button>Get styles</button>

<div>
  <a id="red">#red anchor</a>
</div>

<aside id="styles"></aside>

Broken Snippet

(only change is 1 added line of CSS)

window.css = function (a) {
  var sheets = document.styleSheets, o = [];
  a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;

  for (var i in sheets) {
    var rules = sheets[i].rules || sheets[i].cssRules;
    for (var r in rules) {
      if (a.matches(rules[r].selectorText)) {
          o.push(rules[r].cssText);
      }
    }
  }
  return o;
}
$(document).ready(function(){
  $('button').on('click',function(){
    $('#styles').text(css( $(  $('input').val()  )[0] ));
  });
});
div * {border-left:2px solid black;margin:1em 0;padding:.5em;}

a {text-decoration:none;display:block;cursor:pointer;}
#red {color:red;}

/* v ADDED THIS LINE - THIS IS THE ONLY CHANGE v */
[type="submit"]::-webkit-inner-spin-button {background-color:black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<input type="text" value="#red"><button>Get styles</button>

<div>
  <a id="red">#red anchor</a>
</div>

<aside id="styles"></aside>

Reg-Ex Workaround

window.css = function (a) {
  var sheets = document.styleSheets, o = [];
  a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;

  // v NEW FUNCTION v
  function removeVendorPrefixSelectors (selectorText) {
    if (/::-/.test(selectorText)) {
      //do nothing
    } else {
      return selectorText;
    }
  }

  for (var i in sheets) {
    var rules = sheets[i].rules || sheets[i].cssRules;
    for (var r in rules) {

      // v NEW LINE v
      rule = removeVendorPrefixSelectors(rules[r].selectorText);

      if (a.matches(rule)) {
          o.push(rules[r].cssText);
      }
    }
  }
  return o;
}
$(document).ready(function(){
  $('button').on('click',function(){
    $('#styles').text(css( $(  $('input').val()  )[0] ));
  });
});
div * {border-left:2px solid black;margin:1em 0;padding:.5em;}

a {text-decoration:none;display:block;cursor:pointer;}
#red {color:red;}

a, [type="submit"]::-webkit-inner-spin-button {background-color:black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<input type="text" value="#red"><button>Get styles</button>

<div>
  <a id="red">#red anchor</a>
</div>

<aside id="styles"></aside>
Bmd
  • 1,308
  • 8
  • 15
  • The error message doesn't mean anything - Safari is throwing a SYNTAX_ERR, which means the same thing in all browsers: that the given selector is invalid. What *is* surprising - and incorrect behavior - is the fact that Safari considers all -webkit- prefixed pseudo-elements to be invalid in the Selectors API, despite supporting them in CSS. I think your only options are either removing the offending rules, or wrapping your code in a try block with an empty catch. – BoltClock Aug 17 '17 at 12:04

0 Answers0