test_font_feature_values_parsing.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <meta charset=utf-8>
  5. <title>@font-feature-values rule parsing tests</title>
  6. <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com">
  7. <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-feature-values" />
  8. <meta name="assert" content="tests that valid @font-feature-values rules parse and invalid ones don't" />
  9. <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=549861 -->
  10. <script type="text/javascript" src="/resources/testharness.js"></script>
  11. <script type="text/javascript" src="/resources/testharnessreport.js"></script>
  12. <style type="text/css">
  13. </style>
  14. </head>
  15. <body>
  16. <div id="log"></div>
  17. <pre id="display"></pre>
  18. <style type="text/css" id="testbox"></style>
  19. <script type="text/javascript">
  20. var gPrefix = "";
  21. var kFontFeatureValuesRuleType = 14;
  22. function ruleName() { return "@" + gPrefix + "font-feature-values"; }
  23. function makeRule(f, v) {
  24. return ruleName() + " " + f + " { " + v + " }";
  25. }
  26. function _()
  27. {
  28. var i, decl = [];
  29. for (i = 0; i < arguments.length; i++) {
  30. decl.push(arguments[i]);
  31. }
  32. return makeRule("bongo", decl.join(" "));
  33. }
  34. // note: because of bugs in the way family names are serialized,
  35. // 'serializationSame' only implies that the value definition block
  36. // is the same (i.e. not including the family name list)
  37. var testrules = [
  38. /* basic syntax */
  39. { rule: ruleName() + ";", invalid: true },
  40. { rule: ruleName() + " bongo;", invalid: true },
  41. { rule: ruleName().replace("values", "value") + " {;}", invalid: true },
  42. { rule: ruleName().replace("feature", "features") + " {;}", invalid: true },
  43. { rule: makeRule("bongo", ""), serializationNoValueDefn: true },
  44. { rule: makeRule("bongo", ";"), serializationNoValueDefn: true },
  45. { rule: makeRule("bongo", ",;"), serializationNoValueDefn: true },
  46. { rule: makeRule("bongo", ";,"), serializationNoValueDefn: true },
  47. { rule: makeRule("bongo", ",;,"), serializationNoValueDefn: true },
  48. { rule: makeRule("bongo", "@styleset;"), serializationNoValueDefn: true },
  49. { rule: makeRule("bongo", "@styleset,;"), serializationNoValueDefn: true },
  50. { rule: makeRule("bongo", "@styleset abc;"), serializationNoValueDefn: true },
  51. { rule: makeRule("bongo", "@styleset { abc }"), serializationNoValueDefn: true },
  52. { rule: makeRule("bongo", "@styleset { ;;abc }"), serializationNoValueDefn: true },
  53. { rule: makeRule("bongo", "@styleset { abc;; }"), serializationNoValueDefn: true },
  54. { rule: makeRule("bongo", "@styleset { abc: }"), serializationNoValueDefn: true },
  55. { rule: makeRule("bongo", "@styleset { abc,: }"), serializationNoValueDefn: true },
  56. { rule: makeRule("bongo", "@styleset { abc:, }"), serializationNoValueDefn: true },
  57. { rule: makeRule("bongo", "@styleset { abc:,; }"), serializationNoValueDefn: true },
  58. { rule: makeRule("bongo", "@styleset { a,b }"), serializationNoValueDefn: true },
  59. { rule: makeRule("bongo", "@styleset { a;b }"), serializationNoValueDefn: true },
  60. { rule: makeRule("bongo", "@styleset { a:;b: }"), serializationNoValueDefn: true },
  61. { rule: makeRule("bongo", "@styleset { a:,;b: }"), serializationNoValueDefn: true },
  62. { rule: makeRule("bongo", "@styleset { a:1,;b: }"), serializationNoValueDefn: true },
  63. { rule: makeRule("bongo", "@styleset { abc 1 2 3 }"), serializationNoValueDefn: true },
  64. { rule: makeRule("bongo", "@styleset { abc:, 1 2 3 }"), serializationNoValueDefn: true },
  65. { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true },
  66. { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true },
  67. { rule: makeRule("bongo", "@styleset { abc: 1 2 3a }"), serializationNoValueDefn: true },
  68. { rule: makeRule("bongo", "@styleset { abc: 1 2 3, def: 1; }"), serializationNoValueDefn: true },
  69. { rule: makeRule("bongo", "@blah @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
  70. { rule: makeRule("bongo", "@blah } @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
  71. { rule: makeRule("bongo", "@blah , @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
  72. { rule: ruleName() + " bongo { @styleset { abc: 1 2 3; }", serialization: _("@styleset { abc: 1 2 3; }") },
  73. { rule: ruleName() + " bongo { @styleset { abc: 1 2 3 }", serialization: _("@styleset { abc: 1 2 3; }") },
  74. { rule: ruleName() + " bongo { @styleset { abc: 1 2 3;", serialization: _("@styleset { abc: 1 2 3; }") },
  75. { rule: ruleName() + " bongo { @styleset { abc: 1 2 3", serialization: _("@styleset { abc: 1 2 3; }") },
  76. { rule: _("@styleset { ok-1: 1; }"), serializationSame: true },
  77. { rule: _("@annotation { ok-1: 3; }"), serializationSame: true },
  78. { rule: _("@stylistic { blah: 3; }"), serializationSame: true },
  79. { rule: makeRule("bongo", "\n@styleset\n { blah: 3; super-blah: 4 5;\n more-blah: 5 6 7;\n }"), serializationSame: true },
  80. { 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 },
  81. /* limits on number of values */
  82. { rule: _("@stylistic { blah: 1; }"), serializationSame: true },
  83. { rule: _("@styleset { blah: 1 2 3 4; }"), serializationSame: true },
  84. { rule: _("@character-variant { blah: 1 2; }"), serializationSame: true },
  85. { rule: _("@swash { blah: 1; }"), serializationSame: true },
  86. { rule: _("@ornaments { blah: 1; }"), serializationSame: true },
  87. { rule: _("@annotation { blah: 1; }"), serializationSame: true },
  88. /* values ignored when used */
  89. { rule: _("@styleset { blah: 0; }"), serializationSame: true },
  90. { rule: _("@styleset { blah: 120 124; }"), serializationSame: true },
  91. { rule: _("@character-variant { blah: 0; }"), serializationSame: true },
  92. { rule: _("@character-variant { blah: 111; }"), serializationSame: true },
  93. { rule: _("@character-variant { blah: 111 13; }"), serializationSame: true },
  94. /* invalid value name */
  95. { rulesrc: ["styleset { blah: 1 }"], serializationNoValueDefn: true },
  96. { rulesrc: ["stylistic { blah: 1 }"], serializationNoValueDefn: true },
  97. { rulesrc: ["character-variant { blah: 1 }"], serializationNoValueDefn: true },
  98. { rulesrc: ["swash { blah: 1 }"], serializationNoValueDefn: true },
  99. { rulesrc: ["ornaments { blah: 1 }"], serializationNoValueDefn: true },
  100. { rulesrc: ["annotation { blah: 1 }"], serializationNoValueDefn: true },
  101. { rulesrc: ["@bongo { blah: 1 }"], serializationNoValueDefn: true },
  102. { rulesrc: ["@bongo { blah: 1 2 3 }"], serializationNoValueDefn: true },
  103. { rulesrc: ["@bongo { blah: 1 2 3; burp: 1;;; }"], serializationNoValueDefn: true },
  104. /* values */
  105. { rulesrc: ["@styleset { blah: -1 }"], serializationNoValueDefn: true },
  106. { rulesrc: ["@styleset { blah: 1 -1 }"], serializationNoValueDefn: true },
  107. { rulesrc: ["@styleset { blah: 1.5 }"], serializationNoValueDefn: true },
  108. { rulesrc: ["@styleset { blah: 15px }"], serializationNoValueDefn: true },
  109. { rulesrc: ["@styleset { blah: red }"], serializationNoValueDefn: true },
  110. { rulesrc: ["@styleset { blah: (1) }"], serializationNoValueDefn: true },
  111. { rulesrc: ["@styleset { blah:(1) }"], serializationNoValueDefn: true },
  112. { rulesrc: ["@styleset { blah:, 1 }"], serializationNoValueDefn: true },
  113. { rulesrc: ["@styleset { blah: <1> }"], serializationNoValueDefn: true },
  114. { rulesrc: ["@styleset { blah: 1! }"], serializationNoValueDefn: true },
  115. { rulesrc: ["@styleset { blah: 1,, }"], serializationNoValueDefn: true },
  116. { rulesrc: ["@styleset { blah: 1 1 1 1; }"], serializationSame: true },
  117. /* limits on number of values */
  118. { rulesrc: ["@stylistic { blah: 1 2 }"], serializationNoValueDefn: true },
  119. { rulesrc: ["@character-variant { blah: 1 2 3 }"], serializationNoValueDefn: true },
  120. { rulesrc: ["@swash { blah: 1 2 }"], serializationNoValueDefn: true },
  121. { rulesrc: ["@ornaments { blah: 1 2 }"], serializationNoValueDefn: true },
  122. { rulesrc: ["@annotation { blah: 1 2 }"], serializationNoValueDefn: true },
  123. { rulesrc: ["@styleset { blah: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; }"], serializationSame: true },
  124. /* family names */
  125. { rule: makeRule("bongo", "@styleset { blah: 1; }"), serializationSame: true },
  126. { rule: makeRule("\"bongo\"", "@styleset { blah: 1; }"), serializationSame: true },
  127. { rule: makeRule("'bongo'", "@styleset { blah: 1; }"), serializationSame: true },
  128. { rule: makeRule("\\62 ongo", "@styleset { blah: 1; }"), serializationSame: true },
  129. { rule: makeRule("bongo, super bongo, bongo the supreme", "@styleset { blah: 1; }"), serializationSame: true },
  130. { rule: makeRule("bongo,, super bongo", "@styleset { blah: 1; }"), invalid: true },
  131. { rule: makeRule("bongo,*", "@styleset { blah: 1; }"), invalid: true },
  132. { rule: makeRule("bongo, sans-serif", "@styleset { blah: 1; }"), invalid: true },
  133. { rule: makeRule("serif, sans-serif", "@styleset { blah: 1; }"), invalid: true },
  134. { rule: makeRule("'serif', 'sans-serif'", "@styleset { blah: 1; }"), serializationSame: true },
  135. { rule: makeRule("bongo, \"super bongo\", 'bongo the supreme'", "@styleset { blah: 1; }"), serializationSame: true },
  136. { rule: makeRule("毎日カレーを食べたい!", "@styleset { blah: 1; }"), serializationSame: true },
  137. { rule: makeRule("毎日カレーを食べたい!, 納豆嫌い", "@styleset { blah: 1; }"), serializationSame: true },
  138. { rule: makeRule("bongo, \"super\" bongo, bongo the supreme", "@styleset { blah: 1; }"), invalid: true },
  139. { rule: makeRule("--bongo", "@styleset { blah: 1; }"), invalid: true },
  140. /* ident tests */
  141. { rule: _("@styleset { blah: 1; blah: 1; }"), serializationSame: true },
  142. { rule: _("@styleset { blah: 1; de-blah: 1; blah: 2; }"), serializationSame: true },
  143. { rule: _("@styleset { \\tra-la: 1; }"), serialization: _("@styleset { tra-la: 1; }") },
  144. { rule: _("@styleset { b\\lah: 1; }"), serialization: _("@styleset { blah: 1; }") },
  145. { rule: _("@styleset { \\62 lah: 1; }"), serialization: _("@styleset { blah: 1; }") },
  146. { rule: _("@styleset { \\:blah: 1; }"), serialization: _("@styleset { \\:blah: 1; }") },
  147. { rule: _("@styleset { \\;blah: 1; }"), serialization: _("@styleset { \\;blah: 1; }") },
  148. { rule: _("@styleset { complex\\20 blah: 1; }"), serialization: _("@styleset { complex\\ blah: 1; }") },
  149. { rule: _("@styleset { complex\\ blah: 1; }"), serializationSame: true },
  150. { rule: _("@styleset { Håkon: 1; }"), serializationSame: true },
  151. { rule: _("@styleset { Åквариум: 1; }"), serializationSame: true },
  152. { rule: _("@styleset { \\1f449\\1f4a9\\1f448: 1; }"), serialization: _("@styleset { 👉💩👈: 1; }") },
  153. { rule: _("@styleset { 魅力: 1; }"), serializationSame: true },
  154. { rule: _("@styleset { 毎日カレーを食べたい!: 1; }"), serializationSame: true },
  155. /* from http://en.wikipedia.org/wiki/Metal_umlaut */
  156. { 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 },
  157. { rulesrc: ["@styleset { 123blah: 1; }"], serializationNoValueDefn: true },
  158. { rulesrc: ["@styleset { :123blah 1; }"], serializationNoValueDefn: true },
  159. { rulesrc: ["@styleset { :123blah: 1; }"], serializationNoValueDefn: true },
  160. { rulesrc: ["@styleset { ?123blah: 1; }"], serializationNoValueDefn: true },
  161. { rulesrc: ["@styleset { \"blah\": 1; }"], serializationNoValueDefn: true },
  162. { rulesrc: ["@styleset { complex blah: 1; }"], serializationNoValueDefn: true },
  163. { rulesrc: ["@styleset { complex\\ blah: 1; }"], serializationNoValueDefn: true }
  164. ];
  165. // test that invalid value declarations don't affect the parsing of surrounding
  166. // declarations. So before + invalid + after should match the serialization
  167. // given in s.
  168. var gSurroundingTests = [
  169. // -- invalid, valid ==> valid
  170. { before: "", after: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }") },
  171. // -- valid, invalid ==> valid
  172. { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }", after: "", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }") },
  173. // -- valid, invalid, valid ==> valid, valid
  174. { 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; }") },
  175. // -- invalid, valid, invalid ==> valid
  176. { between: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }") }
  177. ];
  178. /* strip out just values, along with empty value blocks (e.g. @swash { })*/
  179. function valuesText(ruletext)
  180. {
  181. var t = ruletext.replace(/@[a-zA-Z0-9\-]+[ \n]*{[ \n]*}/g, "");
  182. t = t.replace(/[ \n]+/g, " ");
  183. t = t.replace(/^[^{]+{[ \n]*/, "");
  184. t = t.replace(/[ \n]*}[^}]*$/, "");
  185. t = t.replace(/[ \n]*;/g, ";");
  186. return t;
  187. }
  188. function testParse(rulesrc)
  189. {
  190. var sheet = document.styleSheets[1];
  191. var rule = _.apply(this, rulesrc);
  192. while(sheet.cssRules.length > 0)
  193. sheet.deleteRule(0);
  194. try {
  195. sheet.insertRule(rule, 0);
  196. } catch (e) {
  197. return e.toString();
  198. }
  199. if (sheet.cssRules.length == 1 && sheet.cssRules[0].type == kFontFeatureValuesRuleType) {
  200. return sheet.cssRules[0].cssText.replace(/[ \n]+/g, " ");
  201. }
  202. return "";
  203. }
  204. function testOneRule(testrule) {
  205. var sheet = document.styleSheets[1];
  206. var rule;
  207. if ("rulesrc" in testrule) {
  208. rule = _.apply(this, testrule.rulesrc);
  209. } else {
  210. rule = testrule.rule;
  211. }
  212. var parseErr = false;
  213. var expectedErr = false;
  214. var invalid = false;
  215. if ("invalid" in testrule && testrule.invalid) invalid = true;
  216. while(sheet.cssRules.length > 0)
  217. sheet.deleteRule(0);
  218. try {
  219. sheet.insertRule(rule, 0);
  220. } catch (e) {
  221. expectedErr = (e.name == "SyntaxError"
  222. && e instanceof DOMException
  223. && e.code == DOMException.SYNTAX_ERR
  224. && invalid);
  225. parseErr = true;
  226. }
  227. test(function() {
  228. assert_true(!parseErr || expectedErr, "unexpected syntax error");
  229. if (!parseErr) {
  230. assert_equals(sheet.cssRules.length, 1, "bad rule count");
  231. assert_equals(sheet.cssRules[0].type, kFontFeatureValuesRuleType, "bad rule type");
  232. }
  233. }, "basic parse tests - " + rule);
  234. var sanitizedRule = rule.replace(/[ \n]+/g, " ");
  235. if (parseErr) {
  236. return;
  237. }
  238. // should result in one @font-feature-values rule constructed
  239. // serialization matches expectation
  240. // -- note: due to inconsistent font family serialization problems,
  241. // only the serialization of the values is tested currently
  242. var ruleValues = valuesText(rule);
  243. var serialized = sheet.cssRules[0].cssText;
  244. var serializedValues = valuesText(serialized);
  245. var haveSerialization = true;
  246. if (testrule.serializationSame) {
  247. test(function() {
  248. assert_equals(serializedValues, ruleValues, "canonical cssText serialization doesn't match");
  249. }, "serialization check - " + rule);
  250. } else if ("serialization" in testrule) {
  251. var s = valuesText(testrule.serialization);
  252. test(function() {
  253. assert_equals(serializedValues, s, "non-canonical cssText serialization doesn't match - ");
  254. }, "serialization check - " + rule);
  255. } else if (testrule.serializationNoValueDefn) {
  256. test(function() {
  257. assert_equals(serializedValues, "", "cssText serialization should have no value defintions - ");
  258. }, "no value definitions in serialization - " + rule);
  259. haveSerialization = false;
  260. if ("rulesrc" in testrule) {
  261. test(function() {
  262. var j, rulesrc = testrule.rulesrc;
  263. // invalid value definitions shouldn't affect the parsing of valid
  264. // definitions before or after an invalid one
  265. for (var j = 0; j < gSurroundingTests.length; j++) {
  266. var t = gSurroundingTests[j];
  267. var srulesrc = [];
  268. if ("between" in t) {
  269. srulesrc = srulesrc.concat(rulesrc);
  270. srulesrc = srulesrc.concat(t.between);
  271. srulesrc = srulesrc.concat(rulesrc);
  272. } else {
  273. if (t.before != "")
  274. srulesrc = srulesrc.concat(t.before);
  275. srulesrc = srulesrc.concat(rulesrc);
  276. if (t.after != "")
  277. srulesrc = srulesrc.concat(t.after);
  278. }
  279. var result = testParse(srulesrc);
  280. assert_equals(valuesText(result), valuesText(t.s), "invalid declarations should not affect valid ones - ");
  281. }
  282. }, "invalid declarations don't affect valid ones - " + rule);
  283. }
  284. }
  285. // if serialization non-empty, serialization should round-trip to itself
  286. if (haveSerialization) {
  287. var roundTripText = testParse([serializedValues]);
  288. test(function() {
  289. assert_equals(valuesText(roundTripText), serializedValues,
  290. "serialization should round-trip to itself - ");
  291. }, "serialization round-trip - " + rule);
  292. }
  293. }
  294. function testFontFeatureValuesRuleParsing() {
  295. var i;
  296. for (i = 0; i < testrules.length; i++) {
  297. var testrule = testrules[i];
  298. var rule;
  299. if ("rulesrc" in testrule) {
  300. rule = _.apply(this, testrule.rulesrc);
  301. } else {
  302. rule = testrule.rule;
  303. }
  304. testOneRule(testrule);
  305. //test(function() { testOneRule(testrule); }, "parsing " + rule);
  306. }
  307. }
  308. testFontFeatureValuesRuleParsing();
  309. </script>
  310. </body></html>