Add test for variation sequences cluster matching (#45681)

diff --git a/css/css-fonts/resources/vs/MPLUS1-Regular_without-cmap14-subset.ttf b/css/css-fonts/resources/vs/MPLUS1-Regular_without-cmap14-subset.ttf
new file mode 100644
index 0000000..b770966
--- /dev/null
+++ b/css/css-fonts/resources/vs/MPLUS1-Regular_without-cmap14-subset.ttf
Binary files differ
diff --git a/css/css-fonts/resources/vs/NotoColorEmoji-Regular_subset.ttf b/css/css-fonts/resources/vs/NotoColorEmoji-Regular_subset.ttf
new file mode 100644
index 0000000..24ab79f
--- /dev/null
+++ b/css/css-fonts/resources/vs/NotoColorEmoji-Regular_subset.ttf
Binary files differ
diff --git a/css/css-fonts/resources/vs/NotoEmoji-Regular_subset.ttf b/css/css-fonts/resources/vs/NotoEmoji-Regular_subset.ttf
new file mode 100644
index 0000000..0b054c7
--- /dev/null
+++ b/css/css-fonts/resources/vs/NotoEmoji-Regular_subset.ttf
Binary files differ
diff --git a/css/css-fonts/resources/vs/NotoEmoji-Regular_without-cmap14-subset.ttf b/css/css-fonts/resources/vs/NotoEmoji-Regular_without-cmap14-subset.ttf
new file mode 100644
index 0000000..3d00ef8
--- /dev/null
+++ b/css/css-fonts/resources/vs/NotoEmoji-Regular_without-cmap14-subset.ttf
Binary files differ
diff --git a/css/css-fonts/resources/vs/NotoSansJP-Regular_with-cmap14-subset.ttf b/css/css-fonts/resources/vs/NotoSansJP-Regular_with-cmap14-subset.ttf
new file mode 100644
index 0000000..edcb98e
--- /dev/null
+++ b/css/css-fonts/resources/vs/NotoSansJP-Regular_with-cmap14-subset.ttf
Binary files differ
diff --git a/css/css-fonts/resources/vs/NotoSansMath-Regular_without-cmap14-subset.ttf b/css/css-fonts/resources/vs/NotoSansMath-Regular_without-cmap14-subset.ttf
new file mode 100644
index 0000000..5436d06
--- /dev/null
+++ b/css/css-fonts/resources/vs/NotoSansMath-Regular_without-cmap14-subset.ttf
Binary files differ
diff --git a/css/css-fonts/resources/vs/STIXTwoMath-Regular_with-cmap14-subset.ttf b/css/css-fonts/resources/vs/STIXTwoMath-Regular_with-cmap14-subset.ttf
new file mode 100644
index 0000000..c9720cf
--- /dev/null
+++ b/css/css-fonts/resources/vs/STIXTwoMath-Regular_with-cmap14-subset.ttf
Binary files differ
diff --git a/css/css-fonts/support/css/variation-sequences.css b/css/css-fonts/support/css/variation-sequences.css
new file mode 100644
index 0000000..5977f17
--- /dev/null
+++ b/css/css-fonts/support/css/variation-sequences.css
@@ -0,0 +1,38 @@
+@font-face {
+    font-family: "MonoEmojiFont";
+    src: url(../../resources/vs/NotoEmoji-Regular_subset.ttf);
+}
+
+@font-face {
+    font-family: "ColorEmojiFont";
+    src: url(../../resources/vs/NotoColorEmoji-Regular_subset.ttf);
+}
+
+@font-face {
+    font-family: "EmojiFontWithBaseCharOnly";
+    src: url(../../resources/vs/NotoEmoji-Regular_without-cmap14-subset.ttf);
+}
+
+@font-face {
+    font-family: "CJKFontWithVS";
+    src: url(../../resources/vs/NotoSansJP-Regular_with-cmap14-subset.ttf);
+}
+
+@font-face {
+    font-family: "CJKFontWithBaseCharOnly";
+    src: url(../../resources/vs/MPLUS1-Regular_without-cmap14-subset.ttf);
+}
+
+@font-face {
+    font-family: "MathFontWithVS";
+    src: url(../../resources/vs/STIXTwoMath-Regular_with-cmap14-subset.ttf);
+}
+
+@font-face {
+    font-family: "MathFontWithBaseCharOnly";
+    src: url(../../resources/vs/NotoSansMath-Regular_without-cmap14-subset.ttf);
+}
+
+body {
+    font-size: 24px;
+}
\ No newline at end of file
diff --git a/css/css-fonts/support/js/variation-sequences.js b/css/css-fonts/support/js/variation-sequences.js
new file mode 100644
index 0000000..84c5a1a
--- /dev/null
+++ b/css/css-fonts/support/js/variation-sequences.js
@@ -0,0 +1,125 @@
+var baseChars = {
+    "emoji": "\u{1fae8}",
+    "cjk": "\u{8279}",
+    "math": "\u{2205}"
+};
+
+var variationSelectors = {
+    "emoji": ["\u{fe0e}", "\u{fe0f}"],
+    "cjk": ["", "\u{FE00}", "\u{FE01}", "\u{e0100}", "\u{e0101}",
+        "\u{e0102}"
+    ],
+    "math": ["", "\u{FE00}"]
+};
+
+var families = {
+    "emoji": ["ColorEmojiFont", "MonoEmojiFont",
+        "EmojiFontWithBaseCharOnly",
+        "sans-serif"
+    ],
+    "cjk": ["CJKFontWithVS", "CJKFontWithBaseCharOnly",
+        "sans-serif"
+    ],
+    "math": ["MathFontWithVS", "MathFontWithBaseCharOnly",
+        "sans-serif"
+    ]
+};
+
+var variationSequenceFamilies = new Map([
+    ["\u{1fae8}\u{fe0e}", "MonoEmojiFont"],
+    ["\u{1fae8}\u{fe0f}", "ColorEmojiFont"],
+    ["\u{8279}\u{fe00}", "CJKFontWithVS"],
+    ["\u{8279}\u{fe01}", "CJKFontWithVS"],
+    ["\u{8279}\u{e0100}", "CJKFontWithVS"],
+    ["\u{8279}\u{e0101}", "CJKFontWithVS"],
+    ["\u{8279}\u{e0102}", "CJKFontWithVS"],
+    ["\u{2205}\u{FE00}", "MathFontWithVS"]
+]);
+
+var baseCharFamilies = new Map([
+    ["\u{1fae8}", new Set(["MonoEmojiFont", "ColorEmojiFont",
+        "EmojiFontWithBaseCharOnly"
+    ])],
+    ["\u{8279}", new Set(["CJKFontWithVS",
+        "CJKFontWithBaseCharOnly"
+    ])],
+    ["\u{2205}", new Set(["MathFontWithVS",
+        "MathFontWithBaseCharOnly"
+    ])]
+]);
+
+const range = function*(l) {
+    for (let i = 0; i < l; i += 1) yield i;
+}
+const isEmpty = arr =>
+    arr.length === 0;
+
+const permutations =
+    function*(a) {
+  const r = arguments[1] || [];
+  if (isEmpty(a))
+    yield r;
+  for (let i of range(a.length)) {
+    const aa = [...a];
+    const rr = [...r, ...aa.splice(i, 1)];
+    yield* permutations(aa, rr);
+  }
+}
+
+function getMatchedFamilyForVariationSequence(
+    familyList, baseCharacter, variationSelector) {
+  const variationSequence = baseCharacter + variationSelector;
+  // First try to find a match for the whole variation sequence.
+  if (variationSequenceFamilies.has(variationSequence)) {
+    const matchedFamily = variationSequenceFamilies.get(variationSequence);
+    if (familyList.includes(matchedFamily)) {
+      return matchedFamily;
+    }
+  }
+  // If failed, try to match only the base character from the
+  // variation sequence.
+  if (baseCharFamilies.has(baseCharacter)) {
+    const eligibleFamilies = baseCharFamilies.get(baseCharacter);
+    const matchedFamilies =
+        familyList.filter(value => eligibleFamilies.has(value));
+    if (matchedFamilies.length) {
+      return matchedFamilies[0];
+    }
+  }
+  // We should not reach here, we should always match one of the
+  // specified web fonts in the tests.
+  return "";
+}
+
+function generateContent(
+    families, baseChar, variationSelectors, getFontFamilyValue) {
+  var rootElem = document.createElement('div');
+  // We want to test all possible combinations of variation
+  // selectors and font-family list values. For the refs,
+  // we explicitly specify the font that we expect to be
+  // matched from the maps at the beginning of the files.
+  const allFamiliesLists = permutations(families);
+  for (const familyList of allFamiliesLists) {
+    for (const variationSelector of variationSelectors) {
+      const contentSpan = document.createElement("span");
+      contentSpan.textContent = baseChar + variationSelector;
+      contentSpan.style.fontFamily =
+          getFontFamilyValue(familyList, baseChar, variationSelector);
+      rootElem.appendChild(contentSpan);
+    }
+  }
+  document.body.appendChild(rootElem);
+}
+
+function generateVariationSequenceTests(type) {
+  var getFontFamilyValue = (familyList, baseChar, variationSelector) => {
+    return familyList.join(', ');
+  }
+  generateContent(families[type], baseChars[type], variationSelectors[type], getFontFamilyValue);
+}
+
+function generateVariationSequenceRefs(type) {
+  generateContent(
+      families[type], baseChars[type], variationSelectors[type],
+      getMatchedFamilyForVariationSequence);
+}
diff --git a/css/css-fonts/variation-sequences-ref.html b/css/css-fonts/variation-sequences-ref.html
new file mode 100644
index 0000000..a44f18b
--- /dev/null
+++ b/css/css-fonts/variation-sequences-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="UTF-8" />
+<title>CSS Test: Cluster Matching Variation Sequences</title>
+<link rel="stylesheet" type="text/css" href="support/css/variation-sequences.css" />
+<script type="text/javascript" src="support/js/variation-sequences.js"></script>
+<body></body>
+<script>
+    generateVariationSequenceRefs("emoji");
+    generateVariationSequenceRefs("cjk");
+    generateVariationSequenceRefs("math");
+</script>
\ No newline at end of file
diff --git a/css/css-fonts/variation-sequences.html b/css/css-fonts/variation-sequences.html
new file mode 100644
index 0000000..91e46a8
--- /dev/null
+++ b/css/css-fonts/variation-sequences.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="UTF-8" />
+<title>CSS Test: Cluster Matching Variation Sequences</title>
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#cluster-matching" />
+<link rel="help" href="https://unicode.org/reports/tr51/" />
+<link rel="help" href="https://unicode.org/reports/tr37/" />
+<link rel="help" href="https://www.unicode.org/Public/UNIDATA/StandardizedVariants.txt" />
+<link rel="help" href="https://www.unicode.org/versions/Unicode15.1.0/ch23.pdf#G19053" />
+<link rel="match" href="variation-sequences-ref.html">
+<meta name="assert" content="Variation sequences should be taken into account during cluster matching.">
+<link rel="stylesheet" type="text/css" href="support/css/variation-sequences.css" />
+<script type="text/javascript" src="support/js/variation-sequences.js"></script>
+<body></body>
+<script>
+    generateVariationSequenceTests("emoji");
+    generateVariationSequenceTests("cjk");
+    generateVariationSequenceTests("math");
+</script>
\ No newline at end of file