1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/style/test/test_font_feature_values_parsing.html Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,360 @@ 1.4 +<!DOCTYPE HTML> 1.5 +<html> 1.6 +<head> 1.7 + <meta charset=utf-8> 1.8 + <title>@font-feature-values rule parsing tests</title> 1.9 + <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"> 1.10 + <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-feature-values" /> 1.11 + <meta name="assert" content="tests that valid @font-feature-values rules parse and invalid ones don't" /> 1.12 + <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=549861 --> 1.13 + <script type="text/javascript" src="/resources/testharness.js"></script> 1.14 + <script type="text/javascript" src="/resources/testharnessreport.js"></script> 1.15 + <style type="text/css"> 1.16 + </style> 1.17 +</head> 1.18 +<body> 1.19 +<div id="log"></div> 1.20 +<pre id="display"></pre> 1.21 +<style type="text/css" id="testbox"></style> 1.22 + 1.23 +<script type="text/javascript"> 1.24 +var gPrefix = ""; 1.25 +var kFontFeatureValuesRuleType = 14; 1.26 + 1.27 +function ruleName() { return "@" + gPrefix + "font-feature-values"; } 1.28 +function makeRule(f, v) { 1.29 + return ruleName() + " " + f + " { " + v + " }"; 1.30 +} 1.31 + 1.32 +function _() 1.33 +{ 1.34 + var i, decl = []; 1.35 + for (i = 0; i < arguments.length; i++) { 1.36 + decl.push(arguments[i]); 1.37 + } 1.38 + return makeRule("bongo", decl.join(" ")); 1.39 +} 1.40 + 1.41 +// note: because of bugs in the way family names are serialized, 1.42 +// 'serializationSame' only implies that the value definition block 1.43 +// is the same (i.e. not including the family name list) 1.44 + 1.45 +var testrules = [ 1.46 + 1.47 + /* basic syntax */ 1.48 + { rule: ruleName() + ";", invalid: true }, 1.49 + { rule: ruleName() + " bongo;", invalid: true }, 1.50 + { rule: ruleName().replace("values", "value") + " {;}", invalid: true }, 1.51 + { rule: ruleName().replace("feature", "features") + " {;}", invalid: true }, 1.52 + { rule: makeRule("bongo", ""), serializationNoValueDefn: true }, 1.53 + { rule: makeRule("bongo", ";"), serializationNoValueDefn: true }, 1.54 + { rule: makeRule("bongo", ",;"), serializationNoValueDefn: true }, 1.55 + { rule: makeRule("bongo", ";,"), serializationNoValueDefn: true }, 1.56 + { rule: makeRule("bongo", ",;,"), serializationNoValueDefn: true }, 1.57 + { rule: makeRule("bongo", "@styleset;"), serializationNoValueDefn: true }, 1.58 + { rule: makeRule("bongo", "@styleset,;"), serializationNoValueDefn: true }, 1.59 + { rule: makeRule("bongo", "@styleset abc;"), serializationNoValueDefn: true }, 1.60 + { rule: makeRule("bongo", "@styleset { abc }"), serializationNoValueDefn: true }, 1.61 + { rule: makeRule("bongo", "@styleset { ;;abc }"), serializationNoValueDefn: true }, 1.62 + { rule: makeRule("bongo", "@styleset { abc;; }"), serializationNoValueDefn: true }, 1.63 + { rule: makeRule("bongo", "@styleset { abc: }"), serializationNoValueDefn: true }, 1.64 + { rule: makeRule("bongo", "@styleset { abc,: }"), serializationNoValueDefn: true }, 1.65 + { rule: makeRule("bongo", "@styleset { abc:, }"), serializationNoValueDefn: true }, 1.66 + { rule: makeRule("bongo", "@styleset { abc:,; }"), serializationNoValueDefn: true }, 1.67 + { rule: makeRule("bongo", "@styleset { a,b }"), serializationNoValueDefn: true }, 1.68 + { rule: makeRule("bongo", "@styleset { a;b }"), serializationNoValueDefn: true }, 1.69 + { rule: makeRule("bongo", "@styleset { a:;b: }"), serializationNoValueDefn: true }, 1.70 + { rule: makeRule("bongo", "@styleset { a:,;b: }"), serializationNoValueDefn: true }, 1.71 + { rule: makeRule("bongo", "@styleset { a:1,;b: }"), serializationNoValueDefn: true }, 1.72 + { rule: makeRule("bongo", "@styleset { abc 1 2 3 }"), serializationNoValueDefn: true }, 1.73 + { rule: makeRule("bongo", "@styleset { abc:, 1 2 3 }"), serializationNoValueDefn: true }, 1.74 + { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true }, 1.75 + { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true }, 1.76 + { rule: makeRule("bongo", "@styleset { abc: 1 2 3a }"), serializationNoValueDefn: true }, 1.77 + { rule: makeRule("bongo", "@styleset { abc: 1 2 3, def: 1; }"), serializationNoValueDefn: true }, 1.78 + { rule: makeRule("bongo", "@blah @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, 1.79 + { rule: makeRule("bongo", "@blah } @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, 1.80 + { rule: makeRule("bongo", "@blah , @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, 1.81 + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3; }", serialization: _("@styleset { abc: 1 2 3; }") }, 1.82 + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3 }", serialization: _("@styleset { abc: 1 2 3; }") }, 1.83 + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3;", serialization: _("@styleset { abc: 1 2 3; }") }, 1.84 + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3", serialization: _("@styleset { abc: 1 2 3; }") }, 1.85 + { rule: _("@styleset { ok-1: 1; }"), serializationSame: true }, 1.86 + { rule: _("@annotation { ok-1: 3; }"), serializationSame: true }, 1.87 + { rule: _("@stylistic { blah: 3; }"), serializationSame: true }, 1.88 + { rule: makeRule("bongo", "\n@styleset\n { blah: 3; super-blah: 4 5;\n more-blah: 5 6 7;\n }"), serializationSame: true }, 1.89 + { rule: makeRule("bongo", "\n@styleset\n {\n blah:\n 3\n;\n super-blah:\n 4\n 5\n;\n more-blah:\n 5 6\n 7;\n }"), serializationSame: true }, 1.90 + 1.91 + /* limits on number of values */ 1.92 + { rule: _("@stylistic { blah: 1; }"), serializationSame: true }, 1.93 + { rule: _("@styleset { blah: 1 2 3 4; }"), serializationSame: true }, 1.94 + { rule: _("@character-variant { blah: 1 2; }"), serializationSame: true }, 1.95 + { rule: _("@swash { blah: 1; }"), serializationSame: true }, 1.96 + { rule: _("@ornaments { blah: 1; }"), serializationSame: true }, 1.97 + { rule: _("@annotation { blah: 1; }"), serializationSame: true }, 1.98 + 1.99 + /* values ignored when used */ 1.100 + { rule: _("@styleset { blah: 0; }"), serializationSame: true }, 1.101 + { rule: _("@styleset { blah: 120 124; }"), serializationSame: true }, 1.102 + { rule: _("@character-variant { blah: 0; }"), serializationSame: true }, 1.103 + { rule: _("@character-variant { blah: 111; }"), serializationSame: true }, 1.104 + { rule: _("@character-variant { blah: 111 13; }"), serializationSame: true }, 1.105 + 1.106 + /* invalid value name */ 1.107 + { rulesrc: ["styleset { blah: 1 }"], serializationNoValueDefn: true }, 1.108 + { rulesrc: ["stylistic { blah: 1 }"], serializationNoValueDefn: true }, 1.109 + { rulesrc: ["character-variant { blah: 1 }"], serializationNoValueDefn: true }, 1.110 + { rulesrc: ["swash { blah: 1 }"], serializationNoValueDefn: true }, 1.111 + { rulesrc: ["ornaments { blah: 1 }"], serializationNoValueDefn: true }, 1.112 + { rulesrc: ["annotation { blah: 1 }"], serializationNoValueDefn: true }, 1.113 + { rulesrc: ["@bongo { blah: 1 }"], serializationNoValueDefn: true }, 1.114 + { rulesrc: ["@bongo { blah: 1 2 3 }"], serializationNoValueDefn: true }, 1.115 + { rulesrc: ["@bongo { blah: 1 2 3; burp: 1;;; }"], serializationNoValueDefn: true }, 1.116 + 1.117 + /* values */ 1.118 + { rulesrc: ["@styleset { blah: -1 }"], serializationNoValueDefn: true }, 1.119 + { rulesrc: ["@styleset { blah: 1 -1 }"], serializationNoValueDefn: true }, 1.120 + { rulesrc: ["@styleset { blah: 1.5 }"], serializationNoValueDefn: true }, 1.121 + { rulesrc: ["@styleset { blah: 15px }"], serializationNoValueDefn: true }, 1.122 + { rulesrc: ["@styleset { blah: red }"], serializationNoValueDefn: true }, 1.123 + { rulesrc: ["@styleset { blah: (1) }"], serializationNoValueDefn: true }, 1.124 + { rulesrc: ["@styleset { blah:(1) }"], serializationNoValueDefn: true }, 1.125 + { rulesrc: ["@styleset { blah:, 1 }"], serializationNoValueDefn: true }, 1.126 + { rulesrc: ["@styleset { blah: <1> }"], serializationNoValueDefn: true }, 1.127 + { rulesrc: ["@styleset { blah: 1! }"], serializationNoValueDefn: true }, 1.128 + { rulesrc: ["@styleset { blah: 1,, }"], serializationNoValueDefn: true }, 1.129 + { rulesrc: ["@styleset { blah: 1 1 1 1; }"], serializationSame: true }, 1.130 + 1.131 + /* limits on number of values */ 1.132 + { rulesrc: ["@stylistic { blah: 1 2 }"], serializationNoValueDefn: true }, 1.133 + { rulesrc: ["@character-variant { blah: 1 2 3 }"], serializationNoValueDefn: true }, 1.134 + { rulesrc: ["@swash { blah: 1 2 }"], serializationNoValueDefn: true }, 1.135 + { rulesrc: ["@ornaments { blah: 1 2 }"], serializationNoValueDefn: true }, 1.136 + { rulesrc: ["@annotation { blah: 1 2 }"], serializationNoValueDefn: true }, 1.137 + { rulesrc: ["@styleset { blah: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; }"], serializationSame: true }, 1.138 + 1.139 + /* family names */ 1.140 + { rule: makeRule("bongo", "@styleset { blah: 1; }"), serializationSame: true }, 1.141 + { rule: makeRule("\"bongo\"", "@styleset { blah: 1; }"), serializationSame: true }, 1.142 + { rule: makeRule("'bongo'", "@styleset { blah: 1; }"), serializationSame: true }, 1.143 + { rule: makeRule("\\62 ongo", "@styleset { blah: 1; }"), serializationSame: true }, 1.144 + { rule: makeRule("bongo, super bongo, bongo the supreme", "@styleset { blah: 1; }"), serializationSame: true }, 1.145 + { rule: makeRule("bongo,, super bongo", "@styleset { blah: 1; }"), invalid: true }, 1.146 + { rule: makeRule("bongo,*", "@styleset { blah: 1; }"), invalid: true }, 1.147 + { rule: makeRule("bongo, sans-serif", "@styleset { blah: 1; }"), invalid: true }, 1.148 + { rule: makeRule("serif, sans-serif", "@styleset { blah: 1; }"), invalid: true }, 1.149 + { rule: makeRule("'serif', 'sans-serif'", "@styleset { blah: 1; }"), serializationSame: true }, 1.150 + { rule: makeRule("bongo, \"super bongo\", 'bongo the supreme'", "@styleset { blah: 1; }"), serializationSame: true }, 1.151 + { rule: makeRule("毎日カレーを食べたい!", "@styleset { blah: 1; }"), serializationSame: true }, 1.152 + { rule: makeRule("毎日カレーを食べたい!, 納豆嫌い", "@styleset { blah: 1; }"), serializationSame: true }, 1.153 + 1.154 + { rule: makeRule("bongo, \"super\" bongo, bongo the supreme", "@styleset { blah: 1; }"), invalid: true }, 1.155 + { rule: makeRule("--bongo", "@styleset { blah: 1; }"), invalid: true }, 1.156 + 1.157 + /* ident tests */ 1.158 + { rule: _("@styleset { blah: 1; blah: 1; }"), serializationSame: true }, 1.159 + { rule: _("@styleset { blah: 1; de-blah: 1; blah: 2; }"), serializationSame: true }, 1.160 + { rule: _("@styleset { \\tra-la: 1; }"), serialization: _("@styleset { tra-la: 1; }") }, 1.161 + { rule: _("@styleset { b\\lah: 1; }"), serialization: _("@styleset { blah: 1; }") }, 1.162 + { rule: _("@styleset { \\62 lah: 1; }"), serialization: _("@styleset { blah: 1; }") }, 1.163 + { rule: _("@styleset { \\:blah: 1; }"), serialization: _("@styleset { \\:blah: 1; }") }, 1.164 + { rule: _("@styleset { \\;blah: 1; }"), serialization: _("@styleset { \\;blah: 1; }") }, 1.165 + { rule: _("@styleset { complex\\20 blah: 1; }"), serialization: _("@styleset { complex\\ blah: 1; }") }, 1.166 + { rule: _("@styleset { complex\\ blah: 1; }"), serializationSame: true }, 1.167 + { rule: _("@styleset { Håkon: 1; }"), serializationSame: true }, 1.168 + { rule: _("@styleset { Åквариум: 1; }"), serializationSame: true }, 1.169 + { rule: _("@styleset { \\1f449\\1f4a9\\1f448: 1; }"), serialization: _("@styleset { 👉💩👈: 1; }") }, 1.170 + { rule: _("@styleset { 魅力: 1; }"), serializationSame: true }, 1.171 + { rule: _("@styleset { 毎日カレーを食べたい!: 1; }"), serializationSame: true }, 1.172 + /* from http://en.wikipedia.org/wiki/Metal_umlaut */ 1.173 + { rule: _("@styleset { TECHNICIÄNS\\ ÖF\\ SPÅCE\\ SHIP\\ EÅRTH\\ THIS\\ IS\\ YÖÜR\\ CÄPTÅIN\\ SPEÄKING\\ YÖÜR\\ ØÅPTÅIN\\ IS\\ DEA̋D: 1; }"), serializationSame: true }, 1.174 + 1.175 + { rulesrc: ["@styleset { 123blah: 1; }"], serializationNoValueDefn: true }, 1.176 + { rulesrc: ["@styleset { :123blah 1; }"], serializationNoValueDefn: true }, 1.177 + { rulesrc: ["@styleset { :123blah: 1; }"], serializationNoValueDefn: true }, 1.178 + { rulesrc: ["@styleset { ?123blah: 1; }"], serializationNoValueDefn: true }, 1.179 + { rulesrc: ["@styleset { \"blah\": 1; }"], serializationNoValueDefn: true }, 1.180 + { rulesrc: ["@styleset { complex blah: 1; }"], serializationNoValueDefn: true }, 1.181 + { rulesrc: ["@styleset { complex\\ blah: 1; }"], serializationNoValueDefn: true } 1.182 + 1.183 +]; 1.184 + 1.185 +// test that invalid value declarations don't affect the parsing of surrounding 1.186 +// declarations. So before + invalid + after should match the serialization 1.187 +// given in s. 1.188 + 1.189 +var gSurroundingTests = [ 1.190 + // -- invalid, valid ==> valid 1.191 + { before: "", after: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }") }, 1.192 + 1.193 + // -- valid, invalid ==> valid 1.194 + { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }", after: "", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }") }, 1.195 + 1.196 + // -- valid, invalid, valid ==> valid, valid 1.197 + { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", after: "@character-variant { whatchamacallit-2: 23 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; } @character-variant { whatchamacallit-2: 23 4; }") }, 1.198 + 1.199 + // -- invalid, valid, invalid ==> valid 1.200 + { between: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }") } 1.201 +]; 1.202 + 1.203 +/* strip out just values, along with empty value blocks (e.g. @swash { })*/ 1.204 +function valuesText(ruletext) 1.205 +{ 1.206 + var t = ruletext.replace(/@[a-zA-Z0-9\-]+[ \n]*{[ \n]*}/g, ""); 1.207 + t = t.replace(/[ \n]+/g, " "); 1.208 + t = t.replace(/^[^{]+{[ \n]*/, ""); 1.209 + t = t.replace(/[ \n]*}[^}]*$/, ""); 1.210 + t = t.replace(/[ \n]*;/g, ";"); 1.211 + return t; 1.212 +} 1.213 + 1.214 +function testParse(rulesrc) 1.215 +{ 1.216 + var sheet = document.styleSheets[1]; 1.217 + var rule = _.apply(this, rulesrc); 1.218 + 1.219 + while(sheet.cssRules.length > 0) 1.220 + sheet.deleteRule(0); 1.221 + try { 1.222 + sheet.insertRule(rule, 0); 1.223 + } catch (e) { 1.224 + return e.toString(); 1.225 + } 1.226 + 1.227 + if (sheet.cssRules.length == 1 && sheet.cssRules[0].type == kFontFeatureValuesRuleType) { 1.228 + return sheet.cssRules[0].cssText.replace(/[ \n]+/g, " "); 1.229 + } 1.230 + 1.231 + return ""; 1.232 +} 1.233 + 1.234 +function testOneRule(testrule) { 1.235 + var sheet = document.styleSheets[1]; 1.236 + var rule; 1.237 + 1.238 + if ("rulesrc" in testrule) { 1.239 + rule = _.apply(this, testrule.rulesrc); 1.240 + } else { 1.241 + rule = testrule.rule; 1.242 + } 1.243 + 1.244 + var parseErr = false; 1.245 + var expectedErr = false; 1.246 + var invalid = false; 1.247 + if ("invalid" in testrule && testrule.invalid) invalid = true; 1.248 + 1.249 + while(sheet.cssRules.length > 0) 1.250 + sheet.deleteRule(0); 1.251 + try { 1.252 + sheet.insertRule(rule, 0); 1.253 + } catch (e) { 1.254 + expectedErr = (e.name == "SyntaxError" 1.255 + && e instanceof DOMException 1.256 + && e.code == DOMException.SYNTAX_ERR 1.257 + && invalid); 1.258 + parseErr = true; 1.259 + } 1.260 + 1.261 + test(function() { 1.262 + assert_true(!parseErr || expectedErr, "unexpected syntax error"); 1.263 + if (!parseErr) { 1.264 + assert_equals(sheet.cssRules.length, 1, "bad rule count"); 1.265 + assert_equals(sheet.cssRules[0].type, kFontFeatureValuesRuleType, "bad rule type"); 1.266 + } 1.267 + }, "basic parse tests - " + rule); 1.268 + 1.269 + var sanitizedRule = rule.replace(/[ \n]+/g, " "); 1.270 + if (parseErr) { 1.271 + return; 1.272 + } 1.273 + 1.274 + // should result in one @font-feature-values rule constructed 1.275 + 1.276 + // serialization matches expectation 1.277 + // -- note: due to inconsistent font family serialization problems, 1.278 + // only the serialization of the values is tested currently 1.279 + 1.280 + var ruleValues = valuesText(rule); 1.281 + var serialized = sheet.cssRules[0].cssText; 1.282 + var serializedValues = valuesText(serialized); 1.283 + var haveSerialization = true; 1.284 + 1.285 + if (testrule.serializationSame) { 1.286 + test(function() { 1.287 + assert_equals(serializedValues, ruleValues, "canonical cssText serialization doesn't match"); 1.288 + }, "serialization check - " + rule); 1.289 + } else if ("serialization" in testrule) { 1.290 + var s = valuesText(testrule.serialization); 1.291 + test(function() { 1.292 + assert_equals(serializedValues, s, "non-canonical cssText serialization doesn't match - "); 1.293 + }, "serialization check - " + rule); 1.294 + } else if (testrule.serializationNoValueDefn) { 1.295 + test(function() { 1.296 + assert_equals(serializedValues, "", "cssText serialization should have no value defintions - "); 1.297 + }, "no value definitions in serialization - " + rule); 1.298 + 1.299 + haveSerialization = false; 1.300 + 1.301 + if ("rulesrc" in testrule) { 1.302 + test(function() { 1.303 + var j, rulesrc = testrule.rulesrc; 1.304 + 1.305 + // invalid value definitions shouldn't affect the parsing of valid 1.306 + // definitions before or after an invalid one 1.307 + for (var j = 0; j < gSurroundingTests.length; j++) { 1.308 + var t = gSurroundingTests[j]; 1.309 + var srulesrc = []; 1.310 + 1.311 + if ("between" in t) { 1.312 + srulesrc = srulesrc.concat(rulesrc); 1.313 + srulesrc = srulesrc.concat(t.between); 1.314 + srulesrc = srulesrc.concat(rulesrc); 1.315 + } else { 1.316 + if (t.before != "") 1.317 + srulesrc = srulesrc.concat(t.before); 1.318 + srulesrc = srulesrc.concat(rulesrc); 1.319 + if (t.after != "") 1.320 + srulesrc = srulesrc.concat(t.after); 1.321 + } 1.322 + 1.323 + var result = testParse(srulesrc); 1.324 + assert_equals(valuesText(result), valuesText(t.s), "invalid declarations should not affect valid ones - "); 1.325 + } 1.326 + }, "invalid declarations don't affect valid ones - " + rule); 1.327 + } 1.328 + } 1.329 + 1.330 + // if serialization non-empty, serialization should round-trip to itself 1.331 + if (haveSerialization) { 1.332 + var roundTripText = testParse([serializedValues]); 1.333 + test(function() { 1.334 + assert_equals(valuesText(roundTripText), serializedValues, 1.335 + "serialization should round-trip to itself - "); 1.336 + }, "serialization round-trip - " + rule); 1.337 + } 1.338 +} 1.339 + 1.340 +function testFontFeatureValuesRuleParsing() { 1.341 + // Gecko-specific check - if pref not set, skip these tests 1.342 + if (window.SpecialPowers && !window.SpecialPowers.getBoolPref("layout.css.font-features.enabled")) { 1.343 + return; 1.344 + } 1.345 + var i; 1.346 + for (i = 0; i < testrules.length; i++) { 1.347 + var testrule = testrules[i]; 1.348 + var rule; 1.349 + 1.350 + if ("rulesrc" in testrule) { 1.351 + rule = _.apply(this, testrule.rulesrc); 1.352 + } else { 1.353 + rule = testrule.rule; 1.354 + } 1.355 + 1.356 + testOneRule(testrule); 1.357 + //test(function() { testOneRule(testrule); }, "parsing " + rule); 1.358 + } 1.359 +} 1.360 + 1.361 +testFontFeatureValuesRuleParsing(); 1.362 +</script> 1.363 +</body></html>