[Font Loading] Implement FontFaceSet
https://bugs.webkit.org/show_bug.cgi?id=153348

Reviewed by Simon Fraser.

Source/WebCore:

The CSS Font Loading spec includes a FontFaceSet object which represents
a collection of FontFaces. This patch implements such an object, and
backs it with a vector of FontFaces. Similarly to the FontFace object,
FontFaceSet is separated into a FontFaceSet frontend object and a
CSSFontFaceSet backend object, which actually owns the FontFace objects.
All the interaction with Promises is performed in the frontend object.

This patch does not implement the EventTarget part of the FontFaceSet
API, so the only way to know when a font is finished loading is by using
the associated Promise objects.

The CSS Font Loading spec describes how the Document should vend an
instance of FontFaceSet which represents the font faces currently
associated with the Document. However, that functionality is
forthcoming. Currently, the only way to get a FontFaceSet is to create
one yourself (using the constructor). Therefore, this patch does not
implement the spec's notion of a "CSS-connected font face."

Test: fast/text/font-face-set-javascript.html

* CMakeLists.txt: Add new files.
* DerivedSources.make: Ditto.
* WebCore.vcxproj/WebCore.vcxproj: Ditto.
* WebCore.vcxproj/WebCore.vcxproj.filters: Ditto.
* WebCore.xcodeproj/project.pbxproj: Ditto.
* bindings/js/JSFontFaceSetCustom.cpp: Added.
(WebCore::JSFontFaceSet::ready): Use the Promise member.
(WebCore::JSFontFaceSet::entries): Use existing iterator code.
(WebCore::JSFontFaceSet::keys):
(WebCore::JSFontFaceSet::values):
* css/CSSAllInOne.cpp: Add new files.
* css/CSSFontFace.cpp: We now have a collection of clients (instead of
just one). Also, we need to keep a pointer to our FontFace wrapper.
(WebCore::CSSFontFace::CSSFontFace):
(WebCore::CSSFontFace::addClient):
(WebCore::CSSFontFace::removeClient):
(WebCore::CSSFontFace::setStatus): Rename the delegate callback to be
more clear.
(WebCore::CSSFontFace::fontLoaded):
(WebCore::CSSFontFace::addedToSegmentedFontFace): Deleted.
(WebCore::CSSFontFace::removedFromSegmentedFontFace): Deleted.
* css/CSSFontFace.h: Same as above.
(WebCore::CSSFontFace::create):
(WebCore::CSSFontFace::Client::~Client):
(WebCore::CSSFontFace::Client::kick):
(WebCore::CSSFontFace::Client::stateChanged):
(WebCore::CSSFontFace::wrapper):
(WebCore::CSSFontFaceClient::~CSSFontFaceClient): Deleted.
* css/CSSFontFaceSet.cpp: Added. Initial imlementation.
(WebCore::CSSFontFaceSet::CSSFontFaceSet):
(WebCore::CSSFontFaceSet::~CSSFontFaceSet):
(WebCore::CSSFontFaceSet::incrementActiveCount):
(WebCore::CSSFontFaceSet::decrementActiveCount):
(WebCore::CSSFontFaceSet::has):
(WebCore::CSSFontFaceSet::add):
(WebCore::CSSFontFaceSet::remove):
(WebCore::extractFamilies):
(WebCore::familiesIntersect): Because this is an initial imlementation,
this function is not optimized. A subsequent patch (which implements
Document.fonts) will optimize this.
(WebCore::CSSFontFaceSet::matchingFaces):
(WebCore::CSSFontFaceSet::load):
(WebCore::CSSFontFaceSet::check):
(WebCore::CSSFontFaceSet::stateChanged):
* css/CSSFontFaceSet.h: Added.
(WebCore::CSSFontFaceSetClient::~CSSFontFaceSetClient):
(WebCore::CSSFontFaceSet::size):
(WebCore::CSSFontFaceSet::operator[]):
(WebCore::CSSFontFaceSet::status):
* css/CSSFontSelector.cpp:
(WebCore::CSSFontSelector::familyNameFromPrimitive):
(WebCore::CSSFontSelector::registerLocalFontFacesForFamily):
(WebCore::CSSFontSelector::addFontFaceRule):
(WebCore::familyNameFromPrimitive): Deleted.
(WebCore::CSSFontSelector::kick): Deleted.
* css/CSSFontSelector.h:
* css/CSSSegmentedFontFace.cpp:
(WebCore::CSSSegmentedFontFace::~CSSSegmentedFontFace):
(WebCore::CSSSegmentedFontFace::appendFontFace):
(WebCore::CSSSegmentedFontFace::kick):
(WebCore::CSSSegmentedFontFace::fontLoaded): Deleted.
* css/CSSSegmentedFontFace.h:
* css/FontFace.cpp:
(WebCore::FontFace::FontFace):
(WebCore::FontFace::~FontFace):
(WebCore::FontFace::stateChanged): Renamed to make its purpose clearer.
(WebCore::FontFace::kick): Deleted.
* css/FontFace.h:
* css/FontFaceSet.cpp: Added.
(WebCore::createPromise):
(WebCore::FontFaceSet::FontFaceSet):
(WebCore::FontFaceSet::~FontFaceSet):
(WebCore::FontFaceSet::Iterator::Iterator):
(WebCore::FontFaceSet::Iterator::next):
(WebCore::FontFaceSet::PendingPromise::PendingPromise):
(WebCore::FontFaceSet::PendingPromise::~PendingPromise):
(WebCore::FontFaceSet::has):
(WebCore::FontFaceSet::size):
(WebCore::FontFaceSet::add):
(WebCore::FontFaceSet::remove):
(WebCore::FontFaceSet::clear):
(WebCore::FontFaceSet::load): Most of the complexity of loading is
due to the promises involved. Rather than use the Javascript function
Promise.all(), this patch builds a data structure to represent the
promises which need to be resolved. When fonts finish loading, we look
at the data structure to determine which promises to resolve.
(WebCore::FontFaceSet::check):
(WebCore::FontFaceSet::status):
(WebCore::FontFaceSet::canSuspendForDocumentSuspension):
(WebCore::FontFaceSet::startedLoading):
(WebCore::FontFaceSet::completedLoading):
(WebCore::FontFaceSet::fulfillPromise): Keep the promise alive.
(WebCore::FontFaceSet::faceFinished):
* css/FontFaceSet.h: Added.
(WebCore::FontFaceSet::create):
(WebCore::FontFaceSet::load):
(WebCore::FontFaceSet::check):
(WebCore::FontFaceSet::createIterator):
(WebCore::FontFaceSet::PendingPromise::create):
* css/FontFaceSet.idl: Added.
* dom/EventNames.h:
* dom/EventTargetFactory.in:

LayoutTests:

* fast/text/font-face-set-javascript-expected.txt: Added.
* fast/text/font-face-set-javascript.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@196747 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index f9d9e25..a2ace4c 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,13 @@
+2016-02-17  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [Font Loading] Implement FontFaceSet
+        https://bugs.webkit.org/show_bug.cgi?id=153348
+
+        Reviewed by Simon Fraser.
+
+        * fast/text/font-face-set-javascript-expected.txt: Added.
+        * fast/text/font-face-set-javascript.html: Added.
+
 2016-02-17  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r196738.
diff --git a/LayoutTests/fast/text/font-face-set-javascript-expected.txt b/LayoutTests/fast/text/font-face-set-javascript-expected.txt
new file mode 100644
index 0000000..ec88ef3
--- /dev/null
+++ b/LayoutTests/fast/text/font-face-set-javascript-expected.txt
@@ -0,0 +1,48 @@
+PASS new FontFaceSet() threw exception TypeError: Not enough arguments.
+PASS new FontFaceSet([]).size is 0
+PASS new FontFaceSet([fontFace1]).size is 1
+PASS fontFaceSet.status is "loaded"
+PASS item.done is false
+PASS item.value is [fontFace1, fontFace1]
+PASS item.done is true
+PASS item.done is false
+PASS item.value is fontFace1
+PASS item.done is true
+PASS item.done is false
+PASS item.value is fontFace1
+PASS item.done is true
+PASS fontFaceSet.add(fontFace2) is fontFaceSet
+PASS fontFaceSet.size is 2
+PASS item.done is false
+PASS item.value is fontFace1
+PASS item.done is false
+PASS item.value is fontFace2
+PASS item.done is true
+PASS fontFaceSet.delete(fontFace1) is true
+PASS fontFaceSet.delete(fontFace3) is false
+PASS fontFaceSet.size is 0
+PASS fontFaceSet.values().next().done is true
+PASS fontFaceSet.check('garbage') threw exception Error: SyntaxError: DOM Exception 12.
+PASS fontFaceSet.check('16px garbage') is true
+PASS fontFaceSet.check('16px family1') is false
+PASS fontFaceSet.status is "loaded"
+PASS item.code is item.SYNTAX_ERR
+PASS fontFaceSet.check('16px family1') is false
+PASS item is []
+PASS item.code is item.NETWORK_ERR
+PASS fontFaceSet.check('16px family3') is false
+PASS fontFaceSet.status is "loading"
+PASS item is [fontFace3]
+PASS fontFaceSet.check('16px family3') is true
+PASS fontFaceSet.status is "loaded"
+PASS fontFaceSet.status is "loaded"
+PASS item is [fontFace3]
+PASS item is [fontFace3, fontFace4]
+PASS item is fontFaceSet
+PASS fontFaceSet.status is "loaded"
+PASS fontFaceSet.status is "loading"
+PASS item is fontFaceSet
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/text/font-face-set-javascript.html b/LayoutTests/fast/text/font-face-set-javascript.html
new file mode 100644
index 0000000..91fa3b9
--- /dev/null
+++ b/LayoutTests/fast/text/font-face-set-javascript.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+if (window.internals)
+    window.internals.clearMemoryCache();
+
+var fontFace1 = new FontFace("family1", "url('asdf')", {});
+var fontFace2 = new FontFace("family2", "url('asdf')", {});
+var fontFace3 = new FontFace("family3", "url('../../resources/Ahem.ttf')", {});
+var fontFace4 = new FontFace("family3", "url('../../resources/Ahem.ttf')", {'weight': 'bold'});
+var fontFace5 = new FontFace("family5", "url('../../resources/Ahem.otf')", {});
+
+shouldThrow("new FontFaceSet()");
+shouldBe("new FontFaceSet([]).size", "0");
+shouldBe("new FontFaceSet([fontFace1]).size", "1");
+
+var fontFaceSet = new FontFaceSet([]);
+shouldBeEqualToString("fontFaceSet.status", "loaded");
+fontFaceSet.add(fontFace1);
+var iterator = fontFaceSet.entries();
+var item = iterator.next();
+shouldBeFalse("item.done");
+shouldBe("item.value", "[fontFace1, fontFace1]");
+item = iterator.next();
+shouldBeTrue("item.done");
+
+iterator = fontFaceSet.keys();
+item = iterator.next();
+shouldBeFalse("item.done");
+shouldBe("item.value", "fontFace1");
+item = iterator.next();
+shouldBeTrue("item.done");
+
+iterator = fontFaceSet.values();
+item = iterator.next();
+shouldBeFalse("item.done");
+shouldBe("item.value", "fontFace1");
+item = iterator.next();
+shouldBeTrue("item.done");
+
+shouldBe("fontFaceSet.add(fontFace2)", "fontFaceSet");
+shouldBe("fontFaceSet.size", "2");
+
+iterator = fontFaceSet.keys();
+item = iterator.next();
+shouldBeFalse("item.done");
+shouldBe("item.value", "fontFace1");
+item = iterator.next();
+shouldBeFalse("item.done");
+shouldBe("item.value", "fontFace2");
+item = iterator.next();
+shouldBeTrue("item.done");
+
+shouldBe("fontFaceSet.delete(fontFace1)", "true");
+shouldBe("fontFaceSet.delete(fontFace3)", "false");
+fontFaceSet.clear();
+shouldBe("fontFaceSet.size", "0");
+shouldBeTrue("fontFaceSet.values().next().done");
+shouldThrow("fontFaceSet.check('garbage')");
+shouldBeTrue("fontFaceSet.check('16px garbage')");
+
+self.jsTestIsAsync = true;
+fontFaceSet.add(fontFace1);
+shouldBeFalse("fontFaceSet.check('16px family1')");
+var item;
+fontFaceSet.load("garbage").then(function(arg) {
+    testFailed("Should not be able to parse garbage");
+    finishJSTest();
+}, function(arg) {
+    item = arg;
+    shouldBe("item.code", "item.SYNTAX_ERR");
+    shouldBeFalse("fontFaceSet.check('16px family1')");
+    return fontFaceSet.load("16px garbage");
+}).then(function(arg) {
+    item = arg;
+    shouldBe("item", "[]");
+    return fontFaceSet.load("16px family1");
+}, function(arg) {
+    testFailed("Should not be able to parse garbage");
+    finishJSTest();
+}).then(function(arg) {
+    testFailed("Bogus URL should not load");
+    finishJSTest();
+}, function(arg) {
+    item = arg;
+    shouldBe("item.code", "item.NETWORK_ERR");
+    fontFaceSet.add(fontFace3);
+    shouldBeFalse("fontFaceSet.check('16px family3')");
+    var result = fontFaceSet.load("16px family3");
+    shouldBeEqualToString("fontFaceSet.status", "loading");
+    return result;
+}).then(function(arg) {
+    item = arg;
+    shouldBe("item", "[fontFace3]");
+    shouldBeTrue("fontFaceSet.check('16px family3')");
+    shouldBeEqualToString("fontFaceSet.status", "loaded");
+    var result = fontFaceSet.load("16px family3"); // Test when it's in the cache.
+    shouldBeEqualToString("fontFaceSet.status", "loaded");
+    return result;
+}, function(arg) {
+    testFailed("Real URL should load");
+    finishJSTest();
+}).then(function(arg) {
+    item = arg;
+    shouldBe("item", "[fontFace3]");
+    fontFaceSet.add(fontFace4);
+    return fontFaceSet.load("16px family3");
+}, function(arg) {
+    testFailed("Real URL should load");
+    finishJSTest();
+}).then(function(arg) {
+    item = arg;
+    shouldBe("item", "[fontFace3, fontFace4]");
+    fontFaceSet.add(fontFace4);
+    fontFaceSet.load("16px family3");
+    return fontFaceSet.ready;
+}, function(arg) {
+    testFailed("Multiple matching faces should load");
+    finishJSTest();
+}).then(function(arg) {
+    item = arg;
+    shouldBe("item", "fontFaceSet");
+    fontFaceSet.add(fontFace5);
+    shouldBeEqualToString("fontFaceSet.status", "loaded");
+    fontFaceSet.load("16px family5");
+    shouldBeEqualToString("fontFaceSet.status", "loading");
+    return fontFaceSet.ready;
+}, function(arg) {
+    testFailed("Ready attribute should never fail");
+    finishJSTest();
+}).then(function(arg) {
+    item = arg;
+    shouldBe("item", "fontFaceSet");
+    finishJSTest();
+}, function(arg) {
+    testFailed("Ready attribute should never fail");
+    finishJSTest();
+});
+shouldBeEqualToString("fontFaceSet.status", "loaded");
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/global-constructors-attributes-expected.txt b/LayoutTests/js/dom/global-constructors-attributes-expected.txt
index 9f15e61..15a4aea 100644
--- a/LayoutTests/js/dom/global-constructors-attributes-expected.txt
+++ b/LayoutTests/js/dom/global-constructors-attributes-expected.txt
@@ -378,6 +378,11 @@
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').hasOwnProperty('set') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').enumerable is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').configurable is true
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').value is FontFaceSet
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('get') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('set') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').enumerable is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').configurable is true
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').value is FormData
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('get') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('set') is false
diff --git a/LayoutTests/platform/efl/js/dom/global-constructors-attributes-expected.txt b/LayoutTests/platform/efl/js/dom/global-constructors-attributes-expected.txt
index 73664a4..012bf08 100644
--- a/LayoutTests/platform/efl/js/dom/global-constructors-attributes-expected.txt
+++ b/LayoutTests/platform/efl/js/dom/global-constructors-attributes-expected.txt
@@ -373,6 +373,11 @@
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').hasOwnProperty('set') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').enumerable is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').configurable is true
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').value is FontFaceSet
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('get') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('set') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').enumerable is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').configurable is true
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').value is FormData
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('get') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('set') is false
diff --git a/LayoutTests/platform/gtk/js/dom/global-constructors-attributes-expected.txt b/LayoutTests/platform/gtk/js/dom/global-constructors-attributes-expected.txt
index e47aebf..c4e5c4c 100644
--- a/LayoutTests/platform/gtk/js/dom/global-constructors-attributes-expected.txt
+++ b/LayoutTests/platform/gtk/js/dom/global-constructors-attributes-expected.txt
@@ -378,6 +378,11 @@
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').hasOwnProperty('set') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').enumerable is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').configurable is true
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').value is FontFaceSet
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('get') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('set') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').enumerable is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').configurable is true
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').value is FormData
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('get') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('set') is false
diff --git a/LayoutTests/platform/mac-mavericks/js/dom/global-constructors-attributes-expected.txt b/LayoutTests/platform/mac-mavericks/js/dom/global-constructors-attributes-expected.txt
index 231d758..e60e455 100644
--- a/LayoutTests/platform/mac-mavericks/js/dom/global-constructors-attributes-expected.txt
+++ b/LayoutTests/platform/mac-mavericks/js/dom/global-constructors-attributes-expected.txt
@@ -378,6 +378,11 @@
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').hasOwnProperty('set') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').enumerable is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').configurable is true
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').value is FontFaceSet
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('get') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('set') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').enumerable is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').configurable is true
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').value is FormData
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('get') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('set') is false
diff --git a/LayoutTests/platform/mac-yosemite/js/dom/global-constructors-attributes-expected.txt b/LayoutTests/platform/mac-yosemite/js/dom/global-constructors-attributes-expected.txt
index b0b1003..774478e 100644
--- a/LayoutTests/platform/mac-yosemite/js/dom/global-constructors-attributes-expected.txt
+++ b/LayoutTests/platform/mac-yosemite/js/dom/global-constructors-attributes-expected.txt
@@ -423,6 +423,11 @@
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').hasOwnProperty('set') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').enumerable is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').configurable is true
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').value is FontFaceSet
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('get') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('set') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').enumerable is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').configurable is true
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').value is FormData
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('get') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('set') is false
diff --git a/LayoutTests/platform/mac/js/dom/global-constructors-attributes-expected.txt b/LayoutTests/platform/mac/js/dom/global-constructors-attributes-expected.txt
index 7833c21..bda4165 100644
--- a/LayoutTests/platform/mac/js/dom/global-constructors-attributes-expected.txt
+++ b/LayoutTests/platform/mac/js/dom/global-constructors-attributes-expected.txt
@@ -423,6 +423,11 @@
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').hasOwnProperty('set') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').enumerable is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').configurable is true
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').value is FontFaceSet
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('get') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('set') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').enumerable is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').configurable is true
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').value is FormData
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('get') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('set') is false
diff --git a/LayoutTests/platform/win/js/dom/global-constructors-attributes-expected.txt b/LayoutTests/platform/win/js/dom/global-constructors-attributes-expected.txt
index ef03038..540f9a5 100644
--- a/LayoutTests/platform/win/js/dom/global-constructors-attributes-expected.txt
+++ b/LayoutTests/platform/win/js/dom/global-constructors-attributes-expected.txt
@@ -298,6 +298,11 @@
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').hasOwnProperty('set') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').enumerable is false
 PASS Object.getOwnPropertyDescriptor(global, 'FontFace').configurable is true
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').value is FontFaceSet
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('get') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').hasOwnProperty('set') is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').enumerable is false
+PASS Object.getOwnPropertyDescriptor(global, 'FontFaceSet').configurable is true
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').value is FormData
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('get') is false
 PASS Object.getOwnPropertyDescriptor(global, 'FormData').hasOwnProperty('set') is false
diff --git a/Source/WebCore/CMakeLists.txt b/Source/WebCore/CMakeLists.txt
index f76fc98..100ab4d 100644
--- a/Source/WebCore/CMakeLists.txt
+++ b/Source/WebCore/CMakeLists.txt
@@ -331,6 +331,7 @@
     css/Counter.idl
     css/DOMWindowCSS.idl
     css/FontFace.idl
+    css/FontFaceSet.idl
     css/FontLoader.idl
     css/MediaList.idl
     css/MediaQueryList.idl
@@ -1110,6 +1111,7 @@
     bindings/js/JSCSSRuleListCustom.cpp
     bindings/js/JSCSSStyleDeclarationCustom.cpp
     bindings/js/JSFontFaceCustom.cpp
+    bindings/js/JSFontFaceSetCustom.cpp
     bindings/js/JSCSSValueCustom.cpp
     bindings/js/JSCallbackData.cpp
     bindings/js/JSCanvasRenderingContext2DCustom.cpp
@@ -1302,8 +1304,10 @@
     css/CSSCursorImageValue.cpp
     css/CSSDefaultStyleSheets.cpp
     css/CSSFilterImageValue.cpp
+    css/FontFaceSet.cpp
     css/FontFace.cpp
     css/CSSFontFace.cpp
+    css/CSSFontFaceSet.cpp
     css/CSSFontFaceLoadEvent.cpp
     css/CSSFontFaceRule.cpp
     css/CSSFontFaceSource.cpp
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 27353e7..80b5e23 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,133 @@
+2016-02-17  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [Font Loading] Implement FontFaceSet
+        https://bugs.webkit.org/show_bug.cgi?id=153348
+
+        Reviewed by Simon Fraser.
+
+        The CSS Font Loading spec includes a FontFaceSet object which represents
+        a collection of FontFaces. This patch implements such an object, and
+        backs it with a vector of FontFaces. Similarly to the FontFace object,
+        FontFaceSet is separated into a FontFaceSet frontend object and a
+        CSSFontFaceSet backend object, which actually owns the FontFace objects.
+        All the interaction with Promises is performed in the frontend object.
+
+        This patch does not implement the EventTarget part of the FontFaceSet
+        API, so the only way to know when a font is finished loading is by using
+        the associated Promise objects.
+
+        The CSS Font Loading spec describes how the Document should vend an
+        instance of FontFaceSet which represents the font faces currently
+        associated with the Document. However, that functionality is
+        forthcoming. Currently, the only way to get a FontFaceSet is to create
+        one yourself (using the constructor). Therefore, this patch does not
+        implement the spec's notion of a "CSS-connected font face."
+
+        Test: fast/text/font-face-set-javascript.html
+
+        * CMakeLists.txt: Add new files.
+        * DerivedSources.make: Ditto.
+        * WebCore.vcxproj/WebCore.vcxproj: Ditto.
+        * WebCore.vcxproj/WebCore.vcxproj.filters: Ditto.
+        * WebCore.xcodeproj/project.pbxproj: Ditto.
+        * bindings/js/JSFontFaceSetCustom.cpp: Added.
+        (WebCore::JSFontFaceSet::ready): Use the Promise member.
+        (WebCore::JSFontFaceSet::entries): Use existing iterator code.
+        (WebCore::JSFontFaceSet::keys):
+        (WebCore::JSFontFaceSet::values):
+        * css/CSSAllInOne.cpp: Add new files.
+        * css/CSSFontFace.cpp: We now have a collection of clients (instead of
+        just one). Also, we need to keep a pointer to our FontFace wrapper.
+        (WebCore::CSSFontFace::CSSFontFace):
+        (WebCore::CSSFontFace::addClient):
+        (WebCore::CSSFontFace::removeClient):
+        (WebCore::CSSFontFace::setStatus): Rename the delegate callback to be
+        more clear.
+        (WebCore::CSSFontFace::fontLoaded):
+        (WebCore::CSSFontFace::addedToSegmentedFontFace): Deleted.
+        (WebCore::CSSFontFace::removedFromSegmentedFontFace): Deleted.
+        * css/CSSFontFace.h: Same as above.
+        (WebCore::CSSFontFace::create):
+        (WebCore::CSSFontFace::Client::~Client):
+        (WebCore::CSSFontFace::Client::kick):
+        (WebCore::CSSFontFace::Client::stateChanged):
+        (WebCore::CSSFontFace::wrapper):
+        (WebCore::CSSFontFaceClient::~CSSFontFaceClient): Deleted.
+        * css/CSSFontFaceSet.cpp: Added. Initial imlementation.
+        (WebCore::CSSFontFaceSet::CSSFontFaceSet):
+        (WebCore::CSSFontFaceSet::~CSSFontFaceSet):
+        (WebCore::CSSFontFaceSet::incrementActiveCount):
+        (WebCore::CSSFontFaceSet::decrementActiveCount):
+        (WebCore::CSSFontFaceSet::has):
+        (WebCore::CSSFontFaceSet::add):
+        (WebCore::CSSFontFaceSet::remove):
+        (WebCore::extractFamilies):
+        (WebCore::familiesIntersect): Because this is an initial imlementation,
+        this function is not optimized. A subsequent patch (which implements
+        Document.fonts) will optimize this.
+        (WebCore::CSSFontFaceSet::matchingFaces):
+        (WebCore::CSSFontFaceSet::load):
+        (WebCore::CSSFontFaceSet::check):
+        (WebCore::CSSFontFaceSet::stateChanged):
+        * css/CSSFontFaceSet.h: Added.
+        (WebCore::CSSFontFaceSetClient::~CSSFontFaceSetClient):
+        (WebCore::CSSFontFaceSet::size):
+        (WebCore::CSSFontFaceSet::operator[]):
+        (WebCore::CSSFontFaceSet::status):
+        * css/CSSFontSelector.cpp:
+        (WebCore::CSSFontSelector::familyNameFromPrimitive):
+        (WebCore::CSSFontSelector::registerLocalFontFacesForFamily):
+        (WebCore::CSSFontSelector::addFontFaceRule):
+        (WebCore::familyNameFromPrimitive): Deleted.
+        (WebCore::CSSFontSelector::kick): Deleted.
+        * css/CSSFontSelector.h:
+        * css/CSSSegmentedFontFace.cpp:
+        (WebCore::CSSSegmentedFontFace::~CSSSegmentedFontFace):
+        (WebCore::CSSSegmentedFontFace::appendFontFace):
+        (WebCore::CSSSegmentedFontFace::kick):
+        (WebCore::CSSSegmentedFontFace::fontLoaded): Deleted.
+        * css/CSSSegmentedFontFace.h:
+        * css/FontFace.cpp:
+        (WebCore::FontFace::FontFace):
+        (WebCore::FontFace::~FontFace):
+        (WebCore::FontFace::stateChanged): Renamed to make its purpose clearer.
+        (WebCore::FontFace::kick): Deleted.
+        * css/FontFace.h:
+        * css/FontFaceSet.cpp: Added.
+        (WebCore::createPromise):
+        (WebCore::FontFaceSet::FontFaceSet):
+        (WebCore::FontFaceSet::~FontFaceSet):
+        (WebCore::FontFaceSet::Iterator::Iterator):
+        (WebCore::FontFaceSet::Iterator::next):
+        (WebCore::FontFaceSet::PendingPromise::PendingPromise):
+        (WebCore::FontFaceSet::PendingPromise::~PendingPromise):
+        (WebCore::FontFaceSet::has):
+        (WebCore::FontFaceSet::size):
+        (WebCore::FontFaceSet::add):
+        (WebCore::FontFaceSet::remove):
+        (WebCore::FontFaceSet::clear):
+        (WebCore::FontFaceSet::load): Most of the complexity of loading is
+        due to the promises involved. Rather than use the Javascript function
+        Promise.all(), this patch builds a data structure to represent the
+        promises which need to be resolved. When fonts finish loading, we look
+        at the data structure to determine which promises to resolve.
+        (WebCore::FontFaceSet::check):
+        (WebCore::FontFaceSet::status):
+        (WebCore::FontFaceSet::canSuspendForDocumentSuspension):
+        (WebCore::FontFaceSet::startedLoading):
+        (WebCore::FontFaceSet::completedLoading):
+        (WebCore::FontFaceSet::fulfillPromise): Keep the promise alive.
+        (WebCore::FontFaceSet::faceFinished):
+        * css/FontFaceSet.h: Added.
+        (WebCore::FontFaceSet::create):
+        (WebCore::FontFaceSet::load):
+        (WebCore::FontFaceSet::check):
+        (WebCore::FontFaceSet::createIterator):
+        (WebCore::FontFaceSet::PendingPromise::create):
+        * css/FontFaceSet.idl: Added.
+        * dom/EventNames.h:
+        * dom/EventTargetFactory.in:
+
 2016-02-17  Mark Lam  <mark.lam@apple.com>
 
         Callers of JSString::value() should check for exceptions thereafter.
diff --git a/Source/WebCore/DerivedSources.make b/Source/WebCore/DerivedSources.make
index 396c381..a390b47 100644
--- a/Source/WebCore/DerivedSources.make
+++ b/Source/WebCore/DerivedSources.make
@@ -242,6 +242,7 @@
     $(WebCore)/css/Counter.idl \
     $(WebCore)/css/DOMWindowCSS.idl \
     $(WebCore)/css/FontFace.idl \
+    $(WebCore)/css/FontFaceSet.idl \
     $(WebCore)/css/FontLoader.idl \
     $(WebCore)/css/MediaList.idl \
     $(WebCore)/css/MediaQueryList.idl \
diff --git a/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj b/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
index 74e5056..25f3c54 100644
--- a/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
+++ b/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
@@ -1971,6 +1971,20 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFaceSet.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFace.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@@ -9611,6 +9625,20 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="..\css\FontFaceSet.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="..\css\FontFace.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@@ -9625,6 +9653,20 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="..\css\CSSFontFaceSet.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="..\css\CSSFontFace.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@@ -17740,6 +17782,20 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="..\bindings\js\JSFontFaceSetCustom.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="..\bindings\js\JSFontFaceCustom.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@@ -19987,6 +20043,7 @@
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFileReader.h" />
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFileReaderSync.h" />
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontLoader.h" />
+    <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFaceSet.h" />
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFace.h" />
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSGamepad.h" />
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSGamepadButton.h" />
@@ -21705,7 +21762,9 @@
     <ClInclude Include="..\css\CSSCursorImageValue.h" />
     <ClInclude Include="..\css\CSSFilterImageValue.h" />
     <ClInclude Include="..\css\CSSFontFace.h" />
+    <ClInclude Include="..\css\CSSFontFaceSet.h" />
     <ClInclude Include="..\css\FontFace.h" />
+    <ClInclude Include="..\css\FontFaceSet.h" />
     <ClInclude Include="..\css\CSSFontFaceLoadEvent.h" />
     <ClInclude Include="..\css\CSSFontFaceRule.h" />
     <ClInclude Include="..\css\CSSFontFaceSource.h" />
diff --git a/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters b/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
index d84d721..23eb7c2 100644
--- a/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
+++ b/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
@@ -2084,9 +2084,15 @@
     <ClCompile Include="..\css\CSSCursorImageValue.cpp">
       <Filter>css</Filter>
     </ClCompile>
+    <ClCompile Include="..\css\FontFaceSet.cpp">
+      <Filter>css</Filter>
+    </ClCompile>
     <ClCompile Include="..\css\FontFace.cpp">
       <Filter>css</Filter>
     </ClCompile>
+    <ClCompile Include="..\css\CSSFontFaceSet.cpp">
+      <Filter>css</Filter>
+    </ClCompile>
     <ClCompile Include="..\css\CSSFontFace.cpp">
       <Filter>css</Filter>
     </ClCompile>
@@ -4262,6 +4268,9 @@
     <ClCompile Include="..\bindings\js\JSCSSRuleListCustom.cpp">
       <Filter>bindings\js</Filter>
     </ClCompile>
+    <ClCompile Include="..\bindings\js\JSFontFaceSetCustom.cpp">
+      <Filter>bindings\js</Filter>
+    </ClCompile>
     <ClCompile Include="..\bindings\js\JSFontFaceCustom.cpp">
       <Filter>bindings\js</Filter>
     </ClCompile>
@@ -5274,6 +5283,9 @@
     <ClCompile Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFileReaderSync.cpp">
       <Filter>DerivedSources</Filter>
     </ClCompile>
+    <ClCompile Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFaceSet.cpp">
+      <Filter>DerivedSources</Filter>
+    </ClCompile>
     <ClCompile Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFace.cpp">
       <Filter>DerivedSources</Filter>
     </ClCompile>
@@ -9067,9 +9079,15 @@
     <ClInclude Include="..\css\CSSCursorImageValue.h">
       <Filter>css</Filter>
     </ClInclude>
+    <ClInclude Include="..\css\FontFaceSet.h">
+      <Filter>css</Filter>
+    </ClInclude>
     <ClInclude Include="..\css\FontFace.h">
       <Filter>css</Filter>
     </ClInclude>
+    <ClInclude Include="..\css\CSSFontFaceSet.h">
+      <Filter>css</Filter>
+    </ClInclude>
     <ClInclude Include="..\css\CSSFontFace.h">
       <Filter>css</Filter>
     </ClInclude>
@@ -12702,6 +12720,9 @@
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFileReaderSync.h">
       <Filter>DerivedSources</Filter>
     </ClInclude>
+    <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFaceSet.h">
+      <Filter>DerivedSources</Filter>
+    </ClInclude>
     <ClInclude Include="$(ConfigurationBuildDir)\obj$(PlatformArchitecture)\$(ProjectName)\DerivedSources\JSFontFace.h">
       <Filter>DerivedSources</Filter>
     </ClInclude>
diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
index aa6770a..9dfd781 100644
--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj
+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
@@ -950,6 +950,11 @@
 		1C21E57C183ED1FF001C289D /* IOSurfacePool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C21E57A183ED1FF001C289D /* IOSurfacePool.cpp */; };
 		1C21E57D183ED1FF001C289D /* IOSurfacePool.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C21E57B183ED1FF001C289D /* IOSurfacePool.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		1C2417BA1992C04100EF9938 /* SpellingDot@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1C2417B91992C04100EF9938 /* SpellingDot@3x.png */; };
+		1C24EEA41C729CE40080F8FC /* FontFaceSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C24EEA21C729CE40080F8FC /* FontFaceSet.cpp */; };
+		1C24EEA51C729CE40080F8FC /* FontFaceSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C24EEA31C729CE40080F8FC /* FontFaceSet.h */; };
+		1C24EEA81C72A7B40080F8FC /* JSFontFaceSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C24EEA61C72A7B40080F8FC /* JSFontFaceSet.cpp */; };
+		1C24EEA91C72A7B40080F8FC /* JSFontFaceSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C24EEA71C72A7B40080F8FC /* JSFontFaceSet.h */; };
+		1C24EEAB1C72AA0A0080F8FC /* JSFontFaceSetCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C24EEAA1C72AA0A0080F8FC /* JSFontFaceSetCustom.cpp */; };
 		1C26497A0D7E248A00BD10F2 /* DocumentLoaderMac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C2649790D7E248A00BD10F2 /* DocumentLoaderMac.cpp */; };
 		1C3249111C6D6A3B007EDB32 /* FontVariantBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C3249101C6D6A3B007EDB32 /* FontVariantBuilder.cpp */; };
 		1C3969D01B74211E002BCFA7 /* FontCacheCoreText.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C3969CF1B74211E002BCFA7 /* FontCacheCoreText.cpp */; };
@@ -5915,6 +5920,8 @@
 		C105DA620F3AA68F001DD44F /* TextEncodingDetectorICU.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C105DA610F3AA68F001DD44F /* TextEncodingDetectorICU.cpp */; };
 		C105DA640F3AA6B8001DD44F /* TextEncodingDetector.h in Headers */ = {isa = PBXBuildFile; fileRef = C105DA630F3AA6B8001DD44F /* TextEncodingDetector.h */; };
 		C2015C0A1BE6FEB200822389 /* FontVariantBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C2015C091BE6FE2C00822389 /* FontVariantBuilder.h */; };
+		C26017A31C72DC9900F74A16 /* CSSFontFaceSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C26017A11C72DC9900F74A16 /* CSSFontFaceSet.cpp */; };
+		C26017A41C72DC9900F74A16 /* CSSFontFaceSet.h in Headers */ = {isa = PBXBuildFile; fileRef = C26017A21C72DC9900F74A16 /* CSSFontFaceSet.h */; };
 		C280833F1C6DC26F001451B6 /* JSFontFace.h in Headers */ = {isa = PBXBuildFile; fileRef = C280833E1C6DC22C001451B6 /* JSFontFace.h */; };
 		C28083401C6DC275001451B6 /* JSFontFace.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C280833D1C6DC22C001451B6 /* JSFontFace.cpp */; };
 		C28083421C6DC96A001451B6 /* JSFontFaceCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C28083411C6DC96A001451B6 /* JSFontFaceCustom.cpp */; };
@@ -8376,6 +8383,12 @@
 		1C21E57A183ED1FF001C289D /* IOSurfacePool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IOSurfacePool.cpp; sourceTree = "<group>"; };
 		1C21E57B183ED1FF001C289D /* IOSurfacePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IOSurfacePool.h; sourceTree = "<group>"; };
 		1C2417B91992C04100EF9938 /* SpellingDot@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "SpellingDot@3x.png"; sourceTree = "<group>"; };
+		1C24EEA11C729B320080F8FC /* FontFaceSet.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = FontFaceSet.idl; sourceTree = "<group>"; };
+		1C24EEA21C729CE40080F8FC /* FontFaceSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FontFaceSet.cpp; sourceTree = "<group>"; };
+		1C24EEA31C729CE40080F8FC /* FontFaceSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FontFaceSet.h; sourceTree = "<group>"; };
+		1C24EEA61C72A7B40080F8FC /* JSFontFaceSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSFontFaceSet.cpp; sourceTree = "<group>"; };
+		1C24EEA71C72A7B40080F8FC /* JSFontFaceSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSFontFaceSet.h; sourceTree = "<group>"; };
+		1C24EEAA1C72AA0A0080F8FC /* JSFontFaceSetCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSFontFaceSetCustom.cpp; sourceTree = "<group>"; };
 		1C2649790D7E248A00BD10F2 /* DocumentLoaderMac.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DocumentLoaderMac.cpp; sourceTree = "<group>"; };
 		1C3249101C6D6A3B007EDB32 /* FontVariantBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FontVariantBuilder.cpp; sourceTree = "<group>"; };
 		1C3969CF1B74211E002BCFA7 /* FontCacheCoreText.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FontCacheCoreText.cpp; sourceTree = "<group>"; };
@@ -13837,6 +13850,8 @@
 		C105DA630F3AA6B8001DD44F /* TextEncodingDetector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextEncodingDetector.h; sourceTree = "<group>"; };
 		C2015C091BE6FE2C00822389 /* FontVariantBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FontVariantBuilder.h; sourceTree = "<group>"; };
 		C24685131A148E1800811792 /* CoreGraphicsSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreGraphicsSPI.h; sourceTree = "<group>"; };
+		C26017A11C72DC9900F74A16 /* CSSFontFaceSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSFontFaceSet.cpp; sourceTree = "<group>"; };
+		C26017A21C72DC9900F74A16 /* CSSFontFaceSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSFontFaceSet.h; sourceTree = "<group>"; };
 		C280833C1C6DB194001451B6 /* FontFace.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = FontFace.idl; sourceTree = "<group>"; };
 		C280833D1C6DC22C001451B6 /* JSFontFace.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSFontFace.cpp; sourceTree = "<group>"; };
 		C280833E1C6DC22C001451B6 /* JSFontFace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSFontFace.h; sourceTree = "<group>"; };
@@ -17561,6 +17576,8 @@
 		656580EC09D12B20000E61D7 /* Derived Sources */ = {
 			isa = PBXGroup;
 			children = (
+				1C24EEA61C72A7B40080F8FC /* JSFontFaceSet.cpp */,
+				1C24EEA71C72A7B40080F8FC /* JSFontFaceSet.h */,
 				C280833D1C6DC22C001451B6 /* JSFontFace.cpp */,
 				C280833E1C6DC22C001451B6 /* JSFontFace.h */,
 				9908B0F31BCACFFE00ED0F65 /* ByteLengthQueuingStrategyBuiltins.cpp */,
@@ -22471,6 +22488,7 @@
 				A1C7FAA1133A5D3500D6732D /* JSXPathResultCustom.cpp */,
 				BCEFE1E40DCA5F3300739219 /* JSXSLTProcessorCustom.cpp */,
 				C28083411C6DC96A001451B6 /* JSFontFaceCustom.cpp */,
+				1C24EEAA1C72AA0A0080F8FC /* JSFontFaceSetCustom.cpp */,
 			);
 			name = Custom;
 			sourceTree = "<group>";
@@ -23780,6 +23798,11 @@
 				1C66260E1C6E7CA600AB527C /* FontFace.cpp */,
 				1C66260F1C6E7CA600AB527C /* FontFace.h */,
 				C280833C1C6DB194001451B6 /* FontFace.idl */,
+				1C24EEA11C729B320080F8FC /* FontFaceSet.idl */,
+				1C24EEA21C729CE40080F8FC /* FontFaceSet.cpp */,
+				1C24EEA31C729CE40080F8FC /* FontFaceSet.h */,
+				C26017A11C72DC9900F74A16 /* CSSFontFaceSet.cpp */,
+				C26017A21C72DC9900F74A16 /* CSSFontFaceSet.h */,
 			);
 			path = css;
 			sourceTree = "<group>";
@@ -26502,6 +26525,7 @@
 				0FDA7C1F188322FC00C954B5 /* JSGestureEvent.h in Headers */,
 				8482B7521198CB6B00BFB005 /* JSHashChangeEvent.h in Headers */,
 				BC94D14F0C275C68006BC617 /* JSHistory.h in Headers */,
+				1C24EEA51C729CE40080F8FC /* FontFaceSet.h in Headers */,
 				BC97E413109154FA0010D361 /* JSHTMLAllCollection.h in Headers */,
 				1A4A2DF00A1B852A00C807F8 /* JSHTMLAnchorElement.h in Headers */,
 				1A4A2DF20A1B852A00C807F8 /* JSHTMLAppletElement.h in Headers */,
@@ -27315,6 +27339,7 @@
 				BC76AC130DD7AD5C00415F34 /* ParserUtilities.h in Headers */,
 				536D5A23193E8E0C00CE4CAB /* ParsingUtilities.h in Headers */,
 				F55B3DCA1251F12D003EF269 /* PasswordInputType.h in Headers */,
+				1C24EEA91C72A7B40080F8FC /* JSFontFaceSet.h in Headers */,
 				4B2708C70AF19EE40065127F /* Pasteboard.h in Headers */,
 				C598905714E9C28000E8D18B /* PasteboardStrategy.h in Headers */,
 				B27535800B053814002CE64F /* Path.h in Headers */,
@@ -28050,6 +28075,7 @@
 				0810764412828556007C63BA /* SVGListProperty.h in Headers */,
 				088A0E09126EF1DB00978F7A /* SVGListPropertyTearOff.h in Headers */,
 				B2227A410D00BF220071B782 /* SVGLocatable.h in Headers */,
+				C26017A41C72DC9900F74A16 /* CSSFontFaceSet.h in Headers */,
 				436708EE12D9CA4B00044234 /* SVGMarkerData.h in Headers */,
 				B2227A440D00BF220071B782 /* SVGMarkerElement.h in Headers */,
 				B2227A470D00BF220071B782 /* SVGMaskElement.h in Headers */,
@@ -29870,6 +29896,7 @@
 				51E1ECC00C91C90400DC255B /* IconRecord.cpp in Sources */,
 				5185FC771BB4C4E80012898F /* IDBAny.cpp in Sources */,
 				5198F7BE1BC338AF00E2CC5F /* IDBAnyImpl.cpp in Sources */,
+				C26017A31C72DC9900F74A16 /* CSSFontFaceSet.cpp in Sources */,
 				C585A66211D4FAC5004C3E4B /* IDBBindingUtilities.cpp in Sources */,
 				516D7D711BB5F0BD00AF7C77 /* IDBConnectionToClient.cpp in Sources */,
 				5198F7C01BC4856700E2CC5F /* IDBConnectionToServer.cpp in Sources */,
@@ -30395,6 +30422,7 @@
 				1A0D57400A5C7867007EDD4C /* JSOverflowEvent.cpp in Sources */,
 				E1284BB210449FFA00EAEB52 /* JSPageTransitionEvent.cpp in Sources */,
 				FDA15EB112B03EE1003A583A /* JSPannerNode.cpp in Sources */,
+				1C24EEA41C729CE40080F8FC /* FontFaceSet.cpp in Sources */,
 				FD8AA63E169514A700D2EA68 /* JSPannerNodeCustom.cpp in Sources */,
 				E51A81DF17298D7700BFCA61 /* JSPerformance.cpp in Sources */,
 				8A9A587011E84C36008ACFD1 /* JSPerformanceNavigation.cpp in Sources */,
@@ -31165,6 +31193,7 @@
 				ABDDFE790A5C6E7000A3E11D /* RenderMenuList.cpp in Sources */,
 				A454424E119B3687009BE912 /* RenderMeter.cpp in Sources */,
 				1A3586DF15264C450022A659 /* RenderMultiColumnFlowThread.cpp in Sources */,
+				1C24EEA81C72A7B40080F8FC /* JSFontFaceSet.cpp in Sources */,
 				BCE32B9E1517C22700F542EC /* RenderMultiColumnSet.cpp in Sources */,
 				BC1A7D9718FCB5B000421879 /* RenderMultiColumnSpannerPlaceholder.cpp in Sources */,
 				8AC822FC180FC03300FB64D5 /* RenderNamedFlowFragment.cpp in Sources */,
@@ -31586,6 +31615,7 @@
 				511EC12B1C50ABBF0032F983 /* SQLiteIDBTransaction.cpp in Sources */,
 				845E72FB0FD2623900A87D79 /* SVGFilter.cpp in Sources */,
 				081EBF3A0FD34F4100DA7559 /* SVGFilterBuilder.cpp in Sources */,
+				1C24EEAB1C72AA0A0080F8FC /* JSFontFaceSetCustom.cpp in Sources */,
 				B2227A0B0D00BF220071B782 /* SVGFilterElement.cpp in Sources */,
 				B2227A0E0D00BF220071B782 /* SVGFilterPrimitiveStandardAttributes.cpp in Sources */,
 				B2227A110D00BF220071B782 /* SVGFitToViewBox.cpp in Sources */,
diff --git a/Source/WebCore/bindings/js/JSFontFaceSetCustom.cpp b/Source/WebCore/bindings/js/JSFontFaceSetCustom.cpp
new file mode 100644
index 0000000..cd84985
--- /dev/null
+++ b/Source/WebCore/bindings/js/JSFontFaceSetCustom.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "JSFontFaceSet.h"
+
+#include "FontFace.h"
+#include "JSFontFace.h"
+#include "JSKeyValueIterator.h"
+
+namespace WebCore {
+
+using FontFaceSetIterator = JSKeyValueIterator<JSFontFaceSet>;
+using FontFaceSetIteratorPrototype = JSKeyValueIteratorPrototype<JSFontFaceSet>;
+
+template<>
+const JSC::ClassInfo FontFaceSetIterator::s_info = { "Font Face Set Iterator", &Base::s_info, 0, CREATE_METHOD_TABLE(FontFaceSetIterator) };
+
+template<>
+const JSC::ClassInfo FontFaceSetIteratorPrototype::s_info = { "Font Face Set Iterator Prototype", &Base::s_info, 0, CREATE_METHOD_TABLE(FontFaceSetIteratorPrototype) };
+
+JSC::JSValue JSFontFaceSet::ready(JSC::ExecState& execState) const
+{
+    auto& promise = wrapped().promise(execState);
+    return promise.deferred().promise();
+}
+
+JSC::JSValue JSFontFaceSet::entries(JSC::ExecState&)
+{
+    return createIterator<JSFontFaceSet>(*globalObject(), *this, IterationKind::KeyValue);
+}
+
+JSC::JSValue JSFontFaceSet::keys(JSC::ExecState&)
+{
+    return createIterator<JSFontFaceSet>(*globalObject(), *this, IterationKind::Key);
+}
+
+JSC::JSValue JSFontFaceSet::values(JSC::ExecState&)
+{
+    return createIterator<JSFontFaceSet>(*globalObject(), *this, IterationKind::Value);
+}
+
+}
diff --git a/Source/WebCore/css/CSSAllInOne.cpp b/Source/WebCore/css/CSSAllInOne.cpp
index cdc67a7..488ba23 100644
--- a/Source/WebCore/css/CSSAllInOne.cpp
+++ b/Source/WebCore/css/CSSAllInOne.cpp
@@ -42,6 +42,7 @@
 #include "CSSFontFace.cpp"
 #include "CSSFontFaceLoadEvent.cpp"
 #include "CSSFontFaceRule.cpp"
+#include "CSSFontFaceSet.cpp"
 #include "CSSFontFaceSource.cpp"
 #include "CSSFontFaceSrcValue.cpp"
 #include "CSSFontFeatureValue.cpp"
diff --git a/Source/WebCore/css/CSSFontFace.cpp b/Source/WebCore/css/CSSFontFace.cpp
index 9554aed..552e99c 100644
--- a/Source/WebCore/css/CSSFontFace.cpp
+++ b/Source/WebCore/css/CSSFontFace.cpp
@@ -45,9 +45,9 @@
 
 namespace WebCore {
 
-CSSFontFace::CSSFontFace(CSSFontFaceClient& client, CSSFontSelector& fontSelector, bool isLocalFallback)
+CSSFontFace::CSSFontFace(CSSFontSelector& fontSelector, FontFace* wrapper, bool isLocalFallback)
     : m_fontSelector(fontSelector)
-    , m_client(client)
+    , m_wrapper(wrapper)
     , m_isLocalFallback(isLocalFallback)
 {
 }
@@ -230,14 +230,14 @@
     return true;
 }
 
-void CSSFontFace::addedToSegmentedFontFace(CSSSegmentedFontFace& segmentedFontFace)
+void CSSFontFace::addClient(Client& client)
 {
-    m_segmentedFontFaces.add(&segmentedFontFace);
+    m_clients.add(&client);
 }
 
-void CSSFontFace::removedFromSegmentedFontFace(CSSSegmentedFontFace& segmentedFontFace)
+void CSSFontFace::removeClient(Client& client)
 {
-    m_segmentedFontFaces.remove(&segmentedFontFace);
+    m_clients.remove(&client);
 }
 
 void CSSFontFace::adoptSource(std::unique_ptr<CSSFontFaceSource>&& source)
@@ -268,10 +268,10 @@
         break;
     }
 
-    m_status = newStatus;
+    for (auto& client : m_clients)
+        client->stateChanged(*this, m_status, newStatus);
 
-    if (m_status == Status::Success || m_status == Status::Failure)
-        m_client.kick(*this);
+    m_status = newStatus;
 }
 
 void CSSFontFace::fontLoaded(CSSFontFaceSource&)
@@ -282,13 +282,10 @@
     if (m_sourcesPopulated)
         pump();
 
-    if (m_segmentedFontFaces.isEmpty())
-        return;
-
     m_fontSelector->fontLoaded();
 
-    for (auto* face : m_segmentedFontFaces)
-        face->fontLoaded(*this);
+    for (auto& client : m_clients)
+        client->fontLoaded(*this);
 }
 
 size_t CSSFontFace::pump()
diff --git a/Source/WebCore/css/CSSFontFace.h b/Source/WebCore/css/CSSFontFace.h
index 96df452..107d9f2 100644
--- a/Source/WebCore/css/CSSFontFace.h
+++ b/Source/WebCore/css/CSSFontFace.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2007, 2008, 2016 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -44,16 +44,15 @@
 class CSSValueList;
 class FontDescription;
 class Font;
+class FontFace;
 
-class CSSFontFaceClient {
+// FIXME: This class does not need to be reference counted.
+class CSSFontFace final : public RefCounted<CSSFontFace> {
 public:
-    virtual ~CSSFontFaceClient() { }
-    virtual void kick(CSSFontFace&) = 0;
-};
-
-class CSSFontFace : public RefCounted<CSSFontFace> {
-public:
-    static Ref<CSSFontFace> create(CSSFontFaceClient& client, CSSFontSelector& fontSelector, bool isLocalFallback = false) { return adoptRef(*new CSSFontFace(client, fontSelector, isLocalFallback)); }
+    static Ref<CSSFontFace> create(CSSFontSelector& fontSelector, FontFace* wrapper = nullptr, bool isLocalFallback = false)
+    {
+        return adoptRef(*new CSSFontFace(fontSelector, wrapper, isLocalFallback));
+    }
     virtual ~CSSFontFace();
 
     bool setFamilies(CSSValue&);
@@ -80,8 +79,9 @@
     bool isLocalFallback() const { return m_isLocalFallback; }
     Status status() const { return m_status; }
 
-    void addedToSegmentedFontFace(CSSSegmentedFontFace&);
-    void removedFromSegmentedFontFace(CSSSegmentedFontFace&);
+    class Client;
+    void addClient(Client&);
+    void removeClient(Client&);
 
     bool allSourcesFailed() const;
 
@@ -93,6 +93,13 @@
     void load();
     RefPtr<Font> font(const FontDescription&, bool syntheticBold, bool syntheticItalic);
 
+    class Client {
+    public:
+        virtual ~Client() { }
+        virtual void fontLoaded(CSSFontFace&) { };
+        virtual void stateChanged(CSSFontFace&, Status oldState, Status newState) { UNUSED_PARAM(oldState); UNUSED_PARAM(newState); };
+    };
+
     // Pending => Loading  => TimedOut
     //              ||  \\    //  ||
     //              ||   \\  //   ||
@@ -125,12 +132,14 @@
         UChar32 m_to;
     };
 
+    FontFace* wrapper() const { return m_wrapper; }
+
 #if ENABLE(SVG_FONTS)
     bool hasSVGFontFaceSource() const;
 #endif
 
 private:
-    CSSFontFace(CSSFontFaceClient&, CSSFontSelector&, bool isLocalFallback);
+    CSSFontFace(CSSFontSelector&, FontFace*, bool isLocalFallback);
 
     size_t pump();
     void setStatus(Status);
@@ -138,9 +147,9 @@
     RefPtr<CSSValueList> m_families;
     FontTraitsMask m_traitsMask { static_cast<FontTraitsMask>(FontStyleNormalMask | FontWeight400Mask) };
     Vector<UnicodeRange> m_ranges;
-    HashSet<CSSSegmentedFontFace*> m_segmentedFontFaces; // FIXME: Refactor this (in favor of CSSFontFaceClient) when implementing FontFaceSet.
+    HashSet<Client*> m_clients;
     Ref<CSSFontSelector> m_fontSelector;
-    CSSFontFaceClient& m_client;
+    FontFace* m_wrapper;
     FontFeatureSettings m_featureSettings;
     FontVariantSettings m_variantSettings;
     Vector<std::unique_ptr<CSSFontFaceSource>> m_sources;
diff --git a/Source/WebCore/css/CSSFontFaceSet.cpp b/Source/WebCore/css/CSSFontFaceSet.cpp
new file mode 100644
index 0000000..a0f5fd6
--- /dev/null
+++ b/Source/WebCore/css/CSSFontFaceSet.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CSSFontFaceSet.h"
+
+#include "CSSFontFamily.h"
+#include "CSSFontSelector.h"
+#include "CSSParser.h"
+#include "CSSPrimitiveValue.h"
+#include "CSSValueList.h"
+#include "StyleProperties.h"
+
+namespace WebCore {
+
+CSSFontFaceSet::CSSFontFaceSet(CSSFontFaceSetClient& client)
+    : m_client(client)
+{
+}
+
+CSSFontFaceSet::~CSSFontFaceSet()
+{
+    for (auto& face : m_faces)
+        face->removeClient(*this);
+}
+
+void CSSFontFaceSet::incrementActiveCount()
+{
+    ++m_activeCount;
+    if (m_activeCount == 1) {
+        m_status = Status::Loading;
+        m_client.startedLoading();
+    }
+}
+
+void CSSFontFaceSet::decrementActiveCount()
+{
+    --m_activeCount;
+    if (!m_activeCount) {
+        m_status = Status::Loaded;
+        m_client.completedLoading();
+    }
+}
+
+bool CSSFontFaceSet::hasFace(const CSSFontFace& face) const
+{
+    for (auto& myFace : m_faces) {
+        if (myFace.ptr() == &face)
+            return true;
+    }
+    return false;
+}
+
+void CSSFontFaceSet::add(CSSFontFace& face)
+{
+    ASSERT(!hasFace(face));
+
+    m_faces.append(face);
+    face.addClient(*this);
+    if (face.status() == CSSFontFace::Status::Loading || face.status() == CSSFontFace::Status::TimedOut)
+        incrementActiveCount();
+}
+
+void CSSFontFaceSet::remove(const CSSFontFace& face)
+{
+    for (size_t i = 0; i < m_faces.size(); ++i) {
+        if (m_faces[i].ptr() == &face) {
+            m_faces[i]->removeClient(*this);
+            m_faces.remove(i);
+            if (face.status() == CSSFontFace::Status::Loading || face.status() == CSSFontFace::Status::TimedOut)
+                decrementActiveCount();
+            return;
+        }
+    }
+    ASSERT_NOT_REACHED();
+}
+
+static HashSet<String> extractFamilies(const CSSValueList& list)
+{
+    HashSet<String> result;
+    for (auto& family : list) {
+        const CSSPrimitiveValue& primitive = downcast<CSSPrimitiveValue>(family.get());
+        if (!primitive.isFontFamily())
+            continue;
+        result.add(primitive.fontFamily().familyName);
+    }
+    return result;
+}
+
+static bool familiesIntersect(const CSSFontFace& face, const CSSValueList& request)
+{
+    if (!face.families())
+        return false;
+
+    HashSet<String> faceFamilies = extractFamilies(*face.families());
+    HashSet<String> requestFamilies = extractFamilies(request);
+    for (auto& family1 : faceFamilies) {
+        if (requestFamilies.contains(family1))
+            return true;
+    }
+    return false;
+}
+
+Vector<std::reference_wrapper<CSSFontFace>> CSSFontFaceSet::matchingFaces(const String& font, const String&, ExceptionCode& ec)
+{
+    Vector<std::reference_wrapper<CSSFontFace>> result;
+    Ref<MutableStyleProperties> style = MutableStyleProperties::create();
+    auto parseResult = CSSParser::parseValue(style.ptr(), CSSPropertyFont, font, true, CSSStrictMode, nullptr);
+    if (parseResult == CSSParser::ParseResult::Error) {
+        ec = SYNTAX_ERR;
+        return result;
+    }
+    bool desiredStyleIsNormal = true;
+    if (RefPtr<CSSValue> desiredStyle = style->getPropertyCSSValue(CSSPropertyFontStyle)) {
+        if (!is<CSSPrimitiveValue>(*desiredStyle)) {
+            ec = SYNTAX_ERR;
+            return result;
+        }
+        desiredStyleIsNormal = downcast<CSSPrimitiveValue>(*desiredStyle).getValueID() == CSSValueNormal;
+    }
+    RefPtr<CSSValue> family = style->getPropertyCSSValue(CSSPropertyFontFamily);
+    if (!is<CSSValueList>(family.get())) {
+        ec = SYNTAX_ERR;
+        return result;
+    }
+    CSSValueList& familyList = downcast<CSSValueList>(*family);
+
+    // Match CSSFontSelector::getFontFace()
+    for (auto& face : m_faces) {
+        if (!familiesIntersect(face, familyList) || (desiredStyleIsNormal && !(face->traitsMask() & FontStyleNormalMask)))
+            continue;
+        result.append(face.get());
+    }
+    return result;
+}
+
+void CSSFontFaceSet::load(const String& font, const String& text, ExceptionCode& ec)
+{
+    auto matchingFaces = this->matchingFaces(font, text, ec);
+    if (ec)
+        return;
+
+    for (auto& face : matchingFaces)
+        face.get().load();
+}
+
+bool CSSFontFaceSet::check(const String& font, const String& text, ExceptionCode& ec)
+{
+    auto matchingFaces = this->matchingFaces(font, text, ec);
+    if (ec)
+        return false;
+
+    for (auto& face : matchingFaces) {
+        if (face.get().status() == CSSFontFace::Status::Pending)
+            return false;
+    }
+    return true;
+}
+
+void CSSFontFaceSet::stateChanged(CSSFontFace& face, CSSFontFace::Status oldState, CSSFontFace::Status newState)
+{
+    ASSERT(hasFace(face));
+    if (oldState == CSSFontFace::Status::Pending) {
+        ASSERT(newState == CSSFontFace::Status::Loading);
+        incrementActiveCount();
+    }
+    if (newState == CSSFontFace::Status::Success || newState == CSSFontFace::Status::Failure) {
+        ASSERT(oldState == CSSFontFace::Status::Loading || oldState == CSSFontFace::Status::TimedOut);
+        m_client.faceFinished(face, newState);
+        decrementActiveCount();
+    }
+}
+
+}
diff --git a/Source/WebCore/css/CSSFontFaceSet.h b/Source/WebCore/css/CSSFontFaceSet.h
new file mode 100644
index 0000000..c17f00b
--- /dev/null
+++ b/Source/WebCore/css/CSSFontFaceSet.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CSSFontFaceSet_h
+#define CSSFontFaceSet_h
+
+#include "CSSFontFace.h"
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class FontFaceSet;
+
+class CSSFontFaceSetClient {
+public:
+    virtual ~CSSFontFaceSetClient() { }
+    virtual void faceFinished(CSSFontFace&, CSSFontFace::Status) = 0;
+    virtual void startedLoading() = 0;
+    virtual void completedLoading() = 0;
+};
+
+class CSSFontFaceSet final : public CSSFontFace::Client {
+public:
+    CSSFontFaceSet(CSSFontFaceSetClient&);
+    ~CSSFontFaceSet();
+
+    bool hasFace(const CSSFontFace&) const;
+    size_t size() const { return m_faces.size(); }
+    void add(CSSFontFace&);
+    void remove(const CSSFontFace&);
+    const CSSFontFace& operator[](size_t i) const { return m_faces[i]; }
+
+    void load(const String& font, const String& text, ExceptionCode&);
+    bool check(const String& font, const String& text, ExceptionCode&);
+
+    enum class Status {
+        Loading,
+        Loaded
+    };
+    Status status() const { return m_status; }
+
+    Vector<std::reference_wrapper<CSSFontFace>> matchingFaces(const String& font, const String& text, ExceptionCode&);
+
+private:
+    void incrementActiveCount();
+    void decrementActiveCount();
+
+    virtual void stateChanged(CSSFontFace&, CSSFontFace::Status oldState, CSSFontFace::Status newState) override;
+
+    Vector<Ref<CSSFontFace>> m_faces;
+    Status m_status { Status::Loaded };
+    CSSFontFaceSetClient& m_client;
+    unsigned m_activeCount { 0 };
+};
+
+}
+
+#endif
diff --git a/Source/WebCore/css/CSSFontSelector.cpp b/Source/WebCore/css/CSSFontSelector.cpp
index 1c72d99..2c8c2c6 100644
--- a/Source/WebCore/css/CSSFontSelector.cpp
+++ b/Source/WebCore/css/CSSFontSelector.cpp
@@ -117,7 +117,7 @@
     fontFace.sourcesPopulated();
 }
 
-static String familyNameFromPrimitive(const CSSPrimitiveValue& value)
+String CSSFontSelector::familyNameFromPrimitive(const CSSPrimitiveValue& value)
 {
     if (value.isFontFamily())
         return value.fontFamily().familyName;
@@ -154,7 +154,7 @@
 
     Vector<Ref<CSSFontFace>> faces = { };
     for (auto mask : traitsMasks) {
-        Ref<CSSFontFace> face = CSSFontFace::create(*this, *this, true);
+        Ref<CSSFontFace> face = CSSFontFace::create(*this, nullptr, true);
         
         RefPtr<CSSValueList> familyList = CSSValueList::createCommaSeparated();
         familyList->append(CSSValuePool::singleton().createFontFamilyValue(familyName));
@@ -201,7 +201,7 @@
     if (!srcList.length())
         return;
 
-    Ref<CSSFontFace> fontFace = CSSFontFace::create(*this, *this);
+    Ref<CSSFontFace> fontFace = CSSFontFace::create(*this);
 
     if (!fontFace->setFamilies(*fontFamily))
         return;
@@ -496,10 +496,6 @@
 }
 
 
-void CSSFontSelector::kick(CSSFontFace&)
-{
-}
-
 size_t CSSFontSelector::fallbackFontCount()
 {
     if (!m_document)
diff --git a/Source/WebCore/css/CSSFontSelector.h b/Source/WebCore/css/CSSFontSelector.h
index a327baf..9a27fe2 100644
--- a/Source/WebCore/css/CSSFontSelector.h
+++ b/Source/WebCore/css/CSSFontSelector.h
@@ -41,13 +41,14 @@
 namespace WebCore {
 
 class CSSFontFaceRule;
+class CSSPrimitiveValue;
 class CSSSegmentedFontFace;
 class CSSValueList;
 class CachedFont;
 class Document;
 class StyleRuleFontFace;
 
-class CSSFontSelector final : public FontSelector, public CSSFontFaceClient {
+class CSSFontSelector final : public FontSelector {
 public:
     static Ref<CSSFontSelector> create(Document& document)
     {
@@ -80,6 +81,8 @@
 
     void beginLoadingFontSoon(CachedFont*);
 
+    static String familyNameFromPrimitive(const CSSPrimitiveValue&);
+
 private:
     explicit CSSFontSelector(Document&);
 
@@ -89,8 +92,6 @@
 
     void registerLocalFontFacesForFamily(const String&);
 
-    void kick(CSSFontFace&) override;
-
     Document* m_document;
     HashMap<String, Vector<Ref<CSSFontFace>>, ASCIICaseInsensitiveHash> m_fontFaces;
     HashMap<String, Vector<Ref<CSSFontFace>>, ASCIICaseInsensitiveHash> m_locallyInstalledFontFaces;
diff --git a/Source/WebCore/css/CSSSegmentedFontFace.cpp b/Source/WebCore/css/CSSSegmentedFontFace.cpp
index dcf8fe4..f5fcbae 100644
--- a/Source/WebCore/css/CSSSegmentedFontFace.cpp
+++ b/Source/WebCore/css/CSSSegmentedFontFace.cpp
@@ -45,21 +45,21 @@
 CSSSegmentedFontFace::~CSSSegmentedFontFace()
 {
     for (auto& face : m_fontFaces)
-        face->removedFromSegmentedFontFace(*this);
-}
-
-void CSSSegmentedFontFace::fontLoaded(CSSFontFace&)
-{
-    m_cache.clear();
+        face->removeClient(*this);
 }
 
 void CSSSegmentedFontFace::appendFontFace(Ref<CSSFontFace>&& fontFace)
 {
     m_cache.clear();
-    fontFace->addedToSegmentedFontFace(*this);
+    fontFace->addClient(*this);
     m_fontFaces.append(WTFMove(fontFace));
 }
 
+void CSSSegmentedFontFace::fontLoaded(CSSFontFace&)
+{
+    m_cache.clear();
+}
+
 static void appendFontWithInvalidUnicodeRangeIfLoading(FontRanges& ranges, Ref<Font>&& font, const Vector<CSSFontFace::UnicodeRange>& unicodeRanges)
 {
     if (font->isLoading()) {
diff --git a/Source/WebCore/css/CSSSegmentedFontFace.h b/Source/WebCore/css/CSSSegmentedFontFace.h
index cae64af..07b71f8 100644
--- a/Source/WebCore/css/CSSSegmentedFontFace.h
+++ b/Source/WebCore/css/CSSSegmentedFontFace.h
@@ -26,6 +26,7 @@
 #ifndef CSSSegmentedFontFace_h
 #define CSSSegmentedFontFace_h
 
+#include "CSSFontFace.h"
 #include "FontCache.h"
 #include "FontRanges.h"
 #include <wtf/HashMap.h>
@@ -35,11 +36,10 @@
 
 namespace WebCore {
 
-class CSSFontFace;
 class CSSFontSelector;
 class FontDescription;
 
-class CSSSegmentedFontFace final {
+class CSSSegmentedFontFace final : public CSSFontFace::Client {
     WTF_MAKE_FAST_ALLOCATED;
 public:
     CSSSegmentedFontFace(CSSFontSelector&);
@@ -47,13 +47,12 @@
 
     CSSFontSelector& fontSelector() const { return m_fontSelector; }
 
-    void fontLoaded(CSSFontFace&);
-
     void appendFontFace(Ref<CSSFontFace>&&);
 
     FontRanges fontRanges(const FontDescription&);
 
 private:
+    virtual void fontLoaded(CSSFontFace&) override;
 
     CSSFontSelector& m_fontSelector;
     HashMap<FontDescriptionKey, FontRanges, FontDescriptionKeyHash, WTF::SimpleClassHashTraits<FontDescriptionKey>> m_cache;
diff --git a/Source/WebCore/css/FontFace.cpp b/Source/WebCore/css/FontFace.cpp
index bcb2cd0..c45e8d2 100644
--- a/Source/WebCore/css/FontFace.cpp
+++ b/Source/WebCore/css/FontFace.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007, 2008, 2011, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -111,13 +111,15 @@
 }
 
 FontFace::FontFace(JSC::ExecState& execState, CSSFontSelector& fontSelector)
-    : m_backing(CSSFontFace::create(*this, fontSelector))
+    : m_backing(CSSFontFace::create(fontSelector, this))
     , m_promise(createPromise(execState))
 {
+    m_backing->addClient(*this);
 }
 
 FontFace::~FontFace()
 {
+    m_backing->removeClient(*this);
 }
 
 RefPtr<CSSValue> FontFace::parseString(const String& string, CSSPropertyID propertyID)
@@ -315,10 +317,10 @@
     return String("error", String::ConstructFromLiteral);
 }
 
-void FontFace::kick(CSSFontFace& face)
+void FontFace::stateChanged(CSSFontFace& face, CSSFontFace::Status, CSSFontFace::Status newState)
 {
     ASSERT_UNUSED(face, &face == m_backing.ptr());
-    switch (m_backing->status()) {
+    switch (newState) {
     case CSSFontFace::Status::TimedOut:
         rejectPromise(NETWORK_ERR);
         return;
diff --git a/Source/WebCore/css/FontFace.h b/Source/WebCore/css/FontFace.h
index 7c139f2..f66fb9a 100644
--- a/Source/WebCore/css/FontFace.h
+++ b/Source/WebCore/css/FontFace.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2007, 2008, 2016 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -45,7 +45,7 @@
 class CSSValue;
 class Dictionary;
 
-class FontFace : public RefCounted<FontFace>, public CSSFontFaceClient {
+class FontFace final : public RefCounted<FontFace>, public CSSFontFace::Client {
 public:
     static RefPtr<FontFace> create(JSC::ExecState&, ScriptExecutionContext&, const String& family, const Deprecated::ScriptValue& source, const Dictionary& descriptors, ExceptionCode&);
     virtual ~FontFace();
@@ -79,7 +79,7 @@
 private:
     FontFace(JSC::ExecState&, CSSFontSelector&);
 
-    void kick(CSSFontFace&) override;
+    virtual void stateChanged(CSSFontFace&, CSSFontFace::Status oldState, CSSFontFace::Status newState) override;
 
     void fulfillPromise();
     void rejectPromise(ExceptionCode);
diff --git a/Source/WebCore/css/FontFaceSet.cpp b/Source/WebCore/css/FontFaceSet.cpp
new file mode 100644
index 0000000..1b71441
--- /dev/null
+++ b/Source/WebCore/css/FontFaceSet.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "FontFaceSet.h"
+
+#include "Document.h"
+#include "ExceptionCodeDescription.h"
+#include "FontFace.h"
+#include "JSDOMBinding.h"
+#include "JSDOMCoreException.h"
+#include "JSFontFace.h"
+#include "JSFontFaceSet.h"
+
+namespace WebCore {
+
+static FontFaceSet::Promise createPromise(JSC::ExecState& exec)
+{
+    JSDOMGlobalObject& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(exec.lexicalGlobalObject());
+    return FontFaceSet::Promise(DeferredWrapper(&exec, &globalObject, JSC::JSPromiseDeferred::create(&exec, &globalObject)));
+}
+
+FontFaceSet::FontFaceSet(JSC::ExecState& execState, Document& document, const Vector<RefPtr<FontFace>>& initialFaces)
+    : ActiveDOMObject(&document)
+    , m_backing(*this)
+    , m_promise(createPromise(execState))
+{
+    for (auto& face : initialFaces)
+        add(face.get());
+}
+
+FontFaceSet::~FontFaceSet()
+{
+}
+
+FontFaceSet::Iterator::Iterator(FontFaceSet& set)
+    : m_target(set)
+{
+}
+
+bool FontFaceSet::Iterator::next(RefPtr<FontFace>& key, RefPtr<FontFace>& value)
+{
+    if (m_index == m_target->size())
+        return true;
+    key = m_target->m_backing[m_index++].wrapper();
+    value = key;
+    return false;
+}
+
+FontFaceSet::PendingPromise::PendingPromise(Promise&& promise)
+    : promise(WTFMove(promise))
+{
+}
+
+FontFaceSet::PendingPromise::~PendingPromise()
+{
+}
+
+bool FontFaceSet::has(FontFace* face) const
+{
+    if (!face)
+        return false;
+    return m_backing.hasFace(face->backing());
+}
+
+size_t FontFaceSet::size() const
+{
+    return m_backing.size();
+}
+
+FontFaceSet& FontFaceSet::add(FontFace* face)
+{
+    if (face && !m_backing.hasFace(face->backing()))
+        m_backing.add(face->backing());
+    return *this;
+}
+
+bool FontFaceSet::remove(FontFace* face)
+{
+    if (!face)
+        return false;
+
+    bool result = m_backing.hasFace(face->backing());
+    if (result)
+        m_backing.remove(face->backing());
+    return result;
+}
+
+void FontFaceSet::clear()
+{
+    while (m_backing.size())
+        m_backing.remove(m_backing[0]);
+}
+
+void FontFaceSet::load(const String& font, const String& text, DeferredWrapper&& promise, ExceptionCode& ec)
+{
+    auto matchingFaces = m_backing.matchingFaces(font, text, ec);
+    if (ec)
+        return;
+
+    if (matchingFaces.isEmpty()) {
+        promise.resolve(Vector<RefPtr<FontFace>>());
+        return;
+    }
+
+    for (auto& face : matchingFaces)
+        face.get().load();
+
+    auto pendingPromise = PendingPromise::create(WTFMove(promise));
+    bool waiting = false;
+
+    for (auto& face : matchingFaces) {
+        if (face.get().status() == CSSFontFace::Status::Failure) {
+            pendingPromise->promise.reject(DOMCoreException::create(ExceptionCodeDescription(NETWORK_ERR)));
+            return;
+        }
+    }
+
+    for (auto& face : matchingFaces) {
+        pendingPromise->faces.append(face.get().wrapper());
+        if (face.get().status() == CSSFontFace::Status::Success)
+            continue;
+        waiting = true;
+        auto& vector = m_pendingPromises.add(RefPtr<FontFace>(face.get().wrapper()), Vector<Ref<PendingPromise>>()).iterator->value;
+        vector.append(pendingPromise.copyRef());
+    }
+
+    if (!waiting)
+        pendingPromise->promise.resolve(pendingPromise->faces);
+}
+
+bool FontFaceSet::check(const String& family, const String& text, ExceptionCode& ec)
+{
+    return m_backing.check(family, text, ec);
+}
+
+auto FontFaceSet::promise(JSC::ExecState& execState) -> Promise&
+{
+    if (!m_promise) {
+        m_promise = createPromise(execState);
+        if (m_backing.status() == CSSFontFaceSet::Status::Loaded)
+            fulfillPromise();
+    }
+    return m_promise.value();
+}
+    
+String FontFaceSet::status() const
+{
+    switch (m_backing.status()) {
+    case CSSFontFaceSet::Status::Loading:
+        return String("loading", String::ConstructFromLiteral);
+    case CSSFontFaceSet::Status::Loaded:
+        return String("loaded", String::ConstructFromLiteral);
+    }
+    ASSERT_NOT_REACHED();
+    return String("loaded", String::ConstructFromLiteral);
+}
+
+bool FontFaceSet::canSuspendForDocumentSuspension() const
+{
+    return m_backing.status() == CSSFontFaceSet::Status::Loaded;
+}
+
+void FontFaceSet::startedLoading()
+{
+    // FIXME: Fire a "loading" event asynchronously.
+}
+
+void FontFaceSet::completedLoading()
+{
+    if (m_promise)
+        fulfillPromise();
+    m_promise = Nullopt;
+    // FIXME: Fire a "loadingdone" and possibly a "loadingerror" event asynchronously.
+}
+
+void FontFaceSet::fulfillPromise()
+{
+    // Normally, DeferredWrapper::callFunction resets the reference to the promise.
+    // However, API semantics require our promise to live for the entire lifetime of the FontFace.
+    // Let's make sure it stays alive.
+
+    Promise guard(m_promise.value());
+    m_promise.value().resolve(*this);
+    m_promise = guard;
+}
+
+void FontFaceSet::faceFinished(CSSFontFace& face, CSSFontFace::Status newStatus)
+{
+    auto iterator = m_pendingPromises.find(face.wrapper());
+    if (iterator == m_pendingPromises.end())
+        return;
+
+    for (auto& pendingPromise : iterator->value) {
+        if (newStatus == CSSFontFace::Status::Success) {
+            if (pendingPromise->hasOneRef())
+                pendingPromise->promise.resolve(pendingPromise->faces);
+        } else {
+            ASSERT(newStatus == CSSFontFace::Status::Failure);
+            // The first resolution wins, so we can just reject early now.
+            pendingPromise->promise.reject(DOMCoreException::create(ExceptionCodeDescription(NETWORK_ERR)));
+        }
+    }
+
+    m_pendingPromises.remove(iterator);
+}
+
+}
diff --git a/Source/WebCore/css/FontFaceSet.h b/Source/WebCore/css/FontFaceSet.h
new file mode 100644
index 0000000..002b308
--- /dev/null
+++ b/Source/WebCore/css/FontFaceSet.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FontFaceSet_h
+#define FontFaceSet_h
+
+#include "ActiveDOMObject.h"
+#include "CSSFontFaceSet.h"
+#include "DOMCoreException.h"
+#include "EventTarget.h"
+#include "JSDOMPromise.h"
+#include <wtf/Optional.h>
+#include <wtf/Ref.h>
+#include <wtf/RefCounted.h>
+#include <wtf/RefPtr.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class Document;
+class FontFace;
+
+class FontFaceSet final : public RefCounted<FontFaceSet>, public CSSFontFaceSetClient, public EventTargetWithInlineData, public ActiveDOMObject {
+public:
+    static Ref<FontFaceSet> create(JSC::ExecState& execState, Document& document, const Vector<RefPtr<FontFace>>& initialFaces)
+    {
+        Ref<FontFaceSet> result = adoptRef(*new FontFaceSet(execState, document, initialFaces));
+        result->suspendIfNeeded();
+        return result;
+    }
+    virtual ~FontFaceSet();
+
+    bool has(FontFace*) const;
+    size_t size() const;
+    FontFaceSet& add(FontFace*);
+    bool remove(FontFace*);
+    void clear();
+
+    void load(const String& font, DeferredWrapper&& promise, ExceptionCode& ec) { load(font, String(" ", String::ConstructFromLiteral), WTFMove(promise), ec); }
+    void load(const String& font, const String& text, DeferredWrapper&& promise, ExceptionCode&);
+    bool check(const String& font, ExceptionCode& ec) { return check(font, String(" ", String::ConstructFromLiteral), ec); }
+    bool check(const String& font, const String& text, ExceptionCode&);
+    
+    String status() const;
+
+    typedef DOMPromise<FontFaceSet&, DOMCoreException&> Promise;
+    Promise& promise(JSC::ExecState&);
+
+    class Iterator {
+    public:
+        explicit Iterator(FontFaceSet&);
+
+        using Key = RefPtr<FontFace>;
+        using Value = RefPtr<FontFace>;
+
+        bool next(Key& nextKey, Value& nextValue);
+
+    private:
+        Ref<FontFaceSet> m_target;
+        size_t m_index { 0 };
+    };
+    Iterator createIterator() { return Iterator(*this); }
+
+    using RefCounted<FontFaceSet>::ref;
+    using RefCounted<FontFaceSet>::deref;
+
+private:
+    struct PendingPromise : public RefCounted<PendingPromise> {
+        typedef DOMPromise<Vector<RefPtr<FontFace>>&, DOMCoreException&> Promise;
+        static Ref<PendingPromise> create(Promise&& promise)
+        {
+            return adoptRef(*new PendingPromise(WTFMove(promise)));
+        }
+        ~PendingPromise();
+
+    private:
+        PendingPromise(Promise&&);
+
+    public:
+        Vector<RefPtr<FontFace>> faces;
+        Promise promise;
+    };
+
+    FontFaceSet(JSC::ExecState&, Document&, const Vector<RefPtr<FontFace>>&);
+
+    void fulfillPromise();
+
+    // CSSFontFaceSetClient
+    virtual void startedLoading() override;
+    virtual void completedLoading() override;
+    virtual void faceFinished(CSSFontFace&, CSSFontFace::Status) override;
+
+    // ActiveDOMObject
+    virtual const char* activeDOMObjectName() const override { return "FontFaceSet"; }
+    virtual bool canSuspendForDocumentSuspension() const override;
+
+    // EventTarget
+    virtual EventTargetInterface eventTargetInterface() const override { return FontFaceSetEventTargetInterfaceType; }
+    virtual ScriptExecutionContext* scriptExecutionContext() const override { return ActiveDOMObject::scriptExecutionContext(); }
+    virtual void refEventTarget() override { ref(); }
+    virtual void derefEventTarget() override { deref(); }
+
+    CSSFontFaceSet m_backing;
+    HashMap<RefPtr<FontFace>, Vector<Ref<PendingPromise>>> m_pendingPromises;
+    Optional<Promise> m_promise;
+};
+
+}
+
+#endif
diff --git a/Source/WebCore/css/FontFaceSet.idl b/Source/WebCore/css/FontFaceSet.idl
new file mode 100644
index 0000000..4ff685d
--- /dev/null
+++ b/Source/WebCore/css/FontFaceSet.idl
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+enum FontFaceSetLoadStatus {
+    "loading",
+    "loaded"
+};
+
+[
+    ConstructorCallWith=ScriptState&Document,
+    Constructor(sequence<FontFace> initialFaces)
+] interface FontFaceSet : EventTarget {
+    [Custom] Object entries();
+    // forEach(): Maybe implement this in Javascript on top of keys().
+    boolean has(FontFace font);
+    [Custom] Object keys();
+    [Custom] Object values();
+    // @iterator
+    readonly attribute long size;
+
+    FontFaceSet add(FontFace font);
+    [ImplementedAs=remove] boolean delete(FontFace font);
+    void clear();
+
+    attribute EventHandler onloading;
+    attribute EventHandler onloadingdone;
+    attribute EventHandler onloadingerror;
+
+    [RaisesException] Promise load(DOMString font, optional DOMString text);
+    [RaisesException] boolean check(DOMString font, optional DOMString text);
+
+    [Custom] readonly attribute Promise ready;
+    readonly attribute FontFaceSetLoadStatus status;
+};
\ No newline at end of file
diff --git a/Source/WebCore/dom/EventNames.h b/Source/WebCore/dom/EventNames.h
index 46900ec..85b10fc 100644
--- a/Source/WebCore/dom/EventNames.h
+++ b/Source/WebCore/dom/EventNames.h
@@ -141,6 +141,7 @@
     macro(loadend) \
     macro(loading) \
     macro(loadingdone) \
+    macro(loadingerror) \
     macro(loadstart) \
     macro(mark) \
     macro(message) \
diff --git a/Source/WebCore/dom/EventTargetFactory.in b/Source/WebCore/dom/EventTargetFactory.in
index ab4fedb..430b8b7 100644
--- a/Source/WebCore/dom/EventTargetFactory.in
+++ b/Source/WebCore/dom/EventTargetFactory.in
@@ -9,7 +9,7 @@
 DOMWindow
 EventSource
 FileReader
-FontLoader conditional=FONT_LOAD_EVENTS
+FontFaceSet
 IDBDatabase conditional=INDEXED_DATABASE
 IDBOpenDBRequest conditional=INDEXED_DATABASE
 IDBRequest conditional=INDEXED_DATABASE