No bug - tagging b4d3227540c9ebc43d64aac6168fdca7019c22d8 with FIREFOX_BETA_126_BASE...
[gecko.git] / layout / style / test / test_font_loading_api.html
blob7b2a0d9e1b50a8e02ffdc8ef8d93df9852e0dd5e
1 <!DOCTYPE html>
2 <meta charset=utf-8>
3 <title>Test for the CSS Font Loading API</title>
4 <script src=/tests/SimpleTest/SimpleTest.js></script>
5 <link rel=stylesheet type=text/css href=/tests/SimpleTest/test.css>
7 <script src=descriptor_database.js></script>
9 <body onload="runTest()">
10 <iframe id=v src="file_font_loading_api_vframe.html"></iframe>
11 <iframe id=n style="display: none"></iframe>
13 <script>
14 // Map of FontFace descriptor attribute names to @font-face rule descriptor
15 // names.
16 var descriptorNames = {
17 style: "font-style",
18 weight: "font-weight",
19 stretch: "font-stretch",
20 unicodeRange: "unicode-range",
21 variant: "font-variant",
22 featureSettings: "font-feature-settings",
23 display: "font-display"
26 // Default values for the FontFace descriptor attributes other than family, as
27 // Gecko currently serializes them.
28 var defaultValues = {
29 style: "normal",
30 weight: "normal",
31 stretch: "normal",
32 unicodeRange: "U+0-10FFFF",
33 variant: "normal",
34 featureSettings: "normal",
35 display: "auto"
38 // Non-default values for the FontFace descriptor attributes other than family
39 // along with how Gecko currently serializes them. Each value is chosen to be
40 // different from the default value and also has a different serialized form.
41 var nonDefaultValues = {
42 style: ["Italic", "italic"],
43 weight: ["Bold", "bold"],
44 stretch: ["Ultra-Condensed", "ultra-condensed"],
45 unicodeRange: ["U+3??", "U+300-3FF"],
46 variant: ["Small-Caps", "small-caps"],
47 featureSettings: ["'dlig' on", "\"dlig\""],
48 display: ["Block", "block"]
51 // Invalid values for the FontFace descriptor attributes other than family.
52 var invalidValues = {
53 style: "initial",
54 weight: "bolder",
55 stretch: "wider",
56 unicodeRange: "U+1????-2????",
57 variant: "inherit",
58 featureSettings: "dlig",
59 display: "normal"
62 // Invalid font family names.
63 var invalidFontFamilyNames = [
64 "", "sans-serif", "A, B", "inherit", "a 1"
67 // Font family list where at least one is likely to be available on
68 // platforms we care about.
69 var likelyPlatformFonts = "Helvetica Neue, Bitstream Vera Sans, Bitstream Vera Sans Roman, FreeSans, Free Sans, SwissA, DejaVu Sans, Arial";
71 // Will hold an ArrayBuffer containing a valid font.
72 var fontData;
74 var queue = Promise.resolve();
76 function is_resolved_with(aPromise, aExpectedValue, aDescription, aTestID) {
77 // This assumes that all Promise tasks come from the task source.
78 var handled = false;
79 return new Promise(function(aResolve, aReject) {
80 aPromise.then(function(aValue) {
81 if (!handled) {
82 handled = true;
83 is(aValue, aExpectedValue, aDescription + " should be resolved with the expected value " + aTestID);
84 aResolve();
86 }, function(aError) {
87 if (!handled) {
88 handled = true;
89 ok(false, aDescription + " should be resolved; instead it was rejected with " + aError + " " + aTestID);
90 aResolve();
92 });
93 Promise.resolve().then(function() {
94 if (!handled) {
95 handled = true;
96 ok(false, aDescription + " should be resolved; instead it is pending " + aTestID);
97 aResolve();
99 });
103 function is_pending(aPromise, aDescription, aTestID) {
104 // This assumes that all Promise tasks come from the task source.
105 var handled = false;
106 return new Promise(function(aResolve, aReject) {
107 aPromise.then(function(aValue) {
108 if (!handled) {
109 handled = true;
110 ok(false, aDescription + " should be pending; instead it was resolved with " + aValue + " " + aTestID);
111 aResolve();
113 }, function(aError) {
114 if (!handled) {
115 handled = true;
116 ok(false, aDescription + " should be pending; instead it was rejected with " + aError + " " + aTestID);
117 aResolve();
120 Promise.resolve().then(function() {
121 if (!handled) {
122 handled = true;
123 ok(true, aDescription + " should be pending " + aTestID);
124 aResolve();
130 function fetchAsArrayBuffer(aURL) {
131 return new Promise(function(aResolve, aReject) {
132 var xhr = new XMLHttpRequest();
133 xhr.open("GET", aURL);
134 xhr.responseType = "arraybuffer";
135 xhr.onreadystatechange = function(evt) {
136 if (xhr.readyState == 4) {
137 if (xhr.status >= 200 && xhr.status <= 299) {
138 aResolve(xhr.response);
139 } else {
140 aReject(new Error("Error fetching file " + aURL + ", status " + xhr.status));
144 xhr.send();
148 function setTimeoutZero() {
149 return new Promise(function(aResolve, aReject) {
150 setTimeout(aResolve, 0);
154 function awaitRefresh() {
155 function awaitOneRefresh() {
156 return new Promise(function(aResolve, aReject) {
157 requestAnimationFrame(aResolve);
161 return awaitOneRefresh().then(awaitOneRefresh);
164 function flushStyles() {
165 getComputedStyle(document.body).width;
168 function runTest() {
169 // Document and window from inside the display:none iframe.
170 var nframe = document.getElementById("n");
171 var ndocument = nframe.contentDocument;
172 var nwindow = nframe.contentWindow;
174 // Document and window from inside the visible iframe.
175 var vframe = document.getElementById("v");
176 var vdocument = vframe.contentDocument;
177 var vwindow = vframe.contentWindow;
179 // For iterating over different combinations of documents and windows
180 // to test with.
181 var sources = [
182 { win: window, doc: document, what: "window/document" },
183 { win: vwindow, doc: vdocument, what: "vwindow/vdocument" },
184 { win: nwindow, doc: ndocument, what: "nwindow/ndocument" },
185 { win: window, doc: vdocument, what: "window/vdocument" },
186 { win: window, doc: ndocument, what: "window/ndocument" },
187 { win: vwindow, doc: document, what: "vwindow/document" },
188 { win: vwindow, doc: ndocument, what: "vwindow/ndocument" },
189 { win: nwindow, doc: document, what: "nwindow/document" },
190 { win: nwindow, doc: vdocument, what: "nwindow/vdocument" },
193 var sourceDocuments = [
194 { doc: document, what: "document" },
195 { doc: vdocument, what: "vdocument" },
196 { doc: ndocument, what: "ndocument" },
199 var sourceWindows = [
200 { win: window, what: "window" },
201 { win: vwindow, what: "vwindow" },
202 { win: nwindow, what: "nwindow" },
205 queue = queue.then(function() {
207 // First, initialize fontData.
208 return fetchAsArrayBuffer("BitPattern.woff")
209 .then(function(aResult) { fontData = aResult; });
211 }).then(function() {
213 // (TEST 1) Some miscellaneous tests for FontFaceSet and FontFace.
214 ok(window.FontFaceSet, "FontFaceSet interface object should be present (TEST 1)");
215 is(Object.getPrototypeOf(FontFaceSet.prototype), EventTarget.prototype, "FontFaceSet should inherit from EventTarget (TEST 1)");
216 ok(document.fonts instanceof FontFaceSet, "document.fonts should be a a FontFaceSet (TEST 1)");
217 ok(window.FontFace, "FontFace interface object should be present (TEST 1)");
218 is(Object.getPrototypeOf(FontFace.prototype), Object.prototype, "FontFace should inherit from Object (TEST 1)");
220 // (TEST 2) Some miscellaneous tests for FontFaceSetLoadEvent.
221 ok(window.FontFaceSetLoadEvent, "FontFaceSetLoadEvent interface object should be present (TEST 2)");
222 is(Object.getPrototypeOf(FontFaceSetLoadEvent.prototype), Event.prototype, "FontFaceSetLoadEvent should inherit from Event (TEST 2)");
224 }).then(function() {
226 // (TEST 3) Test that document.fonts.ready is resolved with the
227 // document.fonts FontFaceSet.
228 var p = Promise.resolve();
229 sourceDocuments.forEach(function({ doc, what }) {
230 p = p.then(_ => { return doc.fonts.ready }).then(function() {
231 return is_resolved_with(doc.fonts.ready, doc.fonts, "document.fonts.ready resolves with document.fonts.", "(TEST 3) (" + what + ")");
234 return p;
236 }).then(function() {
238 // (TEST 4) Test that document.fonts in this test document starts out with no
239 // FontFace objects in it.
240 sourceDocuments.forEach(function({ doc, what }) {
241 is(Array.from(doc.fonts).length, 0, "initial number of FontFace objects in document.fonts (TEST 4) (" + what + ")");
244 // (TEST 5) Test that document.fonts.status starts off as loaded.
245 sourceDocuments.forEach(function({ doc, what }) {
246 is(doc.fonts.status, "loaded", "initial value of document.fonts.status (TEST 5) (" + what + ")");
249 // (TEST 6) Test initial value of FontFace.status when a url() source is
250 // used.
251 sourceWindows.forEach(function({ win, what }) {
252 is(new win.FontFace("test", "url(x)").status, "unloaded", "initial value of FontFace.status when a url() source is used (TEST 6) (" + what + ")");
255 // (TEST 7) Test initial value of FontFace.status when an invalid
256 // ArrayBuffer source is used. Because it has an implicit initial
257 // load() call, it should either be "loading" if the browser is
258 // asynchronously parsing the font data, or "error" if it parsed
259 // it immediately.
260 sourceWindows.forEach(function({ win, what }) {
261 var status = new win.FontFace("test", new ArrayBuffer(0)).status;
262 ok(status == "loading" || status == "error", "initial value of FontFace.status when an invalid ArrayBuffer source is used (TEST 7) (" + what + ")");
265 // (TEST 8) Test initial value of FontFace.status when a valid ArrayBuffer
266 // source is used. Because it has an implicit initial load() call, it
267 // should either be "loading" if the browser is asynchronously parsing the
268 // font data, or "loaded" if it parsed it immediately.
269 sourceWindows.forEach(function({ win, what }) {
270 status = new win.FontFace("test", fontData).status;
271 ok(status == "loading" || status == "loaded", "initial value of FontFace.status when a valid ArrayBuffer source is used (TEST 8) (" + what + ")");
274 // (TEST 9) (old test became redundant with TEST 19)
276 }).then(function() {
278 // (TEST 10) Test initial value of FontFace.loaded when a valid url()
279 // source is used.
280 var p = Promise.resolve();
281 sourceWindows.forEach(function({ win, what }) {
282 p = p.then(function() {
283 return is_pending(new win.FontFace("test", "url(x)").loaded, "initial value of FontFace.loaded when a valid url() source is used", "(TEST 10) (" + what + ")");
286 return p;
288 }).then(function() {
290 // (TEST 11) (old test became redundant with TEST 21)
292 }).then(function() {
294 // (TEST 12) (old test became redundant with TEST 20)
296 }).then(function() {
298 // (TEST 13) Test initial values of the descriptor attributes on FontFace
299 // objects.
300 sourceWindows.forEach(function({ win, what }) {
301 var face = new win.FontFace("test", fontData);
302 // XXX Spec issue: what values do the descriptor attributes have before the
303 // constructor's dictionary argument is parsed?
304 for (var desc in defaultValues) {
305 is(face[desc], defaultValues[desc], "initial value of FontFace." + desc + " (TEST 13) (" + what + ")");
309 // (TEST 14) Test default values of the FontFaceDescriptors dictionary.
310 var p = Promise.resolve();
311 sourceWindows.forEach(function({ win, what }) {
312 p = p.then(function() {
313 var face = new win.FontFace("test", fontData);
314 return face.loaded.then(function() {
315 for (var desc in defaultValues) {
316 is(face[desc], defaultValues[desc], "default value of FontFace." + desc + " (TEST 14) (" + what + ")");
318 }, function(aError) {
319 ok(false, "FontFace should have loaded succesfully (TEST 14) (" + what + ")");
323 return p;
325 }).then(function() {
327 // (TEST 15) Test passing non-default descriptor values to the FontFace
328 // constructor.
329 var p = Promise.resolve();
330 sourceWindows.forEach(function({ win, what }) {
331 p = p.then(function() {
332 var descriptorTests = Promise.resolve();
333 Object.keys(nonDefaultValues).forEach(function(aDesc) {
334 descriptorTests = descriptorTests.then(function() {
335 var init = {};
336 init[aDesc] = nonDefaultValues[aDesc][0];
337 var face = new win.FontFace("test", fontData, init);
338 var ok_todo = aDesc == "variant" ? todo : ok;
339 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " immediately after construction (TEST 15) (" + what + ")");
340 return face.loaded.then(function() {
341 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " (TEST 15) (" + what + ")");
342 }, function(aError) {
343 ok(false, "FontFace should have loaded succesfully (TEST 15) (" + what + ")");
347 return descriptorTests;
350 return p;
352 }).then(function() {
354 // (TEST 16) Test passing invalid descriptor values to the FontFace
355 // constructor.
356 var p = Promise.resolve();
357 sourceWindows.forEach(function({ win, what }) {
358 p = p.then(function() {
359 var descriptorTests = Promise.resolve();
360 Object.keys(invalidValues).forEach(function(aDesc) {
361 descriptorTests = descriptorTests.then(function() {
362 var init = {};
363 init[aDesc] = invalidValues[aDesc];
364 var face = new win.FontFace("test", fontData, init);
365 var ok_todo = aDesc == "variant" ? todo : ok;
366 ok_todo(face.status == "error", "FontFace should be error immediately after construction with invalid value of FontFace." + aDesc + " (TEST 16) (" + what + ")");
367 return face.loaded.then(function() {
368 ok_todo(false, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")");
369 }, function(aError) {
370 ok(true, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")");
371 is(aError.name, "SyntaxError", "FontFace.loaded with invalid value of FontFace." + aDesc + " should be rejected with a SyntaxError (TEST 16) (" + what + ")");
375 return descriptorTests;
378 return p;
380 }).then(function() {
382 // (TEST 17) Test passing an invalid font family name to the FontFace
383 // constructor.
384 var p = Promise.resolve();
385 sourceWindows.forEach(function({ win, what }) {
386 p = p.then(function() {
387 var familyTests = Promise.resolve();
388 invalidFontFamilyNames.forEach(function(aFamilyName) {
389 familyTests = familyTests.then(function() {
390 var face = new win.FontFace(aFamilyName, fontData);
391 is(face.status, "error", "FontFace should be error immediately after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")");
392 is(face.family, "", "FontFace.family should be the empty string after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")");
393 return face.loaded.then(function() {
394 ok(false, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")");
395 }, function(aError) {
396 ok(true, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")");
397 is(aError.name, "SyntaxError", "FontFace.loaded with invalid family name " + aFamilyName + " should be rejected with a SyntaxError (TEST 17) (" + what + ")");
401 return familyTests;
404 return p;
406 }).then(function() {
408 // (TEST 18) Test passing valid url() source strings to the FontFace
409 // constructor.
410 var p = Promise.resolve();
412 // The sub-test is very fragile on Android platform, see Bug 1455824,
413 // especially Comment 34.
414 if (navigator.appVersion.includes("Android")) {
415 return p;
418 sourceWindows.forEach(function({ win, what }) {
419 p = p.then(function() {
420 var srcTests = Promise.resolve();
421 gCSSFontFaceDescriptors.src.values.forEach(function(aSrc) {
422 srcTests = srcTests.then(function() {
423 var face = new win.FontFace("test", aSrc);
424 return face.load().then(function() {
425 ok(true, "FontFace should load with valid url() src " + aSrc + " (TEST 18) (" + what + ")");
426 }, function(aError) {
427 is(aError.name, "NetworkError", "FontFace had NetworkError when loading with valid url() src " + aSrc + " (TEST 18) (" + what + ")");
431 return srcTests;
434 return p;
436 }).then(function() {
438 // (TEST 19) Test passing invalid url() source strings to the FontFace
439 // constructor.
440 var p = Promise.resolve();
441 sourceWindows.forEach(function({ win, what }) {
442 p = p.then(function() {
443 var srcTests = Promise.resolve();
444 gCSSFontFaceDescriptors.src.invalid_values.forEach(function(aSrc) {
445 srcTests = srcTests.then(function() {
446 var face = new win.FontFace("test", aSrc);
447 is(face.status, "error", "FontFace.status should be \"error\" when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
448 return face.loaded.then(function() {
449 ok(false, "FontFace should not load with invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
450 }, function(aError) {
451 is(aError.name, "SyntaxError", "FontFace.ready should have been rejected with a SyntaxError when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
455 return srcTests;
458 return p;
460 }).then(function() {
462 // (TEST 20) Test that the status of a FontFace constructed with a valid
463 // ArrayBuffer source eventually becomes "loaded".
464 var p = Promise.resolve();
465 sourceWindows.forEach(function({ win, what }) {
466 p = p.then(function() {
467 var face = new win.FontFace("test", fontData);
468 return face.loaded.then(function(aFace) {
469 is(face.status, "loaded", "status of FontFace constructed with a valid ArrayBuffer source should eventually be \"loaded\" (TEST 20) (" + what + ")");
470 is(face, aFace, "FontFace.loaded was resolved with the FontFace object once loaded (TEST 20) (" + what + ")");
471 }, function(aError) {
472 ok(false, "FontFace constructed with a valid ArrayBuffer should eventually load (TEST 20) (" + what + ")");
476 return p;
478 }).then(function() {
480 // (TEST 21) Test that the status of a FontFace constructed with an invalid
481 // ArrayBuffer source eventually becomes "error".
482 var p = Promise.resolve();
483 sourceWindows.forEach(function({ win, what }) {
484 p = p.then(function() {
485 var face = new win.FontFace("test", new ArrayBuffer(0));
486 return face.loaded.then(function() {
487 ok(false, "FontFace constructed with an invalid ArrayBuffer should not load (TEST 21) (" + what + ")");
488 }, function(aError) {
489 is(aError.name, "SyntaxError", "loaded of FontFace constructed with an invalid ArrayBuffer source should be rejected with TypeError (TEST 21) (" + what + ")");
490 is(face.status, "error", "status of FontFace constructed with an invalid ArrayBuffer source should eventually be \"error\" (TEST 21) (" + what + ")");
494 return p;
496 }).then(function() {
498 // (TEST 22) Test assigning non-default descriptor values on the FontFace.
499 var p = Promise.resolve();
500 sourceWindows.forEach(function({ win, what }) {
501 p = p.then(function() {
502 var descriptorTests = Promise.resolve();
503 Object.keys(nonDefaultValues).forEach(function(aDesc) {
504 descriptorTests = descriptorTests.then(function() {
505 var face = new win.FontFace("test", fontData);
506 return face.loaded.then(function() {
507 var ok_todo = aDesc == "variant" ? todo : ok;
508 face[aDesc] = nonDefaultValues[aDesc][0];
509 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "assigned valid non-default value to FontFace." + aDesc + " (TEST 22) (" + what + ")");
510 }, function(aError) {
511 ok(false, "FontFace should have loaded succesfully (TEST 22) (" + what + ")");
515 return descriptorTests;
518 return p;
520 }).then(function() {
522 // (TEST 23) Test assigning invalid descriptor values on the FontFace.
523 var p = Promise.resolve();
524 sourceWindows.forEach(function({ win, what }) {
525 p = p.then(function() {
526 var descriptorTests = Promise.resolve();
527 Object.keys(invalidValues).forEach(function(aDesc) {
528 descriptorTests = descriptorTests.then(function() {
529 var face = new win.FontFace("test", fontData);
530 return face.loaded.then(function() {
531 var ok_todo = aDesc == "variant" ? todo : ok;
532 var exceptionName = "";
533 try {
534 face[aDesc] = invalidValues[aDesc];
535 } catch (ex) {
536 exceptionName = ex.name;
538 ok_todo(exceptionName == "SyntaxError", "assigning invalid value to FontFace." + aDesc + " should throw a SyntaxError (TEST 23) (" + what + ")");
539 }, function(aError) {
540 ok(false, "FontFace should have loaded succesfully (TEST 23) (" + what + ")");
544 return descriptorTests;
547 return p;
549 }).then(function() {
551 // (TEST 24) Test that the status of a FontFace with a non-existing url()
552 // source is set to "loading" right after load() is called, that its .loaded
553 // Promise is returned, and that the Promise is eventually rejected with a
554 // NetworkError and its status is set to "error".
555 var p = Promise.resolve();
556 sourceWindows.forEach(function({ win, what }) {
557 p = p.then(function() {
558 var face = new win.FontFace("test", "url(x)");
559 var result = face.load();
560 is(face.status, "loading", "FontFace.status should be \"loading\" right after load() is called (TEST 24) (" + what + ")");
561 is(result, face.loaded, "FontFace.load() should return the .loaded Promise (TEST 24) (" + what + ")");
563 return result.then(function() {
564 ok(false, "FontFace with a non-existing url() source should not load (TEST 24) (" + what + ")");
565 }, function(aError) {
566 is(aError.name, "NetworkError", "FontFace with a non-existing url() source should result in its .loaded Promise being rejected with a NetworkError (TEST 24) (" + what + ")");
567 is(face.status, "error", "FontFace with a non-existing url() source should result in its .status being set to \"error\" (TEST 24) (" + what + ")");
571 return p;
573 }).then(function() {
575 // (TEST 25) Test simple manipulation of the FontFaceSet.
576 var p = Promise.resolve();
577 sources.forEach(function({ win, doc, what }) {
578 p = p.then(function() {
579 var face, face2, all;
580 face = new win.FontFace("test", "url(x)");
581 face2 = new win.FontFace("test2", "url(x)");
582 ok(!doc.fonts.has(face), "newly created FontFace should not be in document.fonts (TEST 25) (" + what + ")");
583 doc.fonts.add(face);
584 ok(doc.fonts.has(face), "should be able to add a FontFace to document.fonts (TEST 25) (" + what + ")");
585 doc.fonts.add(face);
586 ok(doc.fonts.has(face), "should be able to repeatedly add a FontFace to document.fonts (TEST 25) (" + what + ")");
587 ok(doc.fonts.delete(face), "FontFaceSet.delete should return true when it succeeds (TEST 25) (" + what + ")");
588 ok(!doc.fonts.has(face), "FontFace should be gone from document.fonts after delete is called (TEST 25) (" + what + ")");
589 ok(!doc.fonts.delete(face), "FontFaceSet.delete should return false when it fails (TEST 25) (" + what + ")");
590 doc.fonts.add(face);
591 doc.fonts.add(face2);
592 ok(doc.fonts.has(face2), "should be able to add a second FontFace to document.fonts (TEST 25) (" + what + ")");
593 doc.fonts.clear();
594 ok(!doc.fonts.has(face) && !doc.fonts.has(face2), "FontFaces should be gone from document.fonts after clear is called (TEST 25) (" + what + ")");
595 doc.fonts.add(face);
596 doc.fonts.add(face2);
597 all = Array.from(doc.fonts);
598 is(all[0], face, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")");
599 is(all[1], face2, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")");
600 doc.fonts.add(face);
601 all = Array.from(doc.fonts);
602 is(all[0], face, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")");
603 is(all[1], face2, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")");
604 doc.fonts.clear();
605 return doc.fonts.ready;
608 return p;
610 }).then(function() {
612 // (TEST 26) Test that FontFaceSet.ready is replaced, .status is set to
613 // "loading", and a loading event is dispatched when a loading FontFace is
614 // added to it.
615 var p = Promise.resolve();
616 sources.forEach(function({ win, doc, what }) {
617 p = p.then(function() {
618 var awaitEvents = new Promise(function(aResolve, aReject) {
620 var onloadingTriggered = false, loadingDispatched = false;
622 function check() {
623 if (onloadingTriggered && loadingDispatched) {
624 doc.fonts.onloading = null;
625 doc.fonts.removeEventListener("loading", listener);
626 aResolve();
630 var listener = function(aEvent) {
631 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")");
632 loadingDispatched = true;
633 check();
635 doc.fonts.addEventListener("loading", listener);
636 doc.fonts.onloading = function(aEvent) {
637 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")");
638 onloadingTriggered = true;
639 check();
643 is(doc.fonts.status, "loaded", "FontFaceSet.status initially (TEST 26) (" + what + ")");
645 var oldReady = doc.fonts.ready;
646 var face = new win.FontFace("test", "url(neverending_font_load.sjs)");
647 face.load();
648 doc.fonts.add(face);
650 var newReady = doc.fonts.ready;
651 isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when a loading FontFace is added to it (TEST 26) (" + what + ")");
652 is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when a loading FontFace is added to it (TEST 26) (" + what + ")");
654 return awaitEvents
655 .then(function() {
656 return is_pending(newReady, "FontFaceSet.ready should be replaced with a fresh pending Promise when a loading FontFace is added to it", "(TEST 26) (" + what + ")");
658 .then(function() {
659 doc.fonts.clear();
660 return doc.fonts.ready;
664 return p;
666 }).then(function() {
668 // (TEST 27) Test that FontFaceSet.ready is resolved, .status is set to
669 // "loaded", and a loadingdone event (but no loadingerror event) is
670 // dispatched when the only loading FontFace in it is removed.
671 var p = Promise.resolve();
672 sources.forEach(function({ win, doc, what }) {
673 p = p.then(function() {
674 var awaitEvents = new Promise(function(aResolve, aReject) {
676 var onloadingdoneTriggered = false, loadingdoneDispatched = false;
677 var onloadingerrorTriggered = false, loadingerrorDispatched = false;
679 function check() {
680 doc.fonts.onloadingdone = null;
681 doc.fonts.onloadingerror = null;
682 doc.fonts.removeEventListener("loadingdone", doneListener);
683 doc.fonts.removeEventListener("loadingerror", errorListener);
684 aResolve();
687 var doneListener = function(aEvent) {
688 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")");
689 is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")");
690 loadingdoneDispatched = true;
691 check();
693 doc.fonts.addEventListener("loadingdone", doneListener);
694 doc.fonts.onloadingdone = function(aEvent) {
695 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")");
696 is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")");
697 onloadingdoneTriggered = true;
698 check();
700 var errorListener = function(aEvent) {
701 loadingerrorDispatched = true;
702 check();
704 doc.fonts.addEventListener("loadingerror", errorListener);
705 doc.fonts.onloadingerror = function(aEvent) {
706 onloadingdoneTriggered = true;
707 check();
711 is(doc.fonts.status, "loaded", "FontFaceSet.status should be \"loaded\" initially (TEST 27) (" + what + ")");
713 var f = new win.FontFace("test", "url(neverending_font_load.sjs)");
714 f.load();
715 doc.fonts.add(f);
717 is(doc.fonts.status, "loading", "FontFaceSet.status should be \"loading\" when a loading FontFace is in it (TEST 27) (" + what + ")");
719 doc.fonts.clear();
721 return awaitEvents
722 .then(function() {
723 return is_resolved_with(doc.fonts.ready, doc.fonts, "FontFaceSet.ready when the FontFaceSet is cleared", "(TEST 27) (" + what + ")");
725 .then(function() {
726 is(doc.fonts.status, "loaded", "FontFaceSet.status should be set to \"loaded\" when it is cleared (TEST 27) (" + what + ")");
727 return doc.fonts.ready;
731 return p;
733 }).then(function() {
735 // (TEST 28) Test that FontFaceSet.ready is replaced, .status is set to
736 // "loading", and a loading event is dispatched when a FontFace in it
737 // starts loading.
738 var p = Promise.resolve();
739 sources.forEach(function({ win, doc, what }) {
740 p = p.then(function() {
741 var awaitEvents = new Promise(function(aResolve, aReject) {
743 var onloadingTriggered = false, loadingDispatched = false;
745 function check() {
746 if (onloadingTriggered && loadingDispatched) {
747 doc.fonts.onloading = null;
748 doc.fonts.removeEventListener("loading", listener);
749 aResolve();
753 var listener = function(aEvent) {
754 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")");
755 loadingDispatched = true;
756 check();
758 doc.fonts.addEventListener("loading", listener);
759 doc.fonts.onloading = function(aEvent) {
760 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")");
761 onloadingTriggered = true;
762 check();
766 var oldReady = doc.fonts.ready;
767 var face = new win.FontFace("test", "url(neverending_font_load.sjs)");
768 doc.fonts.add(face);
769 face.load();
771 var newReady = doc.fonts.ready;
772 isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when its only FontFace starts loading (TEST 28) (" + what + ")");
773 is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when its only FontFace starts loading (TEST 28) (" + what + ")");
775 return awaitEvents
776 .then(function() {
777 return is_pending(newReady, "FontFaceSet.ready when the FontFaceSet's only FontFace starts loading", "(TEST 28) (" + what + ")");
779 .then(function() {
780 doc.fonts.clear();
781 return doc.fonts.ready;
785 return p;
787 }).then(function() {
789 // (TEST 29) Test that a loadingdone and a loadingerror event is dispatched
790 // when a FontFace that eventually becomes status "error" is added to the
791 // FontFaceSet.
792 var p = Promise.resolve();
793 sources.forEach(function({ win, doc, what }) {
794 p = p.then(function() {
795 var face;
796 var awaitEvents = new Promise(function(aResolve, aReject) {
798 var onloadingdoneTriggered = false, loadingdoneDispatched = false;
799 var onloadingerrorTriggered = false, loadingerrorDispatched = false;
801 function check() {
802 if (onloadingdoneTriggered && loadingdoneDispatched &&
803 onloadingerrorTriggered && loadingerrorDispatched) {
804 doc.fonts.onloadingdone = null;
805 doc.fonts.onloadingerror = null;
806 doc.fonts.removeEventListener("loadingdone", doneListener);
807 doc.fonts.removeEventListener("loadingerror", errorListener);
808 aResolve();
812 var doneListener = function(aEvent) {
813 loadingdoneDispatched = true;
814 check();
816 doc.fonts.addEventListener("loadingdone", doneListener);
817 doc.fonts.onloadingdone = function(aEvent) {
818 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")");
819 is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 29) (" + what + ")");
820 onloadingdoneTriggered = true;
821 check();
823 var errorListener = function(aEvent) {
824 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingerror event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")");
825 is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 29) (" + what + ")");
826 loadingerrorDispatched = true;
827 check();
829 doc.fonts.addEventListener("loadingerror", errorListener);
830 doc.fonts.onloadingerror = function(aEvent) {
831 onloadingerrorTriggered = true;
832 check();
836 face = new win.FontFace("test", "url(x)");
837 face.load();
838 is(face.status, "loading", "FontFace should have status \"loading\" (TEST 29) (" + what + ")");
839 doc.fonts.add(face);
841 return face.loaded
842 .then(function() {
843 ok(false, "the FontFace should not load (TEST 29) (" + what + ")");
844 }, function(aError) {
845 is(face.status, "error", "FontFace should have status \"error\" (TEST 29) (" + what + ")");
846 return awaitEvents;
848 .then(function() {
849 doc.fonts.clear();
850 return doc.fonts.ready;
854 return p;
856 }).then(function() {
858 // (TEST 30) Test that a loadingdone event is dispatched when a FontFace
859 // that eventually becomes status "loaded" is added to the FontFaceSet.
860 var p = Promise.resolve();
861 sources.forEach(function({ win, doc, what }, i) {
862 p = p.then(function() {
863 var face;
864 var awaitEvents = new Promise(function(aResolve, aReject) {
866 var onloadingdoneTriggered = false, loadingdoneDispatched = false;
868 function check() {
869 if (onloadingdoneTriggered && loadingdoneDispatched) {
870 doc.fonts.onloadingdone = null;
871 doc.fonts.removeEventListener("loadingdone", doneListener);
872 aResolve();
876 var doneListener = function(aEvent) {
877 loadingdoneDispatched = true;
878 check();
880 doc.fonts.addEventListener("loadingdone", doneListener);
881 doc.fonts.onloadingdone = function(aEvent) {
882 is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 30) (" + what + ")");
883 onloadingdoneTriggered = true;
884 check();
888 face = new win.FontFace("test", "url(BitPattern.woff?test30." + i + ")");
889 face.load();
890 is(face.status, "loading", "FontFace should have status \"loading\" (TEST 30) (" + what + ")");
891 doc.fonts.add(face);
893 return face.loaded
894 .then(function() {
895 is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 30) (" + what + ")");
896 return awaitEvents;
898 .then(function() {
899 doc.fonts.clear();
903 return p;
905 }).then(function() {
907 // (TEST 31) Test that a loadingdone event is dispatched when a FontFace
908 // with status "unloaded" is added to the FontFaceSet and load() is called
909 // on it.
910 var p = Promise.resolve();
911 sources.forEach(function({ win, doc, what }, i) {
912 p = p.then(function() {
913 var face;
914 var awaitEvents = new Promise(function(aResolve, aReject) {
916 var onloadingdoneTriggered = false, loadingdoneDispatched = false;
918 function check() {
919 if (onloadingdoneTriggered && loadingdoneDispatched) {
920 doc.fonts.onloadingdone = null;
921 doc.fonts.removeEventListener("loadingdone", doneListener);
922 aResolve();
926 var doneListener = function(aEvent) {
927 loadingdoneDispatched = true;
928 check();
930 doc.fonts.addEventListener("loadingdone", doneListener);
931 doc.fonts.onloadingdone = function(aEvent) {
932 is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 31) (" + what + ")");
933 onloadingdoneTriggered = true;
934 check();
938 face = new win.FontFace("test", "url(BitPattern.woff?test31." + i + ")");
939 is(face.status, "unloaded", "FontFace should have status \"unloaded\" (TEST 31) (" + what + ")");
940 doc.fonts.add(face);
942 return face.load()
943 .then(function() {
944 return awaitEvents;
946 .then(function() {
947 is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 31) (" + what + ")");
948 doc.fonts.clear();
949 return doc.fonts.ready;
953 return p;
955 }).then(function() {
957 // (TEST 32) Test that pending restyles prevent document.fonts.status
958 // from becoming loaded.
959 var face = new FontFace("test", "url(neverending_font_load.sjs)");
960 face.load();
961 document.fonts.add(face);
963 is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace (TEST 32)");
965 document.fonts.clear();
966 flushStyles();
968 is(document.fonts.status, "loaded", "FontFaceSet.status after clearing (TEST 32)");
970 document.fonts.add(face);
972 is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace again (TEST 32)");
974 var div = document.querySelector("div");
975 div.style.color = "blue";
977 document.fonts.clear();
978 is(document.fonts.status, "loading", "FontFaceSet.status after clearing but when there is a pending restyle (TEST 32)");
980 return awaitRefresh() // wait for a refresh driver tick
981 .then(function() {
982 is(document.fonts.status, "loaded", "FontFaceSet.status after clearing and the restyle has been flushed (TEST 32)");
983 return document.fonts.ready;
986 }).then(function() {
988 // (TEST 33) Test that CSS-connected FontFace objects are created
989 // for @font-face rules in the document.
991 is(document.fonts.status, "loaded", "document.fonts.status should initially be loaded (TEST 33)");
993 var style = document.querySelector("style");
994 var ruleText = "@font-face { font-family: something; src: url(x); ";
995 Object.keys(nonDefaultValues).forEach(function(aDesc) {
996 ruleText += descriptorNames[aDesc] + ": " + nonDefaultValues[aDesc][0] + "; ";
998 ruleText += "}";
1000 style.textContent = ruleText;
1002 var rule = style.sheet.cssRules[0];
1004 var all = Array.from(document.fonts);
1005 is(all.length, 1, "document.fonts should contain one FontFace (TEST 33)");
1007 var face = all[0];
1008 is(face.family, "something", "FontFace should have correct family value (TEST 33)");
1009 Object.keys(nonDefaultValues).forEach(function(aDesc) {
1010 var ok_todo = aDesc == "variant" ? todo : ok;
1011 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "FontFace should have correct " + aDesc + " value (TEST 33)");
1014 is(document.fonts.status, "loaded", "document.fonts.status should still be loaded (TEST 33)");
1015 is(face.status, "unloaded", "FontFace.status should be unloaded (TEST 33)");
1017 document.fonts.clear();
1018 ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when clear is called (TEST 33)");
1020 is(document.fonts.delete(face), false, "attempting to remove CSS-connected FontFace from document.fonts should return false (TEST 33)");
1021 ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when delete is called (TEST 33)");
1023 style.textContent = "";
1025 ok(!document.fonts.has(face), "CSS-connected FontFace should be removed from document.fonts once the rule has been removed (TEST 33)");
1027 is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after rule is removed (TEST 33)");
1028 is(face.status, "unloaded", "FontFace.status should still be unloaded after rule is removed (TEST 33)");
1030 document.fonts.add(face);
1031 ok(document.fonts.has(face), "previously CSS-connected FontFace should be able to be added to document.fonts (TEST 33)");
1033 is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after now disconnected FontFace is added (TEST 33)");
1034 is(face.status, "unloaded", "FontFace.status should still be unloaded after now disconnected FontFace is added (TEST 33)");
1036 document.fonts.delete(face);
1037 ok(!document.fonts.has(face), "previously CSS-connected FontFace should be able to be removed from document.fonts (TEST 33)");
1039 }).then(function() {
1041 // (TEST 34) Test that descriptor getters for unspecified descriptors on
1042 // CSS-connected FontFace objects return their default values.
1043 var style = document.querySelector("style");
1044 var ruleText = "@font-face { font-family: something; src: url(x); }";
1046 style.textContent = ruleText;
1048 var all = Array.from(document.fonts);
1049 var face = all[0];
1051 Object.keys(defaultValues).forEach(function(aDesc) {
1052 is(face[aDesc], defaultValues[aDesc], "FontFace should return default value for " + aDesc + " (TEST 34)");
1055 style.textContent = "";
1057 }).then(function() {
1059 // (TEST 35) Test that no loadingdone event is dispatched when a FontFace
1060 // with "loaded" status is added to a "loaded" FontFaceSet.
1061 var p = Promise.resolve();
1062 sources.forEach(function({ win, doc, what }) {
1063 p = p.then(function() {
1064 var gotLoadingDone = false;
1065 doc.fonts.onloadingdone = function(aEvent) {
1066 gotLoadingDone = true;
1069 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 35) (" + what + ")");
1070 var face = new win.FontFace("test", fontData);
1072 return face.loaded
1073 .then(function() {
1074 is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 35) (" + what + ")");
1075 doc.fonts.add(face);
1076 is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 35) (" + what + ")");
1077 return doc.fonts.ready;
1079 .then(function() {
1080 ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 35) (" + what + ")");
1081 doc.fonts.onloadingdone = null;
1082 doc.fonts.clear();
1086 return p;
1088 }).then(function() {
1090 // (TEST 36) Test that no loadingdone or loadingerror event is dispatched
1091 // when a FontFace with "error" status is added to a "loaded" FontFaceSet.
1092 var p = Promise.resolve();
1093 sources.forEach(function({ win, doc, what }) {
1094 var doc = win.document;
1095 p = p.then(function() {
1096 var gotLoadingDone = false, gotLoadingError = false;
1097 doc.fonts.onloadingdone = function(aEvent) {
1098 gotLoadingDone = true;
1100 doc.fonts.onloadingerror = function(aEvent) {
1101 gotLoadingError = true;
1104 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 36) (" + what + ")");
1105 var face = new win.FontFace("test", new ArrayBuffer(0));
1107 return face.loaded
1108 .then(function() {
1109 ok(false, "FontFace should not have loaded (TEST 36) (" + what + ")");
1110 }, function() {
1111 is(face.status, "error", "FontFace should have status \"error\" (TEST 36) (" + what + ")");
1112 doc.fonts.add(face);
1113 is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 36) (" + what + ")");
1114 return doc.fonts.ready;
1116 .then(function() {
1117 ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 36) (" + what + ")");
1118 ok(!gotLoadingError, "loadingerror event should not be dispatched (TEST 36) (" + what + ")");
1119 doc.fonts.onloadingdone = null;
1120 doc.fonts.onloadingerror = null;
1121 doc.fonts.clear();
1125 return p;
1127 }).then(function() {
1129 // (TEST 37) Test that a FontFace only has one loadingdone event dispatched
1130 // at the FontFaceSet containing it.
1132 var p = Promise.resolve();
1133 sources.forEach(function({ win, doc, what}, i) {
1134 p = p.then(function() {
1135 return setTimeoutZero(); // wait for any previous events to be dispatched
1136 }).then(function() {
1137 var events = [], face, face2;
1139 var awaitEvents = new Promise(function(aResolve, aReject) {
1140 doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) {
1141 events.push(e);
1142 if (events.length == 2) {
1143 aResolve();
1148 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 37) (" + what + ")");
1150 face = new win.FontFace("test", "url(BitPattern.woff?test37." + i + "a)");
1151 face.load();
1152 doc.fonts.add(face);
1153 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 37) (" + what + ")");
1155 return doc.fonts.ready
1156 .then(function() {
1157 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 37) (" + what + ")");
1158 is(face.status, "loaded", "first FontFace should have status \"loaded\" (TEST 37) (" + what + ")");
1160 face2 = new win.FontFace("test2", "url(BitPattern.woff?test37." + i + "b)");
1161 face2.load();
1162 doc.fonts.add(face2);
1163 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 37) (" + what + ")");
1165 return doc.fonts.ready;
1166 }).then(function() {
1167 return awaitEvents;
1168 }).then(function() {
1169 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 37) (" + what + ")");
1170 is(face2.status, "loaded", "second FontFace should have status \"loaded\" (TEST 37) (" + what + ")");
1172 is(events.length, 2, "should receive two events (TEST 37) (" + what + ")");
1174 is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 37) (" + what + ")");
1175 is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 37) (" + what + ")");
1176 is(events[0].fontfaces[0], face, "first event should have the first FontFace");
1178 is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 37) (" + what + ")");
1179 is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 37) (" + what + ")");
1180 is(events[1].fontfaces[0], face2, "second event should have the second FontFace (TEST 37) (" + what + ")");
1182 doc.fonts.onloadingdone = null;
1183 doc.fonts.onloadingerror = null;
1184 doc.fonts.clear();
1185 return doc.fonts.ready;
1189 return p;
1191 }).then(function() {
1193 // (TEST 38) Test that a FontFace only has one loadingerror event dispatched
1194 // at the FontFaceSet containing it.
1196 var p = Promise.resolve();
1197 sources.forEach(function({ win, doc, what }) {
1198 p = p.then(function() {
1199 return setTimeoutZero(); // wait for any previous events to be dispatched
1200 }).then(function() {
1201 var events = [], face, face2;
1203 var awaitEvents = new Promise(function(aResolve, aReject) {
1204 doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) {
1205 events.push(e);
1206 if (events.length == 4) {
1207 aResolve();
1212 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 38) (" + what + ")");
1214 face = new win.FontFace("test", "url(x)");
1215 face.load();
1216 doc.fonts.add(face);
1217 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 38) (" + what + ")");
1219 return doc.fonts.ready
1220 .then(function() {
1221 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font failed to load (TEST 38) (" + what + ")");
1222 is(face.status, "error", "first FontFace should have status \"error\" (TEST 38) (" + what + ")");
1224 face2 = new win.FontFace("test2", "url(x)");
1225 face2.load();
1226 doc.fonts.add(face2);
1227 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 38) (" + what + ")");
1229 return doc.fonts.ready;
1230 }).then(function() {
1231 return awaitEvents;
1232 }).then(function() {
1233 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font failed to load (TEST 38) (" + what + ")");
1234 is(face2.status, "error", "second FontFace should have status \"error\" (TEST 38) (" + what + ")");
1236 is(events.length, 4, "should receive four events (TEST 38) (" + what + ")");
1238 is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 38) (" + what + ")");
1239 is(events[0].fontfaces.length, 0, "first event should have no FontFaces (TEST 38) (" + what + ")");
1241 is(events[1].type, "loadingerror", "second event should be \"loadingerror\" (TEST 38) (" + what + ")");
1242 is(events[1].fontfaces.length, 1, "second event should have 1 FontFace (TEST 38) (" + what + ")");
1243 is(events[1].fontfaces[0], face, "second event should have the first FontFace");
1245 is(events[2].type, "loadingdone", "third event should be \"loadingdone\" (TEST 38) (" + what + ")");
1246 is(events[2].fontfaces.length, 0, "third event should have no FontFaces (TEST 38) (" + what + ")");
1248 is(events[3].type, "loadingerror", "third event should be \"loadingerror\" (TEST 38) (" + what + ")");
1249 is(events[3].fontfaces.length, 1, "third event should only have 1 FontFace (TEST 38) (" + what + ")");
1250 is(events[3].fontfaces[0], face2, "third event should have the second FontFace");
1252 doc.fonts.onloadingdone = null;
1253 doc.fonts.onloadingerror = null;
1254 doc.fonts.clear();
1255 return doc.fonts.ready;
1259 return p;
1261 }).then(function() {
1263 // (TEST 39) Test that a FontFace for an @font-face rule only has one
1264 // loadingdone event dispatched at the FontFaceSet containing it.
1266 var style, all, events, awaitEvents;
1268 return setTimeoutZero() // wait for any previous events to be dispatched
1269 .then(function() {
1270 style = document.querySelector("style");
1271 var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff?test39a); } " +
1272 "@font-face { font-family: test2; src: url(BitPattern.woff?test39b); }";
1274 style.textContent = ruleText;
1276 all = Array.from(document.fonts);
1277 events = [];
1279 awaitEvents = new Promise(function(aResolve, aReject) {
1280 document.fonts.onloadingdone = document.fonts.onloadingerror = function(e) {
1281 events.push(e);
1282 if (events.length == 2) {
1283 aResolve();
1288 is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 39)");
1290 all[0].load();
1291 is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font loading (TEST 39)");
1293 return document.fonts.ready
1294 }).then(function() {
1295 is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 39)");
1296 is(all[0].status, "loaded", "first FontFace should have status \"loaded\" (TEST 39)");
1297 is(all[1].status, "unloaded", "second FontFace should have status \"unloaded\" (TEST 39)");
1299 all[1].load();
1300 is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font loading (TEST 39)");
1302 return document.fonts.ready;
1303 }).then(function() {
1304 return awaitEvents;
1305 }).then(function() {
1306 is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 39)");
1307 is(all[1].status, "loaded", "second FontFace should have status \"loaded\" (TEST 39)");
1309 is(events.length, 2, "should receive two events (TEST 39)");
1311 is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 39)");
1312 is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 39)");
1313 is(events[0].fontfaces[0], all[0], "first event should have the first FontFace");
1315 is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 39)");
1316 is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 39)");
1317 is(events[1].fontfaces[0], all[1], "second event should have the second FontFace (TEST 39)");
1319 style.textContent = "";
1321 document.fonts.onloadingdone = null;
1322 document.fonts.onloadingerror = null;
1323 document.fonts.clear();
1324 return document.fonts.ready;
1327 }).then(function() {
1329 // (TEST 40) Test that an attempt to add the same FontFace object a second
1330 // time to a FontFaceSet (where one of the FontFace objects is reflecting
1331 // an @font-face rule) will be ignored.
1333 // First set up a @font-face rule.
1334 var style = document.querySelector("style");
1335 style.textContent = "@font-face { font-family: something; src: url(x); }";
1337 // Then add a couple of non-connected FontFace objects.
1338 var f1 = new FontFace("test1", "url(x)");
1339 var f2 = new FontFace("test2", "url(x)");
1341 document.fonts.add(f1);
1342 document.fonts.add(f2);
1344 var all = Array.from(document.fonts);
1345 var ruleFontFace = all[0];
1347 is(all.length, 3, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 40)");
1348 is(all[1], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)");
1349 is(all[2], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)");
1351 document.fonts.add(f1);
1353 all = Array.from(document.fonts);
1354 is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #1 (TEST 40)");
1355 is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #1 (TEST 40)");
1356 is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)");
1357 is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)");
1359 document.fonts.add(ruleFontFace);
1361 all = Array.from(document.fonts);
1362 is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #2 (TEST 40)");
1363 is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #2 (TEST 40)");
1364 is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)");
1365 is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)");
1367 style.textContent = "";
1369 document.fonts.clear();
1371 }).then(function() {
1373 // (TEST 41) Test that an attempt to add the same FontFace object a second
1374 // time to a FontFaceSet (where none of the FontFace objects are reflecting
1375 // an @font-face rule) will be ignored.
1377 sources.forEach(function({ win, doc, what }) {
1378 // Add a couple of non-connected FontFace objects.
1379 var f1 = new win.FontFace("test1", "url(x)");
1380 var f2 = new win.FontFace("test2", "url(x)");
1382 doc.fonts.add(f1);
1383 doc.fonts.add(f2);
1385 var all = Array.from(doc.fonts);
1387 is(all.length, 2, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
1388 is(all[0], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
1389 is(all[1], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
1391 doc.fonts.add(f1);
1393 all = Array.from(doc.fonts);
1394 is(all.length, 2, "number of FontFace objects in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
1395 is(all[0], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
1396 is(all[1], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
1398 doc.fonts.clear();
1401 }).then(function() {
1403 // (TEST 42) Test that adding a FontFace to multiple FontFaceSets and then
1404 // loading it updates the status of all FontFaceSets.
1406 var face = new FontFace("test", "url(x)");
1408 sourceDocuments.forEach(function({ doc, what }) {
1409 doc.fonts.add(face);
1412 sourceDocuments.forEach(function({ doc, what }) {
1413 is(doc.fonts.status, "loaded", what + ".fonts.status before loading (TEST 42)");
1416 face.load();
1418 sourceDocuments.forEach(function({ doc, what }) {
1419 is(doc.fonts.status, "loading", what + ".fonts.status after loading started (TEST 42)");
1422 return Promise.all(sourceDocuments.map(function({ doc }) { return doc.fonts.ready; }))
1423 .then(function() {
1424 is(face.status, "error", "FontFace.status after loading finished (TEST 42)");
1425 sourceDocuments.forEach(function({ doc, what }) {
1426 is(doc.fonts.status, "loaded", what + ".fonts.status after loading finished (TEST 42)");
1429 sourceDocuments.forEach(function({ doc, what }) {
1430 doc.fonts.clear();
1434 }).then(function() {
1436 // (TEST 43) Test the check method with platform fonts and some
1437 // degenerate cases.
1439 sourceDocuments.forEach(function({ doc, what }) {
1440 // Invalid font shorthands should throw a SyntaxError.
1441 try {
1442 doc.fonts.check("Helvetica");
1443 ok(false, "check should throw when a syntactically invalid font shorthand is given (TEST 43) (" + what + ")");
1444 } catch (ex) {
1445 is(ex.name, "SyntaxError", "exception name when check is called with a syntactically invalid font shorthand (TEST 43) (" + what + ")");
1448 // System fonts should throw a SyntaxError.
1449 try {
1450 doc.fonts.check("caption");
1451 ok(false, "check should throw when a system font value is given (TEST 43) (" + what + ")");
1452 } catch (ex) {
1453 is(ex.name, "SyntaxError", "exception name when check is called with a system font value (TEST 43) (" + what + ")");
1456 // CSS-wide keywords should throw a SyntaxError.
1457 try {
1458 doc.fonts.check("inherit");
1459 ok(false, "check should throw when a CSS-wide keyword is given (TEST 43) (" + what + ")");
1460 } catch (ex) {
1461 is(ex.name, "SyntaxError", "exception name when check is called with a CSS-wide keyword (TEST 43) (" + what + ")");
1464 // CSS variables should throw a SyntaxError.
1465 try {
1466 doc.fonts.check("16px var(--family)");
1467 ok(false, "check should throw when CSS variables are used (TEST 43) (" + what + ")");
1468 } catch (ex) {
1469 is(ex.name, "SyntaxError", "exception name when check is called with CSS variables (TEST 43) (" + what + ")");
1472 // No matching font family names => return true.
1473 is(doc.fonts.check("16px NonExistentFont1, NonExistentFont2"), true, "check return value when no matching font family names are used (TEST 43) (" + what + ")");
1475 // Matching platform font family name => return true.
1476 is(doc.fonts.check("16px NonExistentFont, " + likelyPlatformFonts), true, "check return value when a matching platform font family name is used (TEST 43) (" + what + ")");
1478 // Matching platform font family name, but using a different test
1479 // strings. (Platform fonts always return true from check, regardless
1480 // of the actual glyphs present.)
1482 { test: "\0", desc: "a single non-matching glyph" },
1483 { test: "A\0", desc: "a matching and a non-matching glyph" },
1484 { test: "A", desc: "a matching glyph" },
1485 { test: "AB", desc: "multiple matching glyphs" }
1486 ].forEach(function({ test, desc }) {
1487 is(doc.fonts.check("16px " + likelyPlatformFonts, test), true, "check return value when a matching platform font family name is used but with " + desc + " (TEST 43) (" + what + ")");
1490 // No matching font family name, but an empty test string.
1491 is(doc.fonts.check("16px NonExistentFont", ""), true, "check return value with a non-matching font family name and an empty test string (TEST 43) (" + what + ")");
1493 // Matching platform font family name, but empty test string.
1494 is(doc.fonts.check("16px " + likelyPlatformFonts, ""), true, "check return value with an empty test string (TEST 43) (" + what + ")");
1497 }).then(function() {
1499 // (TEST 44) Test the check method with script-created FontFaces.
1501 var tests = [
1502 // at least one matching FontFace is not loaded ==> false
1503 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }] },
1504 { result: false, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded" }] },
1505 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "loaded" }] },
1506 { result: false, font: "16px Test", faces: [{ family: "Test", status: "loading" }] },
1507 { result: false, font: "16px Test", faces: [{ family: "Test", status: "error" }] },
1508 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic" }] },
1509 { result: false, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold" }] },
1510 { result: false, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] },
1511 { result: false, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] },
1513 // all matching FontFaces are loaded ==> true
1514 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }] },
1515 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "loaded" }] },
1516 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
1517 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] },
1518 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Irrelevant", status: "unloaded" }] },
1519 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic" }] },
1520 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }] },
1521 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed" }] },
1522 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }, { family: "Test", status: "unloaded", weight: "600" }] },
1523 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "loaded" }] },
1525 // no matching FontFaces at all ==> true
1526 { result: true, font: "16px Test", faces: [] },
1527 { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] },
1528 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
1529 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] },
1531 // matching FontFace for one sample text character is loaded but
1532 // not the other ==> false
1533 { result: false, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "unloaded", unicodeRange: "U+62" }] },
1535 // matching FontFaces for separate sample text characters are all
1536 // loaded ==> true
1537 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "loaded", unicodeRange: "U+62" }] },
1540 sources.forEach(function({ win, doc, what }, i) {
1541 tests.forEach(function({ result, font, faces }, j) {
1542 faces.forEach(function(f, k) {
1543 var fontFace;
1544 if (f.status == "loaded") {
1545 fontFace = new win.FontFace(f.family, fontData, f);
1546 } else if (f.status == "error") {
1547 fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f);
1548 } else {
1549 fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test44." + [i, j, k] + ")", f);
1550 if (f.status == "loading") {
1551 fontFace.load();
1554 is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 44) (" + what + ")");
1555 doc.fonts.add(fontFace);
1557 is(doc.fonts.check(font, "ab"), result, "check return value for subtest " + j + " (TEST 44) (" + what + ")");
1558 doc.fonts.clear();
1562 }).then(function() {
1564 // (TEST 45) Test the load method with platform fonts and some
1565 // degenerate cases.
1567 var p = Promise.resolve();
1568 sources.forEach(function({ win, doc, what }) {
1569 p = p.then(function() {
1570 // Invalid font shorthands should reject the promise with a SyntaxError.
1571 return doc.fonts.load("Helvetica").then(function() {
1572 ok(false, "load should reject when a syntactically invalid font shorthand is given (TEST 45) (" + what + ")");
1573 }, function(ex) {
1574 is(ex.name, "SyntaxError", "exception name when load is called with a syntactically invalid font shorthand (TEST 45) (" + what + ")");
1578 p = p.then(function() {
1579 // System fonts should reject with a SyntaxError.
1580 return doc.fonts.load("caption").then(function() {
1581 ok(false, "load should throw when a system font value is given (TEST 45) (" + what + ")");
1582 }, function(ex) {
1583 is(ex.name, "SyntaxError", "exception name when load is called with a system font value (TEST 45) (" + what + ")");
1587 p = p.then(function() {
1588 // CSS-wide keywords should reject with a SyntaxError.
1589 return doc.fonts.load("inherit").then(function() {
1590 ok(false, "load should throw when a CSS-wide keyword is given (TEST 45) (" + what + ")");
1591 }, function(ex) {
1592 is(ex.name, "SyntaxError", "exception name when load is called with a CSS-wide keyword (TEST 45) (" + what + ")");
1596 p = p.then(function() {
1597 // CSS variables should throw a SyntaxError.
1598 return doc.fonts.load("16px var(--family)").then(function() {
1599 ok(false, "load should throw when CSS variables are used (TEST 45) (" + what + ")");
1600 }, function(ex) {
1601 is(ex.name, "SyntaxError", "exception name when load is called with CSS variables (TEST 45) (" + what + ")");
1605 p = p.then(function() {
1606 // No matching font family names => return true.
1607 return doc.fonts.load("16px NonExistentFont1, NonExistentFont2").then(function(result) {
1608 is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")");
1612 p = p.then(function() {
1613 // Matching platform font family name => return true.
1614 return doc.fonts.load("16px NonExistentFont1, " + likelyPlatformFonts).then(function(result) {
1615 is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")");
1619 // Matching platform font family name, but using a different test
1620 // strings. (Platform fonts always return true from load, regardless
1621 // of the actual glyphs present.)
1623 { sample: "\0", desc: "a single non-matching glyph" },
1624 { sample: "A\0", desc: "a matching and a non-matching glyph" },
1625 { sample: "A", desc: "a matching glyph" },
1626 { sample: "AB", desc: "multiple matching glyphs" }
1627 ].forEach(function({ sample, desc }) {
1628 p = p.then(function() {
1629 return doc.fonts.load("16px " + likelyPlatformFonts, sample).then(function(result) {
1630 is(result.length, 0, "load resolves with an empty array when a matching platform font family name is used but with " + desc + " (TEST 45) (" + what + ")");
1635 p = p.then(function() {
1636 // No matching font family name, but an empty test string.
1637 return doc.fonts.load("16px NonExistentFont", "").then(function(result) {
1638 is(result.length, 0, "load resolves with an empty array when a non-matching platform font family name and an empty test string is used (TEST 45) (" + what + ")");
1642 p = p.then(function() {
1643 // Matching font family name, but an empty test string.
1644 return doc.fonts.load("16px " + likelyPlatformFonts, "").then(function(result) {
1645 is(result.length, 0, "load resolves with an empty array when a matching platform font family name and an empty test string is used (TEST 45) (" + what + ")");
1649 return p;
1651 }).then(function() {
1653 // (TEST 46) Test the load method with script-created FontFaces.
1655 var tests = [
1656 // at least one matching FontFace is not yet loaded, but will load ==> resolve
1657 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }] },
1658 { result: true, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded", included: true }] },
1659 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }, { family: "Test", status: "loaded", included: true }] },
1660 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loading", included: true }] },
1661 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic", included: true }] },
1662 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600", included: true }] },
1663 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold", included: true }] },
1664 { result: true, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] },
1665 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] },
1667 // at least one matching FontFace is in an error state ==> reject
1668 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "error" }] },
1670 // all matching FontFaces are already loaded ==> resolve
1671 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }] },
1672 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "loaded", included: true }] },
1673 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
1674 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] },
1675 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Irrelevant", status: "unloaded" }] },
1676 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic", included: true }] },
1677 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }] },
1678 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed", included: true }] },
1679 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }, { family: "Test", status: "loaded", weight: "600" }] },
1680 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "loaded", weight: "bold", included: true }] },
1681 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "loaded", included: true }] },
1683 // no matching FontFaces at all ==> resolve
1684 { result: true, font: "16px Test", faces: [] },
1685 { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] },
1686 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
1687 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] },
1689 // matching FontFace for one sample text character is already loaded but
1690 // the other is not (but will) ==> resolve
1691 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+62", included: true }] },
1693 // matching FontFaces for separate sample text characters are all
1694 // loaded ==> resolve
1695 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "loaded", unicodeRange: "U+62", included: true }] },
1698 var p = Promise.resolve();
1699 sources.forEach(function({ win, doc, what }, i) {
1700 tests.forEach(function({ result, font, faces }, j) {
1701 p = p.then(function() {
1702 var fontFaces = [];
1703 faces.forEach(function(f, k) {
1704 var fontFace;
1705 if (f.status == "loaded") {
1706 fontFace = new win.FontFace(f.family, fontData, f);
1707 } else if (f.status == "error") {
1708 fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f);
1709 } else {
1710 fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test46." + [i, j, k] + ")", f);
1711 if (f.status == "loading") {
1712 fontFace.load();
1715 is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 46) (" + what + ")");
1716 doc.fonts.add(fontFace);
1717 fontFaces.push(fontFace);
1719 return doc.fonts.load(font, "ab").then(function(array) {
1720 ok(result, "load should resolve for subtest " + j + " (TEST 46) (" + what + ")");
1721 var expected = [];
1722 for (var k = 0; k < faces.length; k++) {
1723 if (faces[k].included) {
1724 expected.push(fontFaces[k]);
1727 is(array.length, expected.length, "length of array load resolves with for subtest " + j + " (TEST 46) (" + what + ")");
1728 for (var k = 0; k < array.length; k++) {
1729 is(array[k], expected[k], "value in array[" + k + "] load resolves with for subtest " + j + " (TEST 46) (" + what + ")");
1731 }, function(ex) {
1732 ok(!result, "load should not resolve for subtest " + j + " (TEST 46) (" + what + ")");
1733 is(ex.name, "SyntaxError", "exception load's return value is rejected with for subtest " + j + " (TEST 46) (" + what + ")");
1734 }).then(function() {
1735 doc.fonts.clear();
1740 return p;
1742 }).then(function() {
1744 // (TEST 47) Test that CSS-connected FontFaces can't be added to other
1745 // FontFaceSets.
1747 var style = document.querySelector("style");
1748 style.textContent = "@font-face { font-family: something; src: url(x); }";
1750 var rule = style.sheet.cssRules[0];
1752 var all = Array.from(document.fonts);
1753 is(all.length, 1, "document.fonts should contain one FontFace (TEST 47)");
1755 var face = all[0];
1757 sourceDocuments.forEach(function({ doc, what }) {
1758 if (doc == document) {
1759 return;
1762 var exceptionName;
1763 try {
1764 doc.fonts.add(face);
1765 ok(false, "add should throw when attempting to add a CSS-connected FontFace to another FontFaceSet (TEST 47) (" + what + ")");
1766 } catch (ex) {
1767 is(ex.name, "InvalidModificationError", "exception name when add is called with a CSS-connected FontFace from another FontFaceSet (TEST 47) (" + what + ")");
1771 style.textContent = "";
1772 document.body.offsetTop;
1774 sourceDocuments.forEach(function({ doc, what }) {
1775 if (doc == document) {
1776 return;
1779 ok(!doc.fonts.has(face), "FontFaceSet initially doesn't have the FontFace (TEST 47) (" + what + ")");
1780 doc.fonts.add(face);
1781 ok(doc.fonts.has(face), "add should allow a previously CSS-connected FontFace to be added to another FontFaceSet (TEST 47) (" + what + ")");
1782 doc.fonts.clear();
1785 document.fonts.clear();
1787 }).then(function() {
1789 // (TEST 48) Test that FontFaceSets that hold a combination of FontFaces
1790 // from different documents expose the right set of FontFaces.
1792 // Expected FontFaceSet contents.
1793 var expected = {
1794 document: [],
1795 vdocument: [],
1796 ndocument: [],
1799 // Create a CSS-connected FontFace in the top-level document.
1800 var style = document.querySelector("style");
1801 style.textContent = "@font-face { font-family: something; src: url(x); }";
1803 var all = Array.from(document.fonts);
1804 is(all.length, 1, "document.fonts should contain one FontFace (TEST 48)");
1806 all[0]._description = "CSS-connected in document";
1807 expected.document.push(all[0]);
1809 // Create a CSS-connected FontFace in the visible iframe.
1810 var vstyle = vdocument.querySelector("style");
1811 vstyle.textContent = "@font-face { font-family: somethingelse; src: url(x); }";
1813 all = Array.from(vdocument.fonts);
1814 all[0]._description = "CSS-connected in vdocument";
1815 is(all.length, 1, "vdocument.fonts should contain one FontFace (TEST 48)");
1817 expected.vdocument.push(all[0]);
1819 // Create a FontFace in each window and add it to each document's FontFaceSet.
1820 var faces = [];
1821 sourceWindows.forEach(function({ win, what: whatWin }, index) {
1822 var f = new win.FontFace("test" + index, "url(x)");
1823 sourceDocuments.forEach(function({ doc, what: whatDoc }) {
1824 doc.fonts.add(f);
1825 expected[whatDoc].push(f);
1826 f._description = whatWin + "/" + whatDoc;
1830 sourceDocuments.forEach(function({ doc, what }) {
1831 let allFonts = Array.from(doc.fonts);
1832 is(expected[what].length, allFonts.length, "expected FontFaceSet size (TEST 48) (" + what + ")");
1833 for (let i = 0; i < expected[what].length; i++) {
1834 is(expected[what][i], allFonts[i], "expected FontFace (" + expected[what][i]._description + ") at index " + i + " (TEST 48) (" + what + ")");
1838 vstyle.textContent = "";
1839 style.textContent = "";
1841 sourceDocuments.forEach(function({ doc }) { doc.fonts.clear(); });
1843 }).then(function() {
1845 // (TEST LAST) Test that a pending style sheet load prevents
1846 // document.fonts.status from being set to "loaded".
1848 // First, add a FontFace to document.fonts that will load soon.
1849 var face = new FontFace("test", "url(BitPattern.woff?testlast)");
1850 face.load();
1851 document.fonts.add(face);
1853 // Next, add a style sheet reference.
1854 var link = document.createElement("link");
1855 link.rel = "stylesheet";
1856 link.href = "neverending_stylesheet_load.sjs";
1857 link.type = "text/css";
1858 document.head.appendChild(link);
1860 return setTimeoutZero() // wait for the style sheet to start loading
1861 .then(function() {
1862 document.fonts.clear();
1863 is(document.fonts.status, "loading", "FontFaceSet.status when the FontFaceSet has been cleared of loading FontFaces but there is a pending style sheet load (TEST LAST)");
1864 document.head.removeChild(link);
1865 // XXX Removing the <link> element won't cancel the load of the
1866 // style sheet, so we can't do that to test that
1867 // document.fonts.ready is resolved once there are no more
1868 // loading style sheets.
1871 // NOTE: It is important that this style sheet test comes last in the file,
1872 // as the neverending style sheet load will interfere with subsequent
1873 // sub-tests.
1875 }).then(function() {
1877 // End of the tests.
1878 SimpleTest.finish();
1880 }, function(aError) {
1882 // Something failed.
1883 ok(false, "Something failed: " + aError);
1884 SimpleTest.finish();
1889 SimpleTest.waitForExplicitFinish();
1890 SimpleTest.requestLongerTimeout(5);
1892 </script>
1894 <style></style>
1895 <div></div>