tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>