Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
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>
20 <script type="text/javascript">
21 var gPrefix = "";
22 var kFontFeatureValuesRuleType = 14;
24 function ruleName() { return "@" + gPrefix + "font-feature-values"; }
25 function makeRule(f, v) {
26 return ruleName() + " " + f + " { " + v + " }";
27 }
29 function _()
30 {
31 var i, decl = [];
32 for (i = 0; i < arguments.length; i++) {
33 decl.push(arguments[i]);
34 }
35 return makeRule("bongo", decl.join(" "));
36 }
38 // note: because of bugs in the way family names are serialized,
39 // 'serializationSame' only implies that the value definition block
40 // is the same (i.e. not including the family name list)
42 var testrules = [
44 /* basic syntax */
45 { rule: ruleName() + ";", invalid: true },
46 { rule: ruleName() + " bongo;", invalid: true },
47 { rule: ruleName().replace("values", "value") + " {;}", invalid: true },
48 { rule: ruleName().replace("feature", "features") + " {;}", invalid: true },
49 { rule: makeRule("bongo", ""), serializationNoValueDefn: true },
50 { rule: makeRule("bongo", ";"), serializationNoValueDefn: true },
51 { rule: makeRule("bongo", ",;"), serializationNoValueDefn: true },
52 { rule: makeRule("bongo", ";,"), serializationNoValueDefn: true },
53 { rule: makeRule("bongo", ",;,"), serializationNoValueDefn: true },
54 { rule: makeRule("bongo", "@styleset;"), serializationNoValueDefn: true },
55 { rule: makeRule("bongo", "@styleset,;"), serializationNoValueDefn: true },
56 { rule: makeRule("bongo", "@styleset abc;"), serializationNoValueDefn: true },
57 { rule: makeRule("bongo", "@styleset { abc }"), serializationNoValueDefn: true },
58 { rule: makeRule("bongo", "@styleset { ;;abc }"), serializationNoValueDefn: true },
59 { rule: makeRule("bongo", "@styleset { abc;; }"), serializationNoValueDefn: true },
60 { rule: makeRule("bongo", "@styleset { abc: }"), serializationNoValueDefn: true },
61 { rule: makeRule("bongo", "@styleset { abc,: }"), serializationNoValueDefn: true },
62 { rule: makeRule("bongo", "@styleset { abc:, }"), serializationNoValueDefn: true },
63 { rule: makeRule("bongo", "@styleset { abc:,; }"), serializationNoValueDefn: true },
64 { rule: makeRule("bongo", "@styleset { a,b }"), serializationNoValueDefn: true },
65 { rule: makeRule("bongo", "@styleset { a;b }"), serializationNoValueDefn: true },
66 { rule: makeRule("bongo", "@styleset { a:;b: }"), serializationNoValueDefn: true },
67 { rule: makeRule("bongo", "@styleset { a:,;b: }"), serializationNoValueDefn: true },
68 { rule: makeRule("bongo", "@styleset { a:1,;b: }"), serializationNoValueDefn: true },
69 { rule: makeRule("bongo", "@styleset { abc 1 2 3 }"), serializationNoValueDefn: true },
70 { rule: makeRule("bongo", "@styleset { abc:, 1 2 3 }"), serializationNoValueDefn: true },
71 { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true },
72 { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true },
73 { rule: makeRule("bongo", "@styleset { abc: 1 2 3a }"), serializationNoValueDefn: true },
74 { rule: makeRule("bongo", "@styleset { abc: 1 2 3, def: 1; }"), serializationNoValueDefn: true },
75 { rule: makeRule("bongo", "@blah @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
76 { rule: makeRule("bongo", "@blah } @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
77 { rule: makeRule("bongo", "@blah , @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true },
78 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3; }", serialization: _("@styleset { abc: 1 2 3; }") },
79 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3 }", serialization: _("@styleset { abc: 1 2 3; }") },
80 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3;", serialization: _("@styleset { abc: 1 2 3; }") },
81 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3", serialization: _("@styleset { abc: 1 2 3; }") },
82 { rule: _("@styleset { ok-1: 1; }"), serializationSame: true },
83 { rule: _("@annotation { ok-1: 3; }"), serializationSame: true },
84 { rule: _("@stylistic { blah: 3; }"), serializationSame: true },
85 { rule: makeRule("bongo", "\n@styleset\n { blah: 3; super-blah: 4 5;\n more-blah: 5 6 7;\n }"), serializationSame: true },
86 { 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 },
88 /* limits on number of values */
89 { rule: _("@stylistic { blah: 1; }"), serializationSame: true },
90 { rule: _("@styleset { blah: 1 2 3 4; }"), serializationSame: true },
91 { rule: _("@character-variant { blah: 1 2; }"), serializationSame: true },
92 { rule: _("@swash { blah: 1; }"), serializationSame: true },
93 { rule: _("@ornaments { blah: 1; }"), serializationSame: true },
94 { rule: _("@annotation { blah: 1; }"), serializationSame: true },
96 /* values ignored when used */
97 { rule: _("@styleset { blah: 0; }"), serializationSame: true },
98 { rule: _("@styleset { blah: 120 124; }"), serializationSame: true },
99 { rule: _("@character-variant { blah: 0; }"), serializationSame: true },
100 { rule: _("@character-variant { blah: 111; }"), serializationSame: true },
101 { rule: _("@character-variant { blah: 111 13; }"), serializationSame: true },
103 /* invalid value name */
104 { rulesrc: ["styleset { blah: 1 }"], serializationNoValueDefn: true },
105 { rulesrc: ["stylistic { blah: 1 }"], serializationNoValueDefn: true },
106 { rulesrc: ["character-variant { blah: 1 }"], serializationNoValueDefn: true },
107 { rulesrc: ["swash { blah: 1 }"], serializationNoValueDefn: true },
108 { rulesrc: ["ornaments { blah: 1 }"], serializationNoValueDefn: true },
109 { rulesrc: ["annotation { blah: 1 }"], serializationNoValueDefn: true },
110 { rulesrc: ["@bongo { blah: 1 }"], serializationNoValueDefn: true },
111 { rulesrc: ["@bongo { blah: 1 2 3 }"], serializationNoValueDefn: true },
112 { rulesrc: ["@bongo { blah: 1 2 3; burp: 1;;; }"], serializationNoValueDefn: true },
114 /* values */
115 { rulesrc: ["@styleset { blah: -1 }"], serializationNoValueDefn: true },
116 { rulesrc: ["@styleset { blah: 1 -1 }"], serializationNoValueDefn: true },
117 { rulesrc: ["@styleset { blah: 1.5 }"], serializationNoValueDefn: true },
118 { rulesrc: ["@styleset { blah: 15px }"], serializationNoValueDefn: true },
119 { rulesrc: ["@styleset { blah: red }"], serializationNoValueDefn: true },
120 { rulesrc: ["@styleset { blah: (1) }"], serializationNoValueDefn: true },
121 { rulesrc: ["@styleset { blah:(1) }"], serializationNoValueDefn: true },
122 { rulesrc: ["@styleset { blah:, 1 }"], serializationNoValueDefn: true },
123 { rulesrc: ["@styleset { blah: <1> }"], serializationNoValueDefn: true },
124 { rulesrc: ["@styleset { blah: 1! }"], serializationNoValueDefn: true },
125 { rulesrc: ["@styleset { blah: 1,, }"], serializationNoValueDefn: true },
126 { rulesrc: ["@styleset { blah: 1 1 1 1; }"], serializationSame: true },
128 /* limits on number of values */
129 { rulesrc: ["@stylistic { blah: 1 2 }"], serializationNoValueDefn: true },
130 { rulesrc: ["@character-variant { blah: 1 2 3 }"], serializationNoValueDefn: true },
131 { rulesrc: ["@swash { blah: 1 2 }"], serializationNoValueDefn: true },
132 { rulesrc: ["@ornaments { blah: 1 2 }"], serializationNoValueDefn: true },
133 { rulesrc: ["@annotation { blah: 1 2 }"], serializationNoValueDefn: true },
134 { rulesrc: ["@styleset { blah: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; }"], serializationSame: true },
136 /* family names */
137 { rule: makeRule("bongo", "@styleset { blah: 1; }"), serializationSame: true },
138 { rule: makeRule("\"bongo\"", "@styleset { blah: 1; }"), serializationSame: true },
139 { rule: makeRule("'bongo'", "@styleset { blah: 1; }"), serializationSame: true },
140 { rule: makeRule("\\62 ongo", "@styleset { blah: 1; }"), serializationSame: true },
141 { rule: makeRule("bongo, super bongo, bongo the supreme", "@styleset { blah: 1; }"), serializationSame: true },
142 { rule: makeRule("bongo,, super bongo", "@styleset { blah: 1; }"), invalid: true },
143 { rule: makeRule("bongo,*", "@styleset { blah: 1; }"), invalid: true },
144 { rule: makeRule("bongo, sans-serif", "@styleset { blah: 1; }"), invalid: true },
145 { rule: makeRule("serif, sans-serif", "@styleset { blah: 1; }"), invalid: true },
146 { rule: makeRule("'serif', 'sans-serif'", "@styleset { blah: 1; }"), serializationSame: true },
147 { rule: makeRule("bongo, \"super bongo\", 'bongo the supreme'", "@styleset { blah: 1; }"), serializationSame: true },
148 { rule: makeRule("毎日カレーを食べたい!", "@styleset { blah: 1; }"), serializationSame: true },
149 { rule: makeRule("毎日カレーを食べたい!, 納豆嫌い", "@styleset { blah: 1; }"), serializationSame: true },
151 { rule: makeRule("bongo, \"super\" bongo, bongo the supreme", "@styleset { blah: 1; }"), invalid: true },
152 { rule: makeRule("--bongo", "@styleset { blah: 1; }"), invalid: true },
154 /* ident tests */
155 { rule: _("@styleset { blah: 1; blah: 1; }"), serializationSame: true },
156 { rule: _("@styleset { blah: 1; de-blah: 1; blah: 2; }"), serializationSame: true },
157 { rule: _("@styleset { \\tra-la: 1; }"), serialization: _("@styleset { tra-la: 1; }") },
158 { rule: _("@styleset { b\\lah: 1; }"), serialization: _("@styleset { blah: 1; }") },
159 { rule: _("@styleset { \\62 lah: 1; }"), serialization: _("@styleset { blah: 1; }") },
160 { rule: _("@styleset { \\:blah: 1; }"), serialization: _("@styleset { \\:blah: 1; }") },
161 { rule: _("@styleset { \\;blah: 1; }"), serialization: _("@styleset { \\;blah: 1; }") },
162 { rule: _("@styleset { complex\\20 blah: 1; }"), serialization: _("@styleset { complex\\ blah: 1; }") },
163 { rule: _("@styleset { complex\\ blah: 1; }"), serializationSame: true },
164 { rule: _("@styleset { Håkon: 1; }"), serializationSame: true },
165 { rule: _("@styleset { Åквариум: 1; }"), serializationSame: true },
166 { rule: _("@styleset { \\1f449\\1f4a9\\1f448: 1; }"), serialization: _("@styleset { 👉💩👈: 1; }") },
167 { rule: _("@styleset { 魅力: 1; }"), serializationSame: true },
168 { rule: _("@styleset { 毎日カレーを食べたい!: 1; }"), serializationSame: true },
169 /* from http://en.wikipedia.org/wiki/Metal_umlaut */
170 { 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 },
172 { rulesrc: ["@styleset { 123blah: 1; }"], serializationNoValueDefn: true },
173 { rulesrc: ["@styleset { :123blah 1; }"], serializationNoValueDefn: true },
174 { rulesrc: ["@styleset { :123blah: 1; }"], serializationNoValueDefn: true },
175 { rulesrc: ["@styleset { ?123blah: 1; }"], serializationNoValueDefn: true },
176 { rulesrc: ["@styleset { \"blah\": 1; }"], serializationNoValueDefn: true },
177 { rulesrc: ["@styleset { complex blah: 1; }"], serializationNoValueDefn: true },
178 { rulesrc: ["@styleset { complex\\ blah: 1; }"], serializationNoValueDefn: true }
180 ];
182 // test that invalid value declarations don't affect the parsing of surrounding
183 // declarations. So before + invalid + after should match the serialization
184 // given in s.
186 var gSurroundingTests = [
187 // -- invalid, valid ==> valid
188 { before: "", after: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }") },
190 // -- valid, invalid ==> valid
191 { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }", after: "", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }") },
193 // -- valid, invalid, valid ==> valid, valid
194 { 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; }") },
196 // -- invalid, valid, invalid ==> valid
197 { between: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }") }
198 ];
200 /* strip out just values, along with empty value blocks (e.g. @swash { })*/
201 function valuesText(ruletext)
202 {
203 var t = ruletext.replace(/@[a-zA-Z0-9\-]+[ \n]*{[ \n]*}/g, "");
204 t = t.replace(/[ \n]+/g, " ");
205 t = t.replace(/^[^{]+{[ \n]*/, "");
206 t = t.replace(/[ \n]*}[^}]*$/, "");
207 t = t.replace(/[ \n]*;/g, ";");
208 return t;
209 }
211 function testParse(rulesrc)
212 {
213 var sheet = document.styleSheets[1];
214 var rule = _.apply(this, rulesrc);
216 while(sheet.cssRules.length > 0)
217 sheet.deleteRule(0);
218 try {
219 sheet.insertRule(rule, 0);
220 } catch (e) {
221 return e.toString();
222 }
224 if (sheet.cssRules.length == 1 && sheet.cssRules[0].type == kFontFeatureValuesRuleType) {
225 return sheet.cssRules[0].cssText.replace(/[ \n]+/g, " ");
226 }
228 return "";
229 }
231 function testOneRule(testrule) {
232 var sheet = document.styleSheets[1];
233 var rule;
235 if ("rulesrc" in testrule) {
236 rule = _.apply(this, testrule.rulesrc);
237 } else {
238 rule = testrule.rule;
239 }
241 var parseErr = false;
242 var expectedErr = false;
243 var invalid = false;
244 if ("invalid" in testrule && testrule.invalid) invalid = true;
246 while(sheet.cssRules.length > 0)
247 sheet.deleteRule(0);
248 try {
249 sheet.insertRule(rule, 0);
250 } catch (e) {
251 expectedErr = (e.name == "SyntaxError"
252 && e instanceof DOMException
253 && e.code == DOMException.SYNTAX_ERR
254 && invalid);
255 parseErr = true;
256 }
258 test(function() {
259 assert_true(!parseErr || expectedErr, "unexpected syntax error");
260 if (!parseErr) {
261 assert_equals(sheet.cssRules.length, 1, "bad rule count");
262 assert_equals(sheet.cssRules[0].type, kFontFeatureValuesRuleType, "bad rule type");
263 }
264 }, "basic parse tests - " + rule);
266 var sanitizedRule = rule.replace(/[ \n]+/g, " ");
267 if (parseErr) {
268 return;
269 }
271 // should result in one @font-feature-values rule constructed
273 // serialization matches expectation
274 // -- note: due to inconsistent font family serialization problems,
275 // only the serialization of the values is tested currently
277 var ruleValues = valuesText(rule);
278 var serialized = sheet.cssRules[0].cssText;
279 var serializedValues = valuesText(serialized);
280 var haveSerialization = true;
282 if (testrule.serializationSame) {
283 test(function() {
284 assert_equals(serializedValues, ruleValues, "canonical cssText serialization doesn't match");
285 }, "serialization check - " + rule);
286 } else if ("serialization" in testrule) {
287 var s = valuesText(testrule.serialization);
288 test(function() {
289 assert_equals(serializedValues, s, "non-canonical cssText serialization doesn't match - ");
290 }, "serialization check - " + rule);
291 } else if (testrule.serializationNoValueDefn) {
292 test(function() {
293 assert_equals(serializedValues, "", "cssText serialization should have no value defintions - ");
294 }, "no value definitions in serialization - " + rule);
296 haveSerialization = false;
298 if ("rulesrc" in testrule) {
299 test(function() {
300 var j, rulesrc = testrule.rulesrc;
302 // invalid value definitions shouldn't affect the parsing of valid
303 // definitions before or after an invalid one
304 for (var j = 0; j < gSurroundingTests.length; j++) {
305 var t = gSurroundingTests[j];
306 var srulesrc = [];
308 if ("between" in t) {
309 srulesrc = srulesrc.concat(rulesrc);
310 srulesrc = srulesrc.concat(t.between);
311 srulesrc = srulesrc.concat(rulesrc);
312 } else {
313 if (t.before != "")
314 srulesrc = srulesrc.concat(t.before);
315 srulesrc = srulesrc.concat(rulesrc);
316 if (t.after != "")
317 srulesrc = srulesrc.concat(t.after);
318 }
320 var result = testParse(srulesrc);
321 assert_equals(valuesText(result), valuesText(t.s), "invalid declarations should not affect valid ones - ");
322 }
323 }, "invalid declarations don't affect valid ones - " + rule);
324 }
325 }
327 // if serialization non-empty, serialization should round-trip to itself
328 if (haveSerialization) {
329 var roundTripText = testParse([serializedValues]);
330 test(function() {
331 assert_equals(valuesText(roundTripText), serializedValues,
332 "serialization should round-trip to itself - ");
333 }, "serialization round-trip - " + rule);
334 }
335 }
337 function testFontFeatureValuesRuleParsing() {
338 // Gecko-specific check - if pref not set, skip these tests
339 if (window.SpecialPowers && !window.SpecialPowers.getBoolPref("layout.css.font-features.enabled")) {
340 return;
341 }
342 var i;
343 for (i = 0; i < testrules.length; i++) {
344 var testrule = testrules[i];
345 var rule;
347 if ("rulesrc" in testrule) {
348 rule = _.apply(this, testrule.rulesrc);
349 } else {
350 rule = testrule.rule;
351 }
353 testOneRule(testrule);
354 //test(function() { testOneRule(testrule); }, "parsing " + rule);
355 }
356 }
358 testFontFeatureValuesRuleParsing();
359 </script>
360 </body></html>