test_font_loading_api.html (84705B)
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> 6 7 <script src=descriptor_database.js></script> 8 9 <body onload="runTest()"> 10 <iframe id=v src="file_font_loading_api_vframe.html"></iframe> 11 <iframe id=n style="display: none"></iframe> 12 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" 24 }; 25 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" 36 }; 37 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"] 49 }; 50 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" 60 }; 61 62 // Invalid font family names. 63 var invalidFontFamilyNames = [ 64 "", "sans-serif", "A, B", "inherit", "a 1" 65 ]; 66 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"; 70 71 // Will hold an ArrayBuffer containing a valid font. 72 var fontData; 73 74 var queue = Promise.resolve(); 75 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(); 85 } 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(); 91 } 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(); 98 } 99 }); 100 }); 101 } 102 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(); 112 } 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(); 118 } 119 }); 120 Promise.resolve().then(function() { 121 if (!handled) { 122 handled = true; 123 ok(true, aDescription + " should be pending " + aTestID); 124 aResolve(); 125 } 126 }); 127 }); 128 } 129 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)); 141 } 142 } 143 }; 144 xhr.send(); 145 }); 146 } 147 148 function setTimeoutZero() { 149 return new Promise(function(aResolve, aReject) { 150 setTimeout(aResolve, 0); 151 }); 152 } 153 154 function awaitRefresh() { 155 return new Promise(r => { 156 requestAnimationFrame(() => requestAnimationFrame(r)); 157 }); 158 } 159 160 function flushStyles() { 161 getComputedStyle(document.body).width; 162 } 163 164 function runTest() { 165 // Document and window from inside the display:none iframe. 166 var nframe = document.getElementById("n"); 167 var ndocument = nframe.contentDocument; 168 var nwindow = nframe.contentWindow; 169 170 // Document and window from inside the visible iframe. 171 var vframe = document.getElementById("v"); 172 var vdocument = vframe.contentDocument; 173 var vwindow = vframe.contentWindow; 174 175 // For iterating over different combinations of documents and windows 176 // to test with. 177 var sources = [ 178 { win: window, doc: document, what: "window/document" }, 179 { win: vwindow, doc: vdocument, what: "vwindow/vdocument" }, 180 { win: nwindow, doc: ndocument, what: "nwindow/ndocument" }, 181 { win: window, doc: vdocument, what: "window/vdocument" }, 182 { win: window, doc: ndocument, what: "window/ndocument" }, 183 { win: vwindow, doc: document, what: "vwindow/document" }, 184 { win: vwindow, doc: ndocument, what: "vwindow/ndocument" }, 185 { win: nwindow, doc: document, what: "nwindow/document" }, 186 { win: nwindow, doc: vdocument, what: "nwindow/vdocument" }, 187 ]; 188 189 var sourceDocuments = [ 190 { doc: document, what: "document" }, 191 { doc: vdocument, what: "vdocument" }, 192 { doc: ndocument, what: "ndocument" }, 193 ]; 194 195 var sourceWindows = [ 196 { win: window, what: "window" }, 197 { win: vwindow, what: "vwindow" }, 198 { win: nwindow, what: "nwindow" }, 199 ]; 200 201 queue = queue.then(function() { 202 203 // First, initialize fontData. 204 return fetchAsArrayBuffer("BitPattern.woff") 205 .then(function(aResult) { fontData = aResult; }); 206 207 }).then(function() { 208 209 // (TEST 1) Some miscellaneous tests for FontFaceSet and FontFace. 210 ok(window.FontFaceSet, "FontFaceSet interface object should be present (TEST 1)"); 211 is(Object.getPrototypeOf(FontFaceSet.prototype), EventTarget.prototype, "FontFaceSet should inherit from EventTarget (TEST 1)"); 212 ok(document.fonts instanceof FontFaceSet, "document.fonts should be a a FontFaceSet (TEST 1)"); 213 ok(window.FontFace, "FontFace interface object should be present (TEST 1)"); 214 is(Object.getPrototypeOf(FontFace.prototype), Object.prototype, "FontFace should inherit from Object (TEST 1)"); 215 216 // (TEST 2) Some miscellaneous tests for FontFaceSetLoadEvent. 217 ok(window.FontFaceSetLoadEvent, "FontFaceSetLoadEvent interface object should be present (TEST 2)"); 218 is(Object.getPrototypeOf(FontFaceSetLoadEvent.prototype), Event.prototype, "FontFaceSetLoadEvent should inherit from Event (TEST 2)"); 219 220 }).then(function() { 221 222 // (TEST 3) Test that document.fonts.ready is resolved with the 223 // document.fonts FontFaceSet. 224 var p = Promise.resolve(); 225 sourceDocuments.forEach(function({ doc, what }) { 226 p = p.then(_ => { return doc.fonts.ready }).then(function() { 227 return is_resolved_with(doc.fonts.ready, doc.fonts, "document.fonts.ready resolves with document.fonts.", "(TEST 3) (" + what + ")"); 228 }); 229 }); 230 return p; 231 232 }).then(function() { 233 234 // (TEST 4) Test that document.fonts in this test document starts out with no 235 // FontFace objects in it. 236 sourceDocuments.forEach(function({ doc, what }) { 237 is(Array.from(doc.fonts).length, 0, "initial number of FontFace objects in document.fonts (TEST 4) (" + what + ")"); 238 }); 239 240 // (TEST 5) Test that document.fonts.status starts off as loaded. 241 sourceDocuments.forEach(function({ doc, what }) { 242 is(doc.fonts.status, "loaded", "initial value of document.fonts.status (TEST 5) (" + what + ")"); 243 }); 244 245 // (TEST 6) Test initial value of FontFace.status when a url() source is 246 // used. 247 sourceWindows.forEach(function({ win, what }) { 248 is(new win.FontFace("test", "url(x)").status, "unloaded", "initial value of FontFace.status when a url() source is used (TEST 6) (" + what + ")"); 249 }); 250 251 // (TEST 7) Test initial value of FontFace.status when an invalid 252 // ArrayBuffer source is used. Because it has an implicit initial 253 // load() call, it should either be "loading" if the browser is 254 // asynchronously parsing the font data, or "error" if it parsed 255 // it immediately. 256 sourceWindows.forEach(function({ win, what }) { 257 var status = new win.FontFace("test", new ArrayBuffer(0)).status; 258 ok(status == "loading" || status == "error", "initial value of FontFace.status when an invalid ArrayBuffer source is used (TEST 7) (" + what + ")"); 259 }); 260 261 // (TEST 8) Test initial value of FontFace.status when a valid ArrayBuffer 262 // source is used. Because it has an implicit initial load() call, it 263 // should either be "loading" if the browser is asynchronously parsing the 264 // font data, or "loaded" if it parsed it immediately. 265 sourceWindows.forEach(function({ win, what }) { 266 status = new win.FontFace("test", fontData).status; 267 ok(status == "loading" || status == "loaded", "initial value of FontFace.status when a valid ArrayBuffer source is used (TEST 8) (" + what + ")"); 268 }); 269 270 // (TEST 9) (old test became redundant with TEST 19) 271 272 }).then(function() { 273 274 // (TEST 10) Test initial value of FontFace.loaded when a valid url() 275 // source is used. 276 var p = Promise.resolve(); 277 sourceWindows.forEach(function({ win, what }) { 278 p = p.then(function() { 279 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 + ")"); 280 }); 281 }); 282 return p; 283 284 }).then(function() { 285 286 // (TEST 11) (old test became redundant with TEST 21) 287 288 }).then(function() { 289 290 // (TEST 12) (old test became redundant with TEST 20) 291 292 }).then(function() { 293 294 // (TEST 13) Test initial values of the descriptor attributes on FontFace 295 // objects. 296 sourceWindows.forEach(function({ win, what }) { 297 var face = new win.FontFace("test", fontData); 298 // XXX Spec issue: what values do the descriptor attributes have before the 299 // constructor's dictionary argument is parsed? 300 for (var desc in defaultValues) { 301 is(face[desc], defaultValues[desc], "initial value of FontFace." + desc + " (TEST 13) (" + what + ")"); 302 } 303 }); 304 305 // (TEST 14) Test default values of the FontFaceDescriptors dictionary. 306 var p = Promise.resolve(); 307 sourceWindows.forEach(function({ win, what }) { 308 p = p.then(function() { 309 var face = new win.FontFace("test", fontData); 310 return face.loaded.then(function() { 311 for (var desc in defaultValues) { 312 is(face[desc], defaultValues[desc], "default value of FontFace." + desc + " (TEST 14) (" + what + ")"); 313 } 314 }, function(aError) { 315 ok(false, "FontFace should have loaded succesfully (TEST 14) (" + what + ")"); 316 }); 317 }); 318 }); 319 return p; 320 321 }).then(function() { 322 323 // (TEST 15) Test passing non-default descriptor values to the FontFace 324 // constructor. 325 var p = Promise.resolve(); 326 sourceWindows.forEach(function({ win, what }) { 327 p = p.then(function() { 328 var descriptorTests = Promise.resolve(); 329 Object.keys(nonDefaultValues).forEach(function(aDesc) { 330 descriptorTests = descriptorTests.then(function() { 331 var init = {}; 332 init[aDesc] = nonDefaultValues[aDesc][0]; 333 var face = new win.FontFace("test", fontData, init); 334 var ok_todo = aDesc == "variant" ? todo : ok; 335 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " immediately after construction (TEST 15) (" + what + ")"); 336 return face.loaded.then(function() { 337 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " (TEST 15) (" + what + ")"); 338 }, function(aError) { 339 ok(false, "FontFace should have loaded succesfully (TEST 15) (" + what + ")"); 340 }); 341 }); 342 }); 343 return descriptorTests; 344 }); 345 }); 346 return p; 347 348 }).then(function() { 349 350 // (TEST 16) Test passing invalid descriptor values to the FontFace 351 // constructor. 352 var p = Promise.resolve(); 353 sourceWindows.forEach(function({ win, what }) { 354 p = p.then(function() { 355 var descriptorTests = Promise.resolve(); 356 Object.keys(invalidValues).forEach(function(aDesc) { 357 descriptorTests = descriptorTests.then(function() { 358 var init = {}; 359 init[aDesc] = invalidValues[aDesc]; 360 var face = new win.FontFace("test", fontData, init); 361 var ok_todo = aDesc == "variant" ? todo : ok; 362 ok_todo(face.status == "error", "FontFace should be error immediately after construction with invalid value of FontFace." + aDesc + " (TEST 16) (" + what + ")"); 363 return face.loaded.then(function() { 364 ok_todo(false, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")"); 365 }, function(aError) { 366 ok(true, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")"); 367 is(aError.name, "SyntaxError", "FontFace.loaded with invalid value of FontFace." + aDesc + " should be rejected with a SyntaxError (TEST 16) (" + what + ")"); 368 }); 369 }); 370 }); 371 return descriptorTests; 372 }); 373 }); 374 return p; 375 376 }).then(function() { 377 378 // (TEST 17) Test passing an invalid font family name to the FontFace 379 // constructor. 380 var p = Promise.resolve(); 381 sourceWindows.forEach(function({ win, what }) { 382 p = p.then(function() { 383 var familyTests = Promise.resolve(); 384 invalidFontFamilyNames.forEach(function(aFamilyName) { 385 familyTests = familyTests.then(function() { 386 var face = new win.FontFace(aFamilyName, fontData); 387 isnot(face.status, "error", "FontFace should be quoted after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")"); 388 is(face.family, `"${aFamilyName}"`, "FontFace.family should be the quoted string after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")"); 389 return face.loaded.then(function() { 390 ok(true, "FontFace should load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")"); 391 }, function(aError) { 392 ok(false, "FontFace should load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")"); 393 }); 394 }); 395 }); 396 return familyTests; 397 }); 398 }); 399 return p; 400 401 }).then(function() { 402 403 // (TEST 18) Test passing valid url() source strings to the FontFace 404 // constructor. 405 var p = Promise.resolve(); 406 407 // The sub-test is very fragile on Android platform, see Bug 1455824, 408 // especially Comment 34. 409 if (navigator.appVersion.includes("Android")) { 410 return p; 411 } 412 413 sourceWindows.forEach(function({ win, what }) { 414 p = p.then(function() { 415 var srcTests = Promise.resolve(); 416 gCSSFontFaceDescriptors.src.values.forEach(function(aSrc) { 417 srcTests = srcTests.then(function() { 418 var face = new win.FontFace("test", aSrc); 419 return face.load().then(function() { 420 ok(true, "FontFace should load with valid url() src " + aSrc + " (TEST 18) (" + what + ")"); 421 }, function(aError) { 422 is(aError.name, "NetworkError", "FontFace had NetworkError when loading with valid url() src " + aSrc + " (TEST 18) (" + what + ")"); 423 }); 424 }); 425 }); 426 return srcTests; 427 }); 428 }); 429 return p; 430 431 }).then(function() { 432 433 // (TEST 19) Test passing invalid url() source strings to the FontFace 434 // constructor. 435 var p = Promise.resolve(); 436 sourceWindows.forEach(function({ win, what }) { 437 p = p.then(function() { 438 var srcTests = Promise.resolve(); 439 gCSSFontFaceDescriptors.src.invalid_values.forEach(function(aSrc) { 440 srcTests = srcTests.then(function() { 441 var face = new win.FontFace("test", aSrc); 442 is(face.status, "error", "FontFace.status should be \"error\" when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")"); 443 return face.loaded.then(function() { 444 ok(false, "FontFace should not load with invalid url() src " + aSrc + " (TEST 19) (" + what + ")"); 445 }, function(aError) { 446 is(aError.name, "SyntaxError", "FontFace.ready should have been rejected with a SyntaxError when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")"); 447 }); 448 }); 449 }); 450 return srcTests; 451 }); 452 }); 453 return p; 454 455 }).then(function() { 456 457 // (TEST 20) Test that the status of a FontFace constructed with a valid 458 // ArrayBuffer source eventually becomes "loaded". 459 var p = Promise.resolve(); 460 sourceWindows.forEach(function({ win, what }) { 461 p = p.then(function() { 462 var face = new win.FontFace("test", fontData); 463 return face.loaded.then(function(aFace) { 464 is(face.status, "loaded", "status of FontFace constructed with a valid ArrayBuffer source should eventually be \"loaded\" (TEST 20) (" + what + ")"); 465 is(face, aFace, "FontFace.loaded was resolved with the FontFace object once loaded (TEST 20) (" + what + ")"); 466 }, function(aError) { 467 ok(false, "FontFace constructed with a valid ArrayBuffer should eventually load (TEST 20) (" + what + ")"); 468 }); 469 }); 470 }); 471 return p; 472 473 }).then(function() { 474 475 // (TEST 21) Test that the status of a FontFace constructed with an invalid 476 // ArrayBuffer source eventually becomes "error". 477 var p = Promise.resolve(); 478 sourceWindows.forEach(function({ win, what }) { 479 p = p.then(function() { 480 var face = new win.FontFace("test", new ArrayBuffer(0)); 481 return face.loaded.then(function() { 482 ok(false, "FontFace constructed with an invalid ArrayBuffer should not load (TEST 21) (" + what + ")"); 483 }, function(aError) { 484 is(aError.name, "SyntaxError", "loaded of FontFace constructed with an invalid ArrayBuffer source should be rejected with TypeError (TEST 21) (" + what + ")"); 485 is(face.status, "error", "status of FontFace constructed with an invalid ArrayBuffer source should eventually be \"error\" (TEST 21) (" + what + ")"); 486 }); 487 }); 488 }); 489 return p; 490 491 }).then(function() { 492 493 // (TEST 22) Test assigning non-default descriptor values on the FontFace. 494 var p = Promise.resolve(); 495 sourceWindows.forEach(function({ win, what }) { 496 p = p.then(function() { 497 var descriptorTests = Promise.resolve(); 498 Object.keys(nonDefaultValues).forEach(function(aDesc) { 499 descriptorTests = descriptorTests.then(function() { 500 var face = new win.FontFace("test", fontData); 501 return face.loaded.then(function() { 502 var ok_todo = aDesc == "variant" ? todo : ok; 503 face[aDesc] = nonDefaultValues[aDesc][0]; 504 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "assigned valid non-default value to FontFace." + aDesc + " (TEST 22) (" + what + ")"); 505 }, function(aError) { 506 ok(false, "FontFace should have loaded succesfully (TEST 22) (" + what + ")"); 507 }); 508 }); 509 }); 510 return descriptorTests; 511 }); 512 }); 513 return p; 514 515 }).then(function() { 516 517 // (TEST 23) Test assigning invalid descriptor values on the FontFace. 518 var p = Promise.resolve(); 519 sourceWindows.forEach(function({ win, what }) { 520 p = p.then(function() { 521 var descriptorTests = Promise.resolve(); 522 Object.keys(invalidValues).forEach(function(aDesc) { 523 descriptorTests = descriptorTests.then(function() { 524 var face = new win.FontFace("test", fontData); 525 return face.loaded.then(function() { 526 var ok_todo = aDesc == "variant" ? todo : ok; 527 var exceptionName = ""; 528 try { 529 face[aDesc] = invalidValues[aDesc]; 530 } catch (ex) { 531 exceptionName = ex.name; 532 } 533 ok_todo(exceptionName == "SyntaxError", "assigning invalid value to FontFace." + aDesc + " should throw a SyntaxError (TEST 23) (" + what + ")"); 534 }, function(aError) { 535 ok(false, "FontFace should have loaded succesfully (TEST 23) (" + what + ")"); 536 }); 537 }); 538 }); 539 return descriptorTests; 540 }); 541 }); 542 return p; 543 544 }).then(function() { 545 546 // (TEST 24) Test that the status of a FontFace with a non-existing url() 547 // source is set to "loading" right after load() is called, that its .loaded 548 // Promise is returned, and that the Promise is eventually rejected with a 549 // NetworkError and its status is set to "error". 550 var p = Promise.resolve(); 551 sourceWindows.forEach(function({ win, what }) { 552 p = p.then(function() { 553 var face = new win.FontFace("test", "url(x)"); 554 var result = face.load(); 555 is(face.status, "loading", "FontFace.status should be \"loading\" right after load() is called (TEST 24) (" + what + ")"); 556 is(result, face.loaded, "FontFace.load() should return the .loaded Promise (TEST 24) (" + what + ")"); 557 558 return result.then(function() { 559 ok(false, "FontFace with a non-existing url() source should not load (TEST 24) (" + what + ")"); 560 }, function(aError) { 561 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 + ")"); 562 is(face.status, "error", "FontFace with a non-existing url() source should result in its .status being set to \"error\" (TEST 24) (" + what + ")"); 563 }); 564 }); 565 }); 566 return p; 567 568 }).then(function() { 569 570 // (TEST 25) Test simple manipulation of the FontFaceSet. 571 var p = Promise.resolve(); 572 sources.forEach(function({ win, doc, what }) { 573 p = p.then(function() { 574 var face, face2, all; 575 face = new win.FontFace("test", "url(x)"); 576 face2 = new win.FontFace("test2", "url(x)"); 577 ok(!doc.fonts.has(face), "newly created FontFace should not be in document.fonts (TEST 25) (" + what + ")"); 578 doc.fonts.add(face); 579 ok(doc.fonts.has(face), "should be able to add a FontFace to document.fonts (TEST 25) (" + what + ")"); 580 doc.fonts.add(face); 581 ok(doc.fonts.has(face), "should be able to repeatedly add a FontFace to document.fonts (TEST 25) (" + what + ")"); 582 ok(doc.fonts.delete(face), "FontFaceSet.delete should return true when it succeeds (TEST 25) (" + what + ")"); 583 ok(!doc.fonts.has(face), "FontFace should be gone from document.fonts after delete is called (TEST 25) (" + what + ")"); 584 ok(!doc.fonts.delete(face), "FontFaceSet.delete should return false when it fails (TEST 25) (" + what + ")"); 585 doc.fonts.add(face); 586 doc.fonts.add(face2); 587 ok(doc.fonts.has(face2), "should be able to add a second FontFace to document.fonts (TEST 25) (" + what + ")"); 588 doc.fonts.clear(); 589 ok(!doc.fonts.has(face) && !doc.fonts.has(face2), "FontFaces should be gone from document.fonts after clear is called (TEST 25) (" + what + ")"); 590 doc.fonts.add(face); 591 doc.fonts.add(face2); 592 all = Array.from(doc.fonts); 593 is(all[0], face, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")"); 594 is(all[1], face2, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")"); 595 doc.fonts.add(face); 596 all = Array.from(doc.fonts); 597 is(all[0], face, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")"); 598 is(all[1], face2, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")"); 599 doc.fonts.clear(); 600 return doc.fonts.ready; 601 }); 602 }); 603 return p; 604 605 }).then(function() { 606 607 // (TEST 26) Test that FontFaceSet.ready is replaced, .status is set to 608 // "loading", and a loading event is dispatched when a loading FontFace is 609 // added to it. 610 var p = Promise.resolve(); 611 sources.forEach(function({ win, doc, what }) { 612 p = p.then(function() { 613 var awaitEvents = new Promise(function(aResolve, aReject) { 614 615 var onloadingTriggered = false, loadingDispatched = false; 616 617 function check() { 618 if (onloadingTriggered && loadingDispatched) { 619 doc.fonts.onloading = null; 620 doc.fonts.removeEventListener("loading", listener); 621 aResolve(); 622 } 623 } 624 625 var listener = function(aEvent) { 626 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")"); 627 loadingDispatched = true; 628 check(); 629 }; 630 doc.fonts.addEventListener("loading", listener); 631 doc.fonts.onloading = function(aEvent) { 632 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")"); 633 onloadingTriggered = true; 634 check(); 635 }; 636 }); 637 638 is(doc.fonts.status, "loaded", "FontFaceSet.status initially (TEST 26) (" + what + ")"); 639 640 var oldReady = doc.fonts.ready; 641 var face = new win.FontFace("test", "url(neverending_font_load.sjs)"); 642 face.load(); 643 doc.fonts.add(face); 644 645 var newReady = doc.fonts.ready; 646 isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when a loading FontFace is added to it (TEST 26) (" + what + ")"); 647 is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when a loading FontFace is added to it (TEST 26) (" + what + ")"); 648 649 return awaitEvents 650 .then(function() { 651 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 + ")"); 652 }) 653 .then(function() { 654 doc.fonts.clear(); 655 return doc.fonts.ready; 656 }); 657 }); 658 }); 659 return p; 660 661 }).then(function() { 662 663 // (TEST 27) Test that FontFaceSet.ready is resolved, .status is set to 664 // "loaded", and a loadingdone event (but no loadingerror event) is 665 // dispatched when the only loading FontFace in it is removed. 666 var p = Promise.resolve(); 667 sources.forEach(function({ win, doc, what }) { 668 p = p.then(function() { 669 var awaitEvents = new Promise(function(aResolve, aReject) { 670 671 var onloadingdoneTriggered = false, loadingdoneDispatched = false; 672 var onloadingerrorTriggered = false, loadingerrorDispatched = false; 673 674 function check() { 675 doc.fonts.onloadingdone = null; 676 doc.fonts.onloadingerror = null; 677 doc.fonts.removeEventListener("loadingdone", doneListener); 678 doc.fonts.removeEventListener("loadingerror", errorListener); 679 aResolve(); 680 } 681 682 var doneListener = function(aEvent) { 683 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")"); 684 is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")"); 685 loadingdoneDispatched = true; 686 check(); 687 }; 688 doc.fonts.addEventListener("loadingdone", doneListener); 689 doc.fonts.onloadingdone = function(aEvent) { 690 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")"); 691 is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")"); 692 onloadingdoneTriggered = true; 693 check(); 694 }; 695 var errorListener = function(aEvent) { 696 loadingerrorDispatched = true; 697 check(); 698 } 699 doc.fonts.addEventListener("loadingerror", errorListener); 700 doc.fonts.onloadingerror = function(aEvent) { 701 onloadingdoneTriggered = true; 702 check(); 703 }; 704 }); 705 706 is(doc.fonts.status, "loaded", "FontFaceSet.status should be \"loaded\" initially (TEST 27) (" + what + ")"); 707 708 var f = new win.FontFace("test", "url(neverending_font_load.sjs)"); 709 f.load(); 710 doc.fonts.add(f); 711 712 is(doc.fonts.status, "loading", "FontFaceSet.status should be \"loading\" when a loading FontFace is in it (TEST 27) (" + what + ")"); 713 714 doc.fonts.clear(); 715 716 return awaitEvents 717 .then(function() { 718 return is_resolved_with(doc.fonts.ready, doc.fonts, "FontFaceSet.ready when the FontFaceSet is cleared", "(TEST 27) (" + what + ")"); 719 }) 720 .then(function() { 721 is(doc.fonts.status, "loaded", "FontFaceSet.status should be set to \"loaded\" when it is cleared (TEST 27) (" + what + ")"); 722 return doc.fonts.ready; 723 }); 724 }); 725 }); 726 return p; 727 728 }).then(function() { 729 730 // (TEST 28) Test that FontFaceSet.ready is replaced, .status is set to 731 // "loading", and a loading event is dispatched when a FontFace in it 732 // starts loading. 733 var p = Promise.resolve(); 734 sources.forEach(function({ win, doc, what }) { 735 p = p.then(function() { 736 var awaitEvents = new Promise(function(aResolve, aReject) { 737 738 var onloadingTriggered = false, loadingDispatched = false; 739 740 function check() { 741 if (onloadingTriggered && loadingDispatched) { 742 doc.fonts.onloading = null; 743 doc.fonts.removeEventListener("loading", listener); 744 aResolve(); 745 } 746 } 747 748 var listener = function(aEvent) { 749 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")"); 750 loadingDispatched = true; 751 check(); 752 }; 753 doc.fonts.addEventListener("loading", listener); 754 doc.fonts.onloading = function(aEvent) { 755 is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")"); 756 onloadingTriggered = true; 757 check(); 758 }; 759 }); 760 761 var oldReady = doc.fonts.ready; 762 var face = new win.FontFace("test", "url(neverending_font_load.sjs)"); 763 doc.fonts.add(face); 764 face.load(); 765 766 var newReady = doc.fonts.ready; 767 isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when its only FontFace starts loading (TEST 28) (" + what + ")"); 768 is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when its only FontFace starts loading (TEST 28) (" + what + ")"); 769 770 return awaitEvents 771 .then(function() { 772 return is_pending(newReady, "FontFaceSet.ready when the FontFaceSet's only FontFace starts loading", "(TEST 28) (" + what + ")"); 773 }) 774 .then(function() { 775 doc.fonts.clear(); 776 return doc.fonts.ready; 777 }); 778 }); 779 }); 780 return p; 781 782 }).then(function() { 783 784 // (TEST 29) Test that a loadingdone and a loadingerror event is dispatched 785 // when a FontFace that eventually becomes status "error" is added to the 786 // FontFaceSet. 787 var p = Promise.resolve(); 788 sources.forEach(function({ win, doc, what }) { 789 p = p.then(function() { 790 var face; 791 var awaitEvents = new Promise(function(aResolve, aReject) { 792 793 var onloadingdoneTriggered = false, loadingdoneDispatched = false; 794 var onloadingerrorTriggered = false, loadingerrorDispatched = false; 795 796 function check() { 797 if (onloadingdoneTriggered && loadingdoneDispatched && 798 onloadingerrorTriggered && loadingerrorDispatched) { 799 doc.fonts.onloadingdone = null; 800 doc.fonts.onloadingerror = null; 801 doc.fonts.removeEventListener("loadingdone", doneListener); 802 doc.fonts.removeEventListener("loadingerror", errorListener); 803 aResolve(); 804 } 805 } 806 807 var doneListener = function(aEvent) { 808 loadingdoneDispatched = true; 809 check(); 810 }; 811 doc.fonts.addEventListener("loadingdone", doneListener); 812 doc.fonts.onloadingdone = function(aEvent) { 813 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")"); 814 is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 29) (" + what + ")"); 815 onloadingdoneTriggered = true; 816 check(); 817 }; 818 var errorListener = function(aEvent) { 819 is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingerror event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")"); 820 is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 29) (" + what + ")"); 821 loadingerrorDispatched = true; 822 check(); 823 } 824 doc.fonts.addEventListener("loadingerror", errorListener); 825 doc.fonts.onloadingerror = function(aEvent) { 826 onloadingerrorTriggered = true; 827 check(); 828 }; 829 }); 830 831 face = new win.FontFace("test", "url(x)"); 832 face.load(); 833 is(face.status, "loading", "FontFace should have status \"loading\" (TEST 29) (" + what + ")"); 834 doc.fonts.add(face); 835 836 return face.loaded 837 .then(function() { 838 ok(false, "the FontFace should not load (TEST 29) (" + what + ")"); 839 }, function(aError) { 840 is(face.status, "error", "FontFace should have status \"error\" (TEST 29) (" + what + ")"); 841 return awaitEvents; 842 }) 843 .then(function() { 844 doc.fonts.clear(); 845 return doc.fonts.ready; 846 }); 847 }); 848 }); 849 return p; 850 851 }).then(function() { 852 853 // (TEST 30) Test that a loadingdone event is dispatched when a FontFace 854 // that eventually becomes status "loaded" is added to the FontFaceSet. 855 var p = Promise.resolve(); 856 sources.forEach(function({ win, doc, what }, i) { 857 p = p.then(function() { 858 var face; 859 var awaitEvents = new Promise(function(aResolve, aReject) { 860 861 var onloadingdoneTriggered = false, loadingdoneDispatched = false; 862 863 function check() { 864 if (onloadingdoneTriggered && loadingdoneDispatched) { 865 doc.fonts.onloadingdone = null; 866 doc.fonts.removeEventListener("loadingdone", doneListener); 867 aResolve(); 868 } 869 } 870 871 var doneListener = function(aEvent) { 872 loadingdoneDispatched = true; 873 check(); 874 }; 875 doc.fonts.addEventListener("loadingdone", doneListener); 876 doc.fonts.onloadingdone = function(aEvent) { 877 is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 30) (" + what + ")"); 878 onloadingdoneTriggered = true; 879 check(); 880 }; 881 }); 882 883 face = new win.FontFace("test", "url(BitPattern.woff?test30." + i + ")"); 884 face.load(); 885 is(face.status, "loading", "FontFace should have status \"loading\" (TEST 30) (" + what + ")"); 886 doc.fonts.add(face); 887 888 return face.loaded 889 .then(function() { 890 is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 30) (" + what + ")"); 891 return awaitEvents; 892 }) 893 .then(function() { 894 doc.fonts.clear(); 895 }); 896 }); 897 }); 898 return p; 899 900 }).then(function() { 901 902 // (TEST 31) Test that a loadingdone event is dispatched when a FontFace 903 // with status "unloaded" is added to the FontFaceSet and load() is called 904 // on it. 905 var p = Promise.resolve(); 906 sources.forEach(function({ win, doc, what }, i) { 907 p = p.then(function() { 908 var face; 909 var awaitEvents = new Promise(function(aResolve, aReject) { 910 911 var onloadingdoneTriggered = false, loadingdoneDispatched = false; 912 913 function check() { 914 if (onloadingdoneTriggered && loadingdoneDispatched) { 915 doc.fonts.onloadingdone = null; 916 doc.fonts.removeEventListener("loadingdone", doneListener); 917 aResolve(); 918 } 919 } 920 921 var doneListener = function(aEvent) { 922 loadingdoneDispatched = true; 923 check(); 924 }; 925 doc.fonts.addEventListener("loadingdone", doneListener); 926 doc.fonts.onloadingdone = function(aEvent) { 927 is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 31) (" + what + ")"); 928 onloadingdoneTriggered = true; 929 check(); 930 }; 931 }); 932 933 face = new win.FontFace("test", "url(BitPattern.woff?test31." + i + ")"); 934 is(face.status, "unloaded", "FontFace should have status \"unloaded\" (TEST 31) (" + what + ")"); 935 doc.fonts.add(face); 936 937 return face.load() 938 .then(function() { 939 return awaitEvents; 940 }) 941 .then(function() { 942 is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 31) (" + what + ")"); 943 doc.fonts.clear(); 944 return doc.fonts.ready; 945 }); 946 }); 947 }); 948 return p; 949 950 }).then(async function() { 951 // (TEST 32) Test that pending restyles prevent document.fonts.status 952 // from becoming loaded. 953 var face = new FontFace("test", "url(neverending_font_load.sjs)"); 954 face.load(); 955 document.fonts.add(face); 956 957 is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace (TEST 32)"); 958 959 document.fonts.clear(); 960 961 is(document.fonts.status, "loading", "FontFaceSet.status after clearing, but still waiting for styles (TEST 32)"); 962 963 flushStyles(); 964 await awaitRefresh(); 965 966 is(document.fonts.status, "loaded", "FontFaceSet.status after clearing (TEST 32)"); 967 968 document.fonts.add(face); 969 970 is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace again (TEST 32)"); 971 972 var div = document.querySelector("div"); 973 div.style.color = "blue"; 974 975 document.fonts.clear(); 976 is(document.fonts.status, "loading", "FontFaceSet.status after clearing but when there is a pending restyle (TEST 32)"); 977 978 await awaitRefresh(); 979 980 is(document.fonts.status, "loaded", "FontFaceSet.status after clearing and the restyle has been flushed (TEST 32)"); 981 await document.fonts.ready; 982 }).then(function() { 983 984 // (TEST 33) Test that CSS-connected FontFace objects are created 985 // for @font-face rules in the document. 986 987 is(document.fonts.status, "loaded", "document.fonts.status should initially be loaded (TEST 33)"); 988 989 var style = document.querySelector("style"); 990 var ruleText = "@font-face { font-family: something; src: url(x); "; 991 Object.keys(nonDefaultValues).forEach(function(aDesc) { 992 ruleText += descriptorNames[aDesc] + ": " + nonDefaultValues[aDesc][0] + "; "; 993 }); 994 ruleText += "}"; 995 996 style.textContent = ruleText; 997 998 var rule = style.sheet.cssRules[0]; 999 1000 var all = Array.from(document.fonts); 1001 is(all.length, 1, "document.fonts should contain one FontFace (TEST 33)"); 1002 1003 var face = all[0]; 1004 is(face.family, "something", "FontFace should have correct family value (TEST 33)"); 1005 Object.keys(nonDefaultValues).forEach(function(aDesc) { 1006 var ok_todo = aDesc == "variant" ? todo : ok; 1007 ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "FontFace should have correct " + aDesc + " value (TEST 33)"); 1008 }); 1009 1010 is(document.fonts.status, "loaded", "document.fonts.status should still be loaded (TEST 33)"); 1011 is(face.status, "unloaded", "FontFace.status should be unloaded (TEST 33)"); 1012 1013 document.fonts.clear(); 1014 ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when clear is called (TEST 33)"); 1015 1016 is(document.fonts.delete(face), false, "attempting to remove CSS-connected FontFace from document.fonts should return false (TEST 33)"); 1017 ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when delete is called (TEST 33)"); 1018 1019 style.textContent = ""; 1020 1021 ok(!document.fonts.has(face), "CSS-connected FontFace should be removed from document.fonts once the rule has been removed (TEST 33)"); 1022 1023 is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after rule is removed (TEST 33)"); 1024 is(face.status, "unloaded", "FontFace.status should still be unloaded after rule is removed (TEST 33)"); 1025 1026 document.fonts.add(face); 1027 ok(document.fonts.has(face), "previously CSS-connected FontFace should be able to be added to document.fonts (TEST 33)"); 1028 1029 is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after now disconnected FontFace is added (TEST 33)"); 1030 is(face.status, "unloaded", "FontFace.status should still be unloaded after now disconnected FontFace is added (TEST 33)"); 1031 1032 document.fonts.delete(face); 1033 ok(!document.fonts.has(face), "previously CSS-connected FontFace should be able to be removed from document.fonts (TEST 33)"); 1034 1035 }).then(function() { 1036 1037 // (TEST 34) Test that descriptor getters for unspecified descriptors on 1038 // CSS-connected FontFace objects return their default values. 1039 var style = document.querySelector("style"); 1040 var ruleText = "@font-face { font-family: something; src: url(x); }"; 1041 1042 style.textContent = ruleText; 1043 1044 var all = Array.from(document.fonts); 1045 var face = all[0]; 1046 1047 Object.keys(defaultValues).forEach(function(aDesc) { 1048 is(face[aDesc], defaultValues[aDesc], "FontFace should return default value for " + aDesc + " (TEST 34)"); 1049 }); 1050 1051 style.textContent = ""; 1052 1053 }).then(function() { 1054 1055 // (TEST 35) Test that no loadingdone event is dispatched when a FontFace 1056 // with "loaded" status is added to a "loaded" FontFaceSet. 1057 var p = Promise.resolve(); 1058 sources.forEach(function({ win, doc, what }) { 1059 p = p.then(function() { 1060 var gotLoadingDone = false; 1061 doc.fonts.onloadingdone = function(aEvent) { 1062 gotLoadingDone = true; 1063 }; 1064 1065 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 35) (" + what + ")"); 1066 var face = new win.FontFace("test", fontData); 1067 1068 return face.loaded 1069 .then(function() { 1070 is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 35) (" + what + ")"); 1071 doc.fonts.add(face); 1072 is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 35) (" + what + ")"); 1073 return doc.fonts.ready; 1074 }) 1075 .then(function() { 1076 ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 35) (" + what + ")"); 1077 doc.fonts.onloadingdone = null; 1078 doc.fonts.clear(); 1079 }); 1080 }); 1081 }); 1082 return p; 1083 1084 }).then(function() { 1085 1086 // (TEST 36) Test that no loadingdone or loadingerror event is dispatched 1087 // when a FontFace with "error" status is added to a "loaded" FontFaceSet. 1088 var p = Promise.resolve(); 1089 sources.forEach(function({ win, doc, what }) { 1090 var doc = win.document; 1091 p = p.then(function() { 1092 var gotLoadingDone = false, gotLoadingError = false; 1093 doc.fonts.onloadingdone = function(aEvent) { 1094 gotLoadingDone = true; 1095 }; 1096 doc.fonts.onloadingerror = function(aEvent) { 1097 gotLoadingError = true; 1098 }; 1099 1100 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 36) (" + what + ")"); 1101 var face = new win.FontFace("test", new ArrayBuffer(0)); 1102 1103 return face.loaded 1104 .then(function() { 1105 ok(false, "FontFace should not have loaded (TEST 36) (" + what + ")"); 1106 }, function() { 1107 is(face.status, "error", "FontFace should have status \"error\" (TEST 36) (" + what + ")"); 1108 doc.fonts.add(face); 1109 is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 36) (" + what + ")"); 1110 return doc.fonts.ready; 1111 }) 1112 .then(function() { 1113 ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 36) (" + what + ")"); 1114 ok(!gotLoadingError, "loadingerror event should not be dispatched (TEST 36) (" + what + ")"); 1115 doc.fonts.onloadingdone = null; 1116 doc.fonts.onloadingerror = null; 1117 doc.fonts.clear(); 1118 }); 1119 }); 1120 }); 1121 return p; 1122 1123 }).then(function() { 1124 1125 // (TEST 37) Test that a FontFace only has one loadingdone event dispatched 1126 // at the FontFaceSet containing it. 1127 1128 var p = Promise.resolve(); 1129 sources.forEach(function({ win, doc, what}, i) { 1130 p = p.then(function() { 1131 return setTimeoutZero(); // wait for any previous events to be dispatched 1132 }).then(function() { 1133 var events = [], face, face2; 1134 1135 var awaitEvents = new Promise(function(aResolve, aReject) { 1136 doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) { 1137 events.push(e); 1138 if (events.length == 2) { 1139 aResolve(); 1140 } 1141 }; 1142 }); 1143 1144 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 37) (" + what + ")"); 1145 1146 face = new win.FontFace("test", "url(BitPattern.woff?test37." + i + "a)"); 1147 face.load(); 1148 doc.fonts.add(face); 1149 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 37) (" + what + ")"); 1150 1151 return doc.fonts.ready 1152 .then(function() { 1153 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 37) (" + what + ")"); 1154 is(face.status, "loaded", "first FontFace should have status \"loaded\" (TEST 37) (" + what + ")"); 1155 1156 face2 = new win.FontFace("test2", "url(BitPattern.woff?test37." + i + "b)"); 1157 face2.load(); 1158 doc.fonts.add(face2); 1159 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 37) (" + what + ")"); 1160 1161 return doc.fonts.ready; 1162 }).then(function() { 1163 return awaitEvents; 1164 }).then(function() { 1165 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 37) (" + what + ")"); 1166 is(face2.status, "loaded", "second FontFace should have status \"loaded\" (TEST 37) (" + what + ")"); 1167 1168 is(events.length, 2, "should receive two events (TEST 37) (" + what + ")"); 1169 1170 is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 37) (" + what + ")"); 1171 is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 37) (" + what + ")"); 1172 is(events[0].fontfaces[0], face, "first event should have the first FontFace"); 1173 1174 is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 37) (" + what + ")"); 1175 is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 37) (" + what + ")"); 1176 is(events[1].fontfaces[0], face2, "second event should have the second FontFace (TEST 37) (" + what + ")"); 1177 1178 doc.fonts.onloadingdone = null; 1179 doc.fonts.onloadingerror = null; 1180 doc.fonts.clear(); 1181 return doc.fonts.ready; 1182 }); 1183 }); 1184 }); 1185 return p; 1186 1187 }).then(function() { 1188 1189 // (TEST 38) Test that a FontFace only has one loadingerror event dispatched 1190 // at the FontFaceSet containing it. 1191 1192 var p = Promise.resolve(); 1193 sources.forEach(function({ win, doc, what }) { 1194 p = p.then(function() { 1195 return setTimeoutZero(); // wait for any previous events to be dispatched 1196 }).then(function() { 1197 var events = [], face, face2; 1198 1199 var awaitEvents = new Promise(function(aResolve, aReject) { 1200 doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) { 1201 events.push(e); 1202 if (events.length == 4) { 1203 aResolve(); 1204 } 1205 }; 1206 }); 1207 1208 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 38) (" + what + ")"); 1209 1210 face = new win.FontFace("test", "url(x)"); 1211 face.load(); 1212 doc.fonts.add(face); 1213 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 38) (" + what + ")"); 1214 1215 return doc.fonts.ready 1216 .then(function() { 1217 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font failed to load (TEST 38) (" + what + ")"); 1218 is(face.status, "error", "first FontFace should have status \"error\" (TEST 38) (" + what + ")"); 1219 1220 face2 = new win.FontFace("test2", "url(x)"); 1221 face2.load(); 1222 doc.fonts.add(face2); 1223 is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 38) (" + what + ")"); 1224 1225 return doc.fonts.ready; 1226 }).then(function() { 1227 return awaitEvents; 1228 }).then(function() { 1229 is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font failed to load (TEST 38) (" + what + ")"); 1230 is(face2.status, "error", "second FontFace should have status \"error\" (TEST 38) (" + what + ")"); 1231 1232 is(events.length, 4, "should receive four events (TEST 38) (" + what + ")"); 1233 1234 is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 38) (" + what + ")"); 1235 is(events[0].fontfaces.length, 0, "first event should have no FontFaces (TEST 38) (" + what + ")"); 1236 1237 is(events[1].type, "loadingerror", "second event should be \"loadingerror\" (TEST 38) (" + what + ")"); 1238 is(events[1].fontfaces.length, 1, "second event should have 1 FontFace (TEST 38) (" + what + ")"); 1239 is(events[1].fontfaces[0], face, "second event should have the first FontFace"); 1240 1241 is(events[2].type, "loadingdone", "third event should be \"loadingdone\" (TEST 38) (" + what + ")"); 1242 is(events[2].fontfaces.length, 0, "third event should have no FontFaces (TEST 38) (" + what + ")"); 1243 1244 is(events[3].type, "loadingerror", "third event should be \"loadingerror\" (TEST 38) (" + what + ")"); 1245 is(events[3].fontfaces.length, 1, "third event should only have 1 FontFace (TEST 38) (" + what + ")"); 1246 is(events[3].fontfaces[0], face2, "third event should have the second FontFace"); 1247 1248 doc.fonts.onloadingdone = null; 1249 doc.fonts.onloadingerror = null; 1250 doc.fonts.clear(); 1251 return doc.fonts.ready; 1252 }); 1253 }); 1254 }); 1255 return p; 1256 1257 }).then(function() { 1258 1259 // (TEST 39) Test that a FontFace for an @font-face rule only has one 1260 // loadingdone event dispatched at the FontFaceSet containing it. 1261 1262 var style, all, events, awaitEvents; 1263 1264 return setTimeoutZero() // wait for any previous events to be dispatched 1265 .then(function() { 1266 style = document.querySelector("style"); 1267 var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff?test39a); } " + 1268 "@font-face { font-family: test2; src: url(BitPattern.woff?test39b); }"; 1269 1270 style.textContent = ruleText; 1271 1272 all = Array.from(document.fonts); 1273 events = []; 1274 1275 awaitEvents = new Promise(function(aResolve, aReject) { 1276 document.fonts.onloadingdone = document.fonts.onloadingerror = function(e) { 1277 events.push(e); 1278 if (events.length == 2) { 1279 aResolve(); 1280 } 1281 }; 1282 }); 1283 1284 is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 39)"); 1285 1286 all[0].load(); 1287 is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font loading (TEST 39)"); 1288 1289 return document.fonts.ready 1290 }).then(function() { 1291 is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 39)"); 1292 is(all[0].status, "loaded", "first FontFace should have status \"loaded\" (TEST 39)"); 1293 is(all[1].status, "unloaded", "second FontFace should have status \"unloaded\" (TEST 39)"); 1294 1295 all[1].load(); 1296 is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font loading (TEST 39)"); 1297 1298 return document.fonts.ready; 1299 }).then(function() { 1300 return awaitEvents; 1301 }).then(function() { 1302 is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 39)"); 1303 is(all[1].status, "loaded", "second FontFace should have status \"loaded\" (TEST 39)"); 1304 1305 is(events.length, 2, "should receive two events (TEST 39)"); 1306 1307 is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 39)"); 1308 is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 39)"); 1309 is(events[0].fontfaces[0], all[0], "first event should have the first FontFace"); 1310 1311 is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 39)"); 1312 is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 39)"); 1313 is(events[1].fontfaces[0], all[1], "second event should have the second FontFace (TEST 39)"); 1314 1315 style.textContent = ""; 1316 1317 document.fonts.onloadingdone = null; 1318 document.fonts.onloadingerror = null; 1319 document.fonts.clear(); 1320 return document.fonts.ready; 1321 }); 1322 1323 }).then(function() { 1324 1325 // (TEST 40) Test that an attempt to add the same FontFace object a second 1326 // time to a FontFaceSet (where one of the FontFace objects is reflecting 1327 // an @font-face rule) will be ignored. 1328 1329 // First set up a @font-face rule. 1330 var style = document.querySelector("style"); 1331 style.textContent = "@font-face { font-family: something; src: url(x); }"; 1332 1333 // Then add a couple of non-connected FontFace objects. 1334 var f1 = new FontFace("test1", "url(x)"); 1335 var f2 = new FontFace("test2", "url(x)"); 1336 1337 document.fonts.add(f1); 1338 document.fonts.add(f2); 1339 1340 var all = Array.from(document.fonts); 1341 var ruleFontFace = all[0]; 1342 1343 is(all.length, 3, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 40)"); 1344 is(all[1], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)"); 1345 is(all[2], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)"); 1346 1347 document.fonts.add(f1); 1348 1349 all = Array.from(document.fonts); 1350 is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #1 (TEST 40)"); 1351 is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #1 (TEST 40)"); 1352 is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)"); 1353 is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)"); 1354 1355 document.fonts.add(ruleFontFace); 1356 1357 all = Array.from(document.fonts); 1358 is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #2 (TEST 40)"); 1359 is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #2 (TEST 40)"); 1360 is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)"); 1361 is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)"); 1362 1363 style.textContent = ""; 1364 1365 document.fonts.clear(); 1366 1367 }).then(function() { 1368 1369 // (TEST 41) Test that an attempt to add the same FontFace object a second 1370 // time to a FontFaceSet (where none of the FontFace objects are reflecting 1371 // an @font-face rule) will be ignored. 1372 1373 sources.forEach(function({ win, doc, what }) { 1374 // Add a couple of non-connected FontFace objects. 1375 var f1 = new win.FontFace("test1", "url(x)"); 1376 var f2 = new win.FontFace("test2", "url(x)"); 1377 1378 doc.fonts.add(f1); 1379 doc.fonts.add(f2); 1380 1381 var all = Array.from(doc.fonts); 1382 1383 is(all.length, 2, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 41) (" + what + ")"); 1384 is(all[0], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")"); 1385 is(all[1], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")"); 1386 1387 doc.fonts.add(f1); 1388 1389 all = Array.from(doc.fonts); 1390 is(all.length, 2, "number of FontFace objects in the FontFaceSet after duplicate add (TEST 41) (" + what + ")"); 1391 is(all[0], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")"); 1392 is(all[1], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")"); 1393 1394 doc.fonts.clear(); 1395 }); 1396 1397 }).then(function() { 1398 1399 // (TEST 42) Test that adding a FontFace to multiple FontFaceSets and then 1400 // loading it updates the status of all FontFaceSets. 1401 1402 var face = new FontFace("test", "url(x)"); 1403 1404 sourceDocuments.forEach(function({ doc, what }) { 1405 doc.fonts.add(face); 1406 }); 1407 1408 sourceDocuments.forEach(function({ doc, what }) { 1409 is(doc.fonts.status, "loaded", what + ".fonts.status before loading (TEST 42)"); 1410 }); 1411 1412 face.load(); 1413 1414 sourceDocuments.forEach(function({ doc, what }) { 1415 is(doc.fonts.status, "loading", what + ".fonts.status after loading started (TEST 42)"); 1416 }); 1417 1418 return Promise.all(sourceDocuments.map(function({ doc }) { return doc.fonts.ready; })) 1419 .then(function() { 1420 is(face.status, "error", "FontFace.status after loading finished (TEST 42)"); 1421 sourceDocuments.forEach(function({ doc, what }) { 1422 is(doc.fonts.status, "loaded", what + ".fonts.status after loading finished (TEST 42)"); 1423 }); 1424 1425 sourceDocuments.forEach(function({ doc, what }) { 1426 doc.fonts.clear(); 1427 }); 1428 }); 1429 1430 }).then(function() { 1431 1432 // (TEST 43) Test the check method with platform fonts and some 1433 // degenerate cases. 1434 1435 sourceDocuments.forEach(function({ doc, what }) { 1436 // Invalid font shorthands should throw a SyntaxError. 1437 try { 1438 doc.fonts.check("Helvetica"); 1439 ok(false, "check should throw when a syntactically invalid font shorthand is given (TEST 43) (" + what + ")"); 1440 } catch (ex) { 1441 is(ex.name, "SyntaxError", "exception name when check is called with a syntactically invalid font shorthand (TEST 43) (" + what + ")"); 1442 } 1443 1444 // System fonts should throw a SyntaxError. 1445 try { 1446 doc.fonts.check("caption"); 1447 ok(false, "check should throw when a system font value is given (TEST 43) (" + what + ")"); 1448 } catch (ex) { 1449 is(ex.name, "SyntaxError", "exception name when check is called with a system font value (TEST 43) (" + what + ")"); 1450 } 1451 1452 // CSS-wide keywords should throw a SyntaxError. 1453 try { 1454 doc.fonts.check("inherit"); 1455 ok(false, "check should throw when a CSS-wide keyword is given (TEST 43) (" + what + ")"); 1456 } catch (ex) { 1457 is(ex.name, "SyntaxError", "exception name when check is called with a CSS-wide keyword (TEST 43) (" + what + ")"); 1458 } 1459 1460 // CSS variables should throw a SyntaxError. 1461 try { 1462 doc.fonts.check("16px var(--family)"); 1463 ok(false, "check should throw when CSS variables are used (TEST 43) (" + what + ")"); 1464 } catch (ex) { 1465 is(ex.name, "SyntaxError", "exception name when check is called with CSS variables (TEST 43) (" + what + ")"); 1466 } 1467 1468 // No matching font family names => return true. 1469 is(doc.fonts.check("16px NonExistentFont1, NonExistentFont2"), true, "check return value when no matching font family names are used (TEST 43) (" + what + ")"); 1470 1471 // Matching platform font family name => return true. 1472 is(doc.fonts.check("16px NonExistentFont, " + likelyPlatformFonts), true, "check return value when a matching platform font family name is used (TEST 43) (" + what + ")"); 1473 1474 // Matching platform font family name, but using a different test 1475 // strings. (Platform fonts always return true from check, regardless 1476 // of the actual glyphs present.) 1477 [ 1478 { test: "\0", desc: "a single non-matching glyph" }, 1479 { test: "A\0", desc: "a matching and a non-matching glyph" }, 1480 { test: "A", desc: "a matching glyph" }, 1481 { test: "AB", desc: "multiple matching glyphs" } 1482 ].forEach(function({ test, desc }) { 1483 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 + ")"); 1484 }); 1485 1486 // No matching font family name, but an empty test string. 1487 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 + ")"); 1488 1489 // Matching platform font family name, but empty test string. 1490 is(doc.fonts.check("16px " + likelyPlatformFonts, ""), true, "check return value with an empty test string (TEST 43) (" + what + ")"); 1491 }); 1492 1493 }).then(function() { 1494 1495 // (TEST 44) Test the check method with script-created FontFaces. 1496 1497 var tests = [ 1498 // at least one matching FontFace is not loaded ==> false 1499 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }] }, 1500 { result: false, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded" }] }, 1501 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "loaded" }] }, 1502 { result: false, font: "16px Test", faces: [{ family: "Test", status: "loading" }] }, 1503 { result: false, font: "16px Test", faces: [{ family: "Test", status: "error" }] }, 1504 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic" }] }, 1505 { result: false, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold" }] }, 1506 { result: false, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] }, 1507 { result: false, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] }, 1508 1509 // all matching FontFaces are loaded ==> true 1510 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }] }, 1511 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "loaded" }] }, 1512 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, 1513 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] }, 1514 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Irrelevant", status: "unloaded" }] }, 1515 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic" }] }, 1516 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }] }, 1517 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed" }] }, 1518 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }, { family: "Test", status: "unloaded", weight: "600" }] }, 1519 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "loaded" }] }, 1520 1521 // no matching FontFaces at all ==> true 1522 { result: true, font: "16px Test", faces: [] }, 1523 { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] }, 1524 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, 1525 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] }, 1526 1527 // matching FontFace for one sample text character is loaded but 1528 // not the other ==> false 1529 { result: false, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "unloaded", unicodeRange: "U+62" }] }, 1530 1531 // matching FontFaces for separate sample text characters are all 1532 // loaded ==> true 1533 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "loaded", unicodeRange: "U+62" }] }, 1534 ]; 1535 1536 sources.forEach(function({ win, doc, what }, i) { 1537 tests.forEach(function({ result, font, faces }, j) { 1538 faces.forEach(function(f, k) { 1539 var fontFace; 1540 if (f.status == "loaded") { 1541 fontFace = new win.FontFace(f.family, fontData, f); 1542 } else if (f.status == "error") { 1543 fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f); 1544 } else { 1545 fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test44." + [i, j, k] + ")", f); 1546 if (f.status == "loading") { 1547 fontFace.load(); 1548 } 1549 } 1550 is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 44) (" + what + ")"); 1551 doc.fonts.add(fontFace); 1552 }); 1553 is(doc.fonts.check(font, "ab"), result, "check return value for subtest " + j + " (TEST 44) (" + what + ")"); 1554 doc.fonts.clear(); 1555 }); 1556 }); 1557 1558 }).then(function() { 1559 1560 // (TEST 45) Test the load method with platform fonts and some 1561 // degenerate cases. 1562 1563 var p = Promise.resolve(); 1564 sources.forEach(function({ win, doc, what }) { 1565 p = p.then(function() { 1566 // Invalid font shorthands should reject the promise with a SyntaxError. 1567 return doc.fonts.load("Helvetica").then(function() { 1568 ok(false, "load should reject when a syntactically invalid font shorthand is given (TEST 45) (" + what + ")"); 1569 }, function(ex) { 1570 is(ex.name, "SyntaxError", "exception name when load is called with a syntactically invalid font shorthand (TEST 45) (" + what + ")"); 1571 }); 1572 }); 1573 1574 p = p.then(function() { 1575 // System fonts should reject with a SyntaxError. 1576 return doc.fonts.load("caption").then(function() { 1577 ok(false, "load should throw when a system font value is given (TEST 45) (" + what + ")"); 1578 }, function(ex) { 1579 is(ex.name, "SyntaxError", "exception name when load is called with a system font value (TEST 45) (" + what + ")"); 1580 }); 1581 }); 1582 1583 p = p.then(function() { 1584 // CSS-wide keywords should reject with a SyntaxError. 1585 return doc.fonts.load("inherit").then(function() { 1586 ok(false, "load should throw when a CSS-wide keyword is given (TEST 45) (" + what + ")"); 1587 }, function(ex) { 1588 is(ex.name, "SyntaxError", "exception name when load is called with a CSS-wide keyword (TEST 45) (" + what + ")"); 1589 }); 1590 }); 1591 1592 p = p.then(function() { 1593 // CSS variables should throw a SyntaxError. 1594 return doc.fonts.load("16px var(--family)").then(function() { 1595 ok(false, "load should throw when CSS variables are used (TEST 45) (" + what + ")"); 1596 }, function(ex) { 1597 is(ex.name, "SyntaxError", "exception name when load is called with CSS variables (TEST 45) (" + what + ")"); 1598 }); 1599 }); 1600 1601 p = p.then(function() { 1602 // No matching font family names => return true. 1603 return doc.fonts.load("16px NonExistentFont1, NonExistentFont2").then(function(result) { 1604 is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")"); 1605 }); 1606 }); 1607 1608 p = p.then(function() { 1609 // Matching platform font family name => return true. 1610 return doc.fonts.load("16px NonExistentFont1, " + likelyPlatformFonts).then(function(result) { 1611 is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")"); 1612 }); 1613 }); 1614 1615 // Matching platform font family name, but using a different test 1616 // strings. (Platform fonts always return true from load, regardless 1617 // of the actual glyphs present.) 1618 [ 1619 { sample: "\0", desc: "a single non-matching glyph" }, 1620 { sample: "A\0", desc: "a matching and a non-matching glyph" }, 1621 { sample: "A", desc: "a matching glyph" }, 1622 { sample: "AB", desc: "multiple matching glyphs" } 1623 ].forEach(function({ sample, desc }) { 1624 p = p.then(function() { 1625 return doc.fonts.load("16px " + likelyPlatformFonts, sample).then(function(result) { 1626 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 + ")"); 1627 }); 1628 }); 1629 }); 1630 1631 p = p.then(function() { 1632 // No matching font family name, but an empty test string. 1633 return doc.fonts.load("16px NonExistentFont", "").then(function(result) { 1634 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 + ")"); 1635 }); 1636 }); 1637 1638 p = p.then(function() { 1639 // Matching font family name, but an empty test string. 1640 return doc.fonts.load("16px " + likelyPlatformFonts, "").then(function(result) { 1641 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 + ")"); 1642 }); 1643 }); 1644 }); 1645 return p; 1646 1647 }).then(function() { 1648 1649 // (TEST 46) Test the load method with script-created FontFaces. 1650 1651 var tests = [ 1652 // at least one matching FontFace is not yet loaded, but will load ==> resolve 1653 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }] }, 1654 { result: true, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded", included: true }] }, 1655 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }, { family: "Test", status: "loaded", included: true }] }, 1656 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loading", included: true }] }, 1657 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic", included: true }] }, 1658 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600", included: true }] }, 1659 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold", included: true }] }, 1660 { result: true, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] }, 1661 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] }, 1662 1663 // at least one matching FontFace is in an error state ==> reject 1664 { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "error" }] }, 1665 1666 // all matching FontFaces are already loaded ==> resolve 1667 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }] }, 1668 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "loaded", included: true }] }, 1669 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, 1670 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] }, 1671 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Irrelevant", status: "unloaded" }] }, 1672 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic", included: true }] }, 1673 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }] }, 1674 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed", included: true }] }, 1675 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }, { family: "Test", status: "loaded", weight: "600" }] }, 1676 { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "loaded", weight: "bold", included: true }] }, 1677 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "loaded", included: true }] }, 1678 1679 // no matching FontFaces at all ==> resolve 1680 { result: true, font: "16px Test", faces: [] }, 1681 { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] }, 1682 { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, 1683 { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] }, 1684 1685 // matching FontFace for one sample text character is already loaded but 1686 // the other is not (but will) ==> resolve 1687 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+62", included: true }] }, 1688 1689 // matching FontFaces for separate sample text characters are all 1690 // loaded ==> resolve 1691 { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "loaded", unicodeRange: "U+62", included: true }] }, 1692 ]; 1693 1694 var p = Promise.resolve(); 1695 sources.forEach(function({ win, doc, what }, i) { 1696 tests.forEach(function({ result, font, faces }, j) { 1697 p = p.then(function() { 1698 var fontFaces = []; 1699 faces.forEach(function(f, k) { 1700 var fontFace; 1701 if (f.status == "loaded") { 1702 fontFace = new win.FontFace(f.family, fontData, f); 1703 } else if (f.status == "error") { 1704 fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f); 1705 } else { 1706 fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test46." + [i, j, k] + ")", f); 1707 if (f.status == "loading") { 1708 fontFace.load(); 1709 } 1710 } 1711 is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 46) (" + what + ")"); 1712 doc.fonts.add(fontFace); 1713 fontFaces.push(fontFace); 1714 }); 1715 return doc.fonts.load(font, "ab").then(function(array) { 1716 ok(result, "load should resolve for subtest " + j + " (TEST 46) (" + what + ")"); 1717 var expected = []; 1718 for (var k = 0; k < faces.length; k++) { 1719 if (faces[k].included) { 1720 expected.push(fontFaces[k]); 1721 } 1722 } 1723 is(array.length, expected.length, "length of array load resolves with for subtest " + j + " (TEST 46) (" + what + ")"); 1724 for (var k = 0; k < array.length; k++) { 1725 is(array[k], expected[k], "value in array[" + k + "] load resolves with for subtest " + j + " (TEST 46) (" + what + ")"); 1726 } 1727 }, function(ex) { 1728 ok(!result, "load should not resolve for subtest " + j + " (TEST 46) (" + what + ")"); 1729 is(ex.name, "SyntaxError", "exception load's return value is rejected with for subtest " + j + " (TEST 46) (" + what + ")"); 1730 }).then(function() { 1731 doc.fonts.clear(); 1732 }); 1733 }); 1734 }); 1735 }); 1736 return p; 1737 1738 }).then(function() { 1739 1740 // (TEST 47) Test that CSS-connected FontFaces can't be added to other 1741 // FontFaceSets. 1742 1743 var style = document.querySelector("style"); 1744 style.textContent = "@font-face { font-family: something; src: url(x); }"; 1745 1746 var rule = style.sheet.cssRules[0]; 1747 1748 var all = Array.from(document.fonts); 1749 is(all.length, 1, "document.fonts should contain one FontFace (TEST 47)"); 1750 1751 var face = all[0]; 1752 1753 sourceDocuments.forEach(function({ doc, what }) { 1754 if (doc == document) { 1755 return; 1756 } 1757 1758 var exceptionName; 1759 try { 1760 doc.fonts.add(face); 1761 ok(false, "add should throw when attempting to add a CSS-connected FontFace to another FontFaceSet (TEST 47) (" + what + ")"); 1762 } catch (ex) { 1763 is(ex.name, "InvalidModificationError", "exception name when add is called with a CSS-connected FontFace from another FontFaceSet (TEST 47) (" + what + ")"); 1764 } 1765 }); 1766 1767 style.textContent = ""; 1768 document.body.offsetTop; 1769 1770 sourceDocuments.forEach(function({ doc, what }) { 1771 if (doc == document) { 1772 return; 1773 } 1774 1775 ok(!doc.fonts.has(face), "FontFaceSet initially doesn't have the FontFace (TEST 47) (" + what + ")"); 1776 doc.fonts.add(face); 1777 ok(doc.fonts.has(face), "add should allow a previously CSS-connected FontFace to be added to another FontFaceSet (TEST 47) (" + what + ")"); 1778 doc.fonts.clear(); 1779 }); 1780 1781 document.fonts.clear(); 1782 1783 }).then(function() { 1784 1785 // (TEST 48) Test that FontFaceSets that hold a combination of FontFaces 1786 // from different documents expose the right set of FontFaces. 1787 1788 // Expected FontFaceSet contents. 1789 var expected = { 1790 document: [], 1791 vdocument: [], 1792 ndocument: [], 1793 }; 1794 1795 // Create a CSS-connected FontFace in the top-level document. 1796 var style = document.querySelector("style"); 1797 style.textContent = "@font-face { font-family: something; src: url(x); }"; 1798 1799 var all = Array.from(document.fonts); 1800 is(all.length, 1, "document.fonts should contain one FontFace (TEST 48)"); 1801 1802 all[0]._description = "CSS-connected in document"; 1803 expected.document.push(all[0]); 1804 1805 // Create a CSS-connected FontFace in the visible iframe. 1806 var vstyle = vdocument.querySelector("style"); 1807 vstyle.textContent = "@font-face { font-family: somethingelse; src: url(x); }"; 1808 1809 all = Array.from(vdocument.fonts); 1810 all[0]._description = "CSS-connected in vdocument"; 1811 is(all.length, 1, "vdocument.fonts should contain one FontFace (TEST 48)"); 1812 1813 expected.vdocument.push(all[0]); 1814 1815 // Create a FontFace in each window and add it to each document's FontFaceSet. 1816 var faces = []; 1817 sourceWindows.forEach(function({ win, what: whatWin }, index) { 1818 var f = new win.FontFace("test" + index, "url(x)"); 1819 sourceDocuments.forEach(function({ doc, what: whatDoc }) { 1820 doc.fonts.add(f); 1821 expected[whatDoc].push(f); 1822 f._description = whatWin + "/" + whatDoc; 1823 }); 1824 }); 1825 1826 sourceDocuments.forEach(function({ doc, what }) { 1827 let allFonts = Array.from(doc.fonts); 1828 is(expected[what].length, allFonts.length, "expected FontFaceSet size (TEST 48) (" + what + ")"); 1829 for (let i = 0; i < expected[what].length; i++) { 1830 is(expected[what][i], allFonts[i], "expected FontFace (" + expected[what][i]._description + ") at index " + i + " (TEST 48) (" + what + ")"); 1831 } 1832 }); 1833 1834 vstyle.textContent = ""; 1835 style.textContent = ""; 1836 1837 sourceDocuments.forEach(function({ doc }) { doc.fonts.clear(); }); 1838 1839 }).then(function() { 1840 1841 // (TEST LAST) Test that a pending style sheet load prevents 1842 // document.fonts.status from being set to "loaded". 1843 1844 // First, add a FontFace to document.fonts that will load soon. 1845 var face = new FontFace("test", "url(BitPattern.woff?testlast)"); 1846 face.load(); 1847 document.fonts.add(face); 1848 1849 // Next, add a style sheet reference. 1850 var link = document.createElement("link"); 1851 link.rel = "stylesheet"; 1852 link.href = "neverending_stylesheet_load.sjs"; 1853 link.type = "text/css"; 1854 document.head.appendChild(link); 1855 1856 return setTimeoutZero() // wait for the style sheet to start loading 1857 .then(function() { 1858 document.fonts.clear(); 1859 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)"); 1860 document.head.removeChild(link); 1861 // XXX Removing the <link> element won't cancel the load of the 1862 // style sheet, so we can't do that to test that 1863 // document.fonts.ready is resolved once there are no more 1864 // loading style sheets. 1865 }); 1866 1867 // NOTE: It is important that this style sheet test comes last in the file, 1868 // as the neverending style sheet load will interfere with subsequent 1869 // sub-tests. 1870 1871 }).then(function() { 1872 1873 // End of the tests. 1874 SimpleTest.finish(); 1875 1876 }, function(aError) { 1877 1878 // Something failed. 1879 ok(false, "Something failed: " + aError); 1880 SimpleTest.finish(); 1881 1882 }); 1883 } 1884 1885 SimpleTest.waitForExplicitFinish(); 1886 SimpleTest.requestLongerTimeout(5); 1887 1888 </script> 1889 1890 <style></style> 1891 <div></div>