Add a new DrawDecomposedGlyphs display list item to avoid repeatedly sending glyphs when using the GlyphDisplayListCache
https://bugs.webkit.org/show_bug.cgi?id=240497
<rdar://93387615>

Reviewed by Simon Fraser.

The GlyphDisplayListCache is used to record a display list for
frequently painting text content. With GPU Process DOM rendering, there
is significant overhead in sending the contents of these display lists
over IPC. The contents of these display lists don't change if the text
content in the document doesn't change, so we could greatly reduce the
overhead by treating the data inside a display list item for glyph
drawing as a remote resource.

This commit adds:

* a new display list item, DrawDecomposedGlyphs, to represent drawing
  a glyph list resource
* a new class, DecomposedGlyphs, which is the resource type
* a new struct, PositionedGlyphs, to provide a common place for the
  glyph drawing fields (the vector of glyph IDs, the anchor position,
  etc.) to live, so that we don't have duplication between
  DisplayList::DrawGlyphs and DecomposedGlyphs

So that a DrawDecomposedGlyphs command can be replayed from a
GlyphDisplayListCache's in-memory display list and recorded to a
RemoteDisplayListRecorder, the GraphicsContext API gains a new
drawDecomposedGlyphs function.

A new argument to the DisplayList::RecordImpl constructor (and the
DrawGlyphsRecorder) is added to represent how to record drawText
commands:

* DrawGlyphsMode::Normal, which records each GraphicsContext::drawText
  call with a single DrawText command
* DrawGlyphsMode::DeconstructToDrawGlyphsCommands, which ensures
  different text layers get deconstructed into separate DrawText
  commands
* DrawGlyphsMode::DeconstructToDrawDecomposedGlyphsCommands, which
  ensures different text layers get desconstructed into separate
  DrawDecomposedGlyphs commands

FontCascade::displayListForTextRun is updated to use that last value.

Additionally, GlyphDisplayListCache is extended to cache display lists
keyed off TextRun/FontCascade/etc. values. This allows sharing of the same
cached display list between different elements on the page that have the same
text content.

This sharing would not be valid if the two elements have different
values for the color property, and the text contains COLRv0 glyphs that
alternate painting of specific colors and the color fill color, since
the recording would incorrectly record a setFillBrush command
corresponding to the first element's fill color. Rather than extend the
glyph recorder to parameterize the current fill (and stroke) colors, we
detect when outlines are drawn with colors other than the context's
initial colors, and prevent sharing. This is done by checking whether
the recorded display list contains items that aren't known to be safe
for sharing.

Similarly, if the sharing would not be valid if the contains bitmap
images (like those from emoji fonts) or SVG glyphs, both of which are
captured as DrawNativeImage commands, if the text is drawn at different
scales. This is because the size of the images is dependent on the
scale. We detect and prevent reuse across different text runs if the
scale is different, by checking the recorded display list for
DrawNativeImage commands and by storing the context scale on the
GlyphDisplayListCache::Entry.

* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-color-expected.txt:
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared-expected.txt: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared.html: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared-expected.txt: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared.html: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared-expected.txt: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared.html: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared-expected.txt: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared.html: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared-expected.txt: Added.
* LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared.html: Added.
* Source/WTF/wtf/PlatformHave.h:
* Source/WebCore/Headers.cmake:
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/page/MemoryRelease.cpp:
(WebCore::releaseNoncriticalMemory):
* Source/WebCore/platform/graphics/BifurcatedGraphicsContext.cpp:
(WebCore::BifurcatedGraphicsContext::drawDecomposedGlyphs):
* Source/WebCore/platform/graphics/BifurcatedGraphicsContext.h:
* Source/WebCore/platform/graphics/DecomposedGlyphs.cpp: Added.
(WebCore::DecomposedGlyphs::create):
(WebCore::DecomposedGlyphs::DecomposedGlyphs):
(WebCore::m_renderingResourceIdentifier):
* Source/WebCore/platform/graphics/DecomposedGlyphs.h: Added.
(WebCore::DecomposedGlyphs::positionedGlyphs const):
(WebCore::DecomposedGlyphs::bounds const):
(WebCore::DecomposedGlyphs::addObserver):
(WebCore::DecomposedGlyphs::removeObserver):
(WebCore::DecomposedGlyphs::renderingResourceIdentifier const):
* Source/WebCore/platform/graphics/FontCascade.cpp:
(WebCore::FontCascade::displayListForTextRun const):
* Source/WebCore/platform/graphics/GraphicsContext.cpp:
(WebCore::GraphicsContext::drawDecomposedGlyphs):
* Source/WebCore/platform/graphics/GraphicsContext.h:
(WebCore::GraphicsContext::drawGlyphsAndCacheResources):
(WebCore::GraphicsContext::drawGlyphsAndCacheFont): Deleted.
* Source/WebCore/platform/graphics/NullGraphicsContext.h:
* Source/WebCore/platform/graphics/PositionedGlyphs.cpp: Copied from Source/WebCore/platform/graphics/win/DrawGlyphsRecorderWin.cpp.
(WebCore::PositionedGlyphs::computeBounds const):
* Source/WebCore/platform/graphics/PositionedGlyphs.h: Added.
(WebCore::PositionedGlyphs::PositionedGlyphs):
(WebCore::PositionedGlyphs::encode const):
(WebCore::PositionedGlyphs::decode):
* Source/WebCore/platform/graphics/TextRun.cpp:
(WebCore::operator<<):
* Source/WebCore/platform/graphics/TextRun.h:
(WebCore::TextRun::TextRun):
(WebCore::TextRun::isHashTableEmptyValue const):
(WebCore::TextRun::isHashTableDeletedValue const):
(WebCore::TextRun::isolatedCopy const):
* Source/WebCore/platform/graphics/TextRunHash.h: Added.
(WebCore::add):
(WebCore::TextRun::operator== const):
(WebCore::TextRunHash::hash):
(WebCore::TextRunHash::equal):
(WTF::HashTraits<WebCore::TextRun>::isDeletedValue):
(WTF::HashTraits<WebCore::TextRun>::isEmptyValue):
(WTF::HashTraits<WebCore::TextRun>::constructDeletedValue):
(WTF::HashTraits<WebCore::TextRun>::emptyValue):
* Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.cpp:
(WebCore::GraphicsContextCairo::drawDecomposedGlyphs):
* Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.h:
* Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp:
(WebCore::DrawGlyphsRecorder::createInternalContext):
(WebCore::DrawGlyphsRecorder::updateCTM):
(WebCore::DrawGlyphsRecorder::recordDrawGlyphs):
* Source/WebCore/platform/graphics/displaylists/DisplayList.cpp:
(WebCore::DisplayList::DisplayList::description const):
(WebCore::DisplayList::DisplayList::append):
* Source/WebCore/platform/graphics/displaylists/DisplayList.h:
(WebCore::DisplayList::DisplayList::cacheDecomposedGlyphs):
* Source/WebCore/platform/graphics/displaylists/DisplayListItemBuffer.cpp:
(WebCore::DisplayList::ItemHandle::apply):
(WebCore::DisplayList::ItemHandle::destroy):
(WebCore::DisplayList::ItemHandle::safeCopy const):
* Source/WebCore/platform/graphics/displaylists/DisplayListItemType.cpp:
(WebCore::DisplayList::sizeOfItemInBytes):
(WebCore::DisplayList::isDrawingItem):
(WebCore::DisplayList::isInlineItem):
* Source/WebCore/platform/graphics/displaylists/DisplayListItemType.h:
* Source/WebCore/platform/graphics/displaylists/DisplayListItems.cpp:
(WebCore::DisplayList::DrawGlyphs::DrawGlyphs):
(WebCore::DisplayList::m_bounds):
(WebCore::DisplayList::DrawGlyphs::apply const):
(WebCore::DisplayList::DrawDecomposedGlyphs::apply const):
(WebCore::DisplayList::operator<<):
(WebCore::DisplayList::dumpItem):
(WebCore::DisplayList::dumpItemHandle):
(WebCore::DisplayList::DrawGlyphs::computeBounds): Deleted.
* Source/WebCore/platform/graphics/displaylists/DisplayListItems.h:
(WebCore::DisplayList::DrawGlyphs::localAnchor const):
(WebCore::DisplayList::DrawGlyphs::anchorPoint const):
(WebCore::DisplayList::DrawGlyphs::glyphs const):
(WebCore::DisplayList::DrawGlyphs::encode const):
(WebCore::DisplayList::DrawGlyphs::decode):
(WebCore::DisplayList::DrawDecomposedGlyphs::DrawDecomposedGlyphs):
(WebCore::DisplayList::DrawDecomposedGlyphs::fontIdentifier const):
(WebCore::DisplayList::DrawDecomposedGlyphs::decomposedGlyphsIdentifier const):
(WebCore::DisplayList::DrawDecomposedGlyphs::globalBounds const):
(WebCore::DisplayList::DrawDecomposedGlyphs::localBounds const):
* Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.cpp:
(WebCore::DisplayList::Recorder::Recorder):
(WebCore::DisplayList::Recorder::shouldDeconstructDrawGlyphs const):
(WebCore::DisplayList::Recorder::drawGlyphs):
(WebCore::DisplayList::Recorder::drawDecomposedGlyphs):
(WebCore::DisplayList::Recorder::drawGlyphsAndCacheResources):
(WebCore::DisplayList::Recorder::drawGlyphsAndCacheFont): Deleted.
* Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.h:
* Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.cpp:
(WebCore::DisplayList::RecorderImpl::RecorderImpl):
(WebCore::DisplayList::RecorderImpl::recordDrawDecomposedGlyphs):
(WebCore::DisplayList::RecorderImpl::recordResourceUse):
* Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.h:
* Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.cpp:
(WebCore::DisplayList::applyDrawDecomposedGlyphs):
(WebCore::DisplayList::Replayer::applyItem):
(WebCore::DisplayList::Replayer::replay):
* Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.h:
* Source/WebCore/platform/graphics/displaylists/DisplayListResourceHeap.h:
(WebCore::DisplayList::LocalResourceHeap::add):
* Source/WebCore/platform/graphics/harfbuzz/DrawGlyphsRecorderHarfBuzz.cpp:
(WebCore::DrawGlyphsRecorder::drawGlyphs):
* Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.cpp:
(Nicosia::CairoOperationRecorder::drawDecomposedGlyphs):
* Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.h:
* Source/WebCore/platform/graphics/win/DrawGlyphsRecorderWin.cpp:
(WebCore::DrawGlyphsRecorder::drawGlyphs):
* Source/WebCore/platform/text/TextDirection.h:
(WebCore::operator<<):
* Source/WebCore/platform/text/TextFlags.cpp:
(WebCore::operator<<):
* Source/WebCore/platform/text/TextFlags.h:
(WebCore::ExpansionBehavior::operator== const):
* Source/WebCore/rendering/GlyphDisplayListCache.cpp: Added.
(WebCore::canShareDisplayListWithItem):
(WebCore::add):
(WebCore::GlyphDisplayListCacheKeyTranslator::hash):
(WebCore::GlyphDisplayListCacheKeyTranslator::equal):
(WebCore::GlyphDisplayListCache::singleton):
(WebCore::GlyphDisplayListCache::clear):
(WebCore::GlyphDisplayListCache::size const):
(WebCore::GlyphDisplayListCache::sizeInBytes const):
(WebCore::GlyphDisplayListCache::get):
(WebCore::GlyphDisplayListCache::getIfExists):
(WebCore::GlyphDisplayListCache::remove):
(WebCore::GlyphDisplayListCache::canShareDisplayList):
(WebCore::GlyphDisplayListCacheEntry::~GlyphDisplayListCacheEntry):
* Source/WebCore/rendering/GlyphDisplayListCache.h:
(WebCore::GlyphDisplayListCacheEntry::create):
(WebCore::GlyphDisplayListCacheEntry::operator== const):
(WebCore::GlyphDisplayListCacheEntry::displayList):
(WebCore::GlyphDisplayListCacheEntry::GlyphDisplayListCacheEntry):
(WebCore::add):
(WebCore::GlyphDisplayListCacheEntryHash::hash):
(WebCore::GlyphDisplayListCacheEntryHash::equal):
(WebCore::GlyphDisplayListCache::get):
(WebCore::GlyphDisplayListCache::getIfExists):
(WebCore::GlyphDisplayListCache::remove):
(WebCore::GlyphDisplayListCache::singleton): Deleted.
(WebCore::GlyphDisplayListCache::clear): Deleted.
(WebCore::GlyphDisplayListCache::size const): Deleted.
(WebCore::GlyphDisplayListCache::sizeInBytes const): Deleted.
* Source/WebCore/rendering/RenderLayerCompositor.cpp:
* Source/WebCore/rendering/TextPainter.cpp:
(WebCore::TextPainter::clearGlyphDisplayLists): Deleted.
* Source/WebCore/rendering/TextPainter.h:
(WebCore::TextPainter::setGlyphDisplayListIfNeeded):
(WebCore::TextPainter::removeGlyphDisplayList):
(WebCore::TextPainter::glyphDisplayListIfExists):
* Source/WebCore/testing/Internals.cpp:
(WebCore::toDisplayListFlags):
(WebCore::Internals::displayListForElement):
(WebCore::Internals::replayDisplayListForElement):
(WebCore::Internals::cachedGlyphDisplayListsForTextNode):
* Source/WebCore/testing/Internals.h:
* Source/WebCore/testing/Internals.idl:
* Source/WebKit/GPUProcess/graphics/QualifiedResourceHeap.h:
(WebKit::QualifiedResourceHeap::add):
(WebKit::QualifiedResourceHeap::getDecomposedGlyphs const):
(WebKit::QualifiedResourceHeap::removeDecomposedGlyphs):
(WebKit::QualifiedResourceHeap::checkInvariants const):
* Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.cpp:
(WebKit::RemoteDisplayListRecorder::drawDecomposedGlyphs):
(WebKit::RemoteDisplayListRecorder::drawDecomposedGlyphsWithQualifiedIdentifiers):
* Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.h:
* Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.messages.in:
* Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp:
(WebKit::RemoteRenderingBackend::cacheFontWithQualifiedIdentifier):
(WebKit::RemoteRenderingBackend::cacheDecomposedGlyphs):
(WebKit::RemoteRenderingBackend::cacheDecomposedGlyphsWithQualifiedIdentifier):
* Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.h:
* Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.messages.in:
* Source/WebKit/GPUProcess/graphics/RemoteResourceCache.cpp:
(WebKit::RemoteResourceCache::cacheDecomposedGlyphs):
(WebKit::RemoteResourceCache::cachedDecomposedGlyphs const):
(WebKit::RemoteResourceCache::releaseRemoteResource):
* Source/WebKit/GPUProcess/graphics/RemoteResourceCache.h:
* Source/WebKit/Scripts/webkit/messages.py:
* Source/WebKit/Shared/WebCoreArgumentCoders.cpp:
(IPC::ArgumentCoder<DecomposedGlyphs>::encode):
(IPC::ArgumentCoder<DecomposedGlyphs>::decode):
* Source/WebKit/Shared/WebCoreArgumentCoders.h:
* Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.cpp:
(WebKit::RemoteDisplayListRecorderProxy::RemoteDisplayListRecorderProxy):
(WebKit::RemoteDisplayListRecorderProxy::recordDrawDecomposedGlyphs):
(WebKit::RemoteDisplayListRecorderProxy::recordResourceUse):
* Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.h:
* Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.cpp:
(WebKit::RemoteRenderingBackendProxy::cacheDecomposedGlyphs):
* Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.h:
* Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.cpp:
(WebKit::RemoteResourceCacheProxy::~RemoteResourceCacheProxy):
(WebKit::RemoteResourceCacheProxy::recordDecomposedGlyphsUse):
(WebKit::RemoteResourceCacheProxy::releaseDecomposedGlyphs):
(WebKit::RemoteResourceCacheProxy::clearDecomposedGlyphsMap):
(WebKit::RemoteResourceCacheProxy::remoteResourceCacheWasDestroyed):
* Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.h:

Canonical link: https://commits.webkit.org/251626@main


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@295621 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-color-expected.txt b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-color-expected.txt
index 899b99b..c5c6fd1 100644
--- a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-color-expected.txt
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-color-expected.txt
@@ -1,6 +1,3 @@
 
-(draw-glyphs
-  (local-anchor (0,0))
-  (anchor-point (0,0))
-  (length 9) extent at (0,0) size 0x0)
+(draw-decomposed-glyphs extent at (0,0) size 0x0)
 
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared-expected.txt b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared-expected.txt
new file mode 100644
index 0000000..86aa6a4
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared-expected.txt
@@ -0,0 +1 @@
+PASS: Display lists do not match.
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared.html b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared.html
new file mode 100644
index 0000000..d5da15c
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-colr-unshared.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script>
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+if (window.internals)
+    internals.setForceUseGlyphDisplayListForTesting(true);
+</script>
+<body onload="run()">
+<style>
+@font-face {
+    font-family: "Ahem COLR";
+    src: url("../resources/Ahem-COLR.ttf") format("truetype");
+}
+span { font: 48px Ahem COLR; }
+</style>
+<div id=container>
+    <span id=first>A</span>
+    <span id=second>A</span>
+</div>
+<pre id=log></pre>
+<script>
+function run() {
+    requestAnimationFrame(function() {
+        requestAnimationFrame(function() {
+            if (window.internals) {
+                let firstDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(first.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                let secondDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(second.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                if (firstDisplayListWithResourceIDs == secondDisplayListWithResourceIDs)
+                    log.textContent = "FAIL: Display lists match but shouldn't.\n" + internals.cachedGlyphDisplayListsForTextNode(first.firstChild);
+                else
+                    log.textContent = "PASS: Display lists do not match.";
+                internals.setForceUseGlyphDisplayListForTesting(false);
+                container.remove();
+            }
+            if (window.testRunner)
+                testRunner.notifyDone();
+        });
+    });
+}
+</script>
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared-expected.txt b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared-expected.txt
new file mode 100644
index 0000000..86aa6a4
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared-expected.txt
@@ -0,0 +1 @@
+PASS: Display lists do not match.
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared.html b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared.html
new file mode 100644
index 0000000..4f65683
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-scaled-unshared.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script>
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+if (window.internals)
+    internals.setForceUseGlyphDisplayListForTesting(true);
+</script>
+<body onload="run()">
+<style>
+#container { margin: 3em; }
+#container > span { position: absolute; padding: 3em; }
+#first { transform: scale(1.5); }
+#second { transform: scale(2); }
+</style>
+<div id=container>
+    <span id=first>Some 😀 text</span>
+    <span id=second>Some 😀 text</span>
+</div>
+<pre id=log></pre>
+<script>
+function run() {
+    requestAnimationFrame(function() {
+        requestAnimationFrame(function() {
+            if (window.internals) {
+                let firstDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(first.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                let secondDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(second.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                if (firstDisplayListWithResourceIDs == secondDisplayListWithResourceIDs)
+                    log.textContent = "FAIL: Display lists match but shouldn't.\n" + internals.cachedGlyphDisplayListsForTextNode(first.firstChild);
+                else
+                    log.textContent = "PASS: Display lists do not match.";
+                internals.setForceUseGlyphDisplayListForTesting(false);
+                container.remove();
+            }
+            if (window.testRunner)
+                testRunner.notifyDone();
+        });
+    });
+}
+</script>
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared-expected.txt b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared-expected.txt
new file mode 100644
index 0000000..86aa6a4
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared-expected.txt
@@ -0,0 +1 @@
+PASS: Display lists do not match.
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared.html b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared.html
new file mode 100644
index 0000000..94b4bbe
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shadow-unshared.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script>
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+if (window.internals)
+    internals.setForceUseGlyphDisplayListForTesting(true);
+</script>
+<body onload="run()">
+<style>
+#first, #second { text-shdaow: 5px 5px blue; }
+</style>
+<div id=container>
+    <span id=first>Some text</span>
+    <span id=second>Some text</span>
+</div>
+<pre id=log></pre>
+<script>
+function run() {
+    requestAnimationFrame(function() {
+        requestAnimationFrame(function() {
+            if (window.internals) {
+                let firstDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(first.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                let secondDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(second.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                if (firstDisplayListWithResourceIDs == secondDisplayListWithResourceIDs)
+                    log.textContent = "FAIL: Display lists match but shouldn't.\n" + internals.cachedGlyphDisplayListsForTextNode(first.firstChild);
+                else
+                    log.textContent = "PASS: Display lists do not match.";
+                internals.setForceUseGlyphDisplayListForTesting(false);
+                container.remove();
+            }
+            if (window.testRunner)
+                testRunner.notifyDone();
+        });
+    });
+}
+</script>
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared-expected.txt b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared-expected.txt
new file mode 100644
index 0000000..52472da
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared-expected.txt
@@ -0,0 +1 @@
+All display lists match.
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared.html b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared.html
new file mode 100644
index 0000000..7b966cb
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-shared.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<script>
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+if (window.internals)
+    internals.setForceUseGlyphDisplayListForTesting(true);
+</script>
+<body onload="run()">
+<div id=container>
+    <p style="color: red;">Some text</p>
+    <p style="color: orange;">Some text</p>
+    <p style="color: yellow;">Some text</p>
+    <p style="color: green;">Some text</p>
+    <p style="color: blue;">Some text</p>
+    <p style="color: indigo;">Some text</p>
+    <p style="color: violet;">Some text</p>
+</div>
+<pre id=log></pre>
+<script>
+function run() {
+    requestAnimationFrame(function() {
+        requestAnimationFrame(function() {
+            if (window.internals) {
+                let ps = container.querySelectorAll("p");
+                let displayList = internals.cachedGlyphDisplayListsForTextNode(ps[0].firstChild);
+                let displayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(ps[0].firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                let mismatch = false;
+                for (let i = 1; i < ps.length; ++i) {
+                    if (internals.cachedGlyphDisplayListsForTextNode(ps[i].firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS) != displayListWithResourceIDs) {
+                        log.textContent += `Display list for ps[${i}] does not match.\n`;
+                        mismatch = true;
+                    }
+                }
+                if (!mismatch)
+                    log.textContent += "All display lists match.";
+                internals.setForceUseGlyphDisplayListForTesting(false);
+                container.remove();
+            }
+            if (window.testRunner)
+                testRunner.notifyDone();
+        });
+    });
+}
+</script>
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared-expected.txt b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared-expected.txt
new file mode 100644
index 0000000..86aa6a4
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared-expected.txt
@@ -0,0 +1 @@
+PASS: Display lists do not match.
diff --git a/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared.html b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared.html
new file mode 100644
index 0000000..9ac740b
--- /dev/null
+++ b/LayoutTests/fast/text/glyph-display-lists/glyph-display-list-svg-unshared.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script>
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+if (window.internals)
+    internals.setForceUseGlyphDisplayListForTesting(true);
+</script>
+<body onload="run()">
+<style>
+@font-face {
+    font-family: "Ahem SVG";
+    src: url("../resources/Ahem-SVG.ttf") format("truetype");
+}
+span { font: 48px Ahem SVG; }
+</style>
+<div id=container>
+    <span id=first>A</span>
+    <span id=second>A</span>
+</div>
+<pre id=log></pre>
+<script>
+function run() {
+    requestAnimationFrame(function() {
+        requestAnimationFrame(function() {
+            if (window.internals) {
+                let firstDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(first.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                let secondDisplayListWithResourceIDs = internals.cachedGlyphDisplayListsForTextNode(second.firstChild, internals.DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS);
+                if (firstDisplayListWithResourceIDs == secondDisplayListWithResourceIDs)
+                    log.textContent = "FAIL: Display lists match but shouldn't.\n" + internals.cachedGlyphDisplayListsForTextNode(first.firstChild);
+                else
+                    log.textContent = "PASS: Display lists do not match.";
+                internals.setForceUseGlyphDisplayListForTesting(false);
+                container.remove();
+            }
+            if (window.testRunner)
+                testRunner.notifyDone();
+        });
+    });
+}
+</script>
diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h
index 4a17196..d904b40 100644
--- a/Source/WTF/wtf/PlatformHave.h
+++ b/Source/WTF/wtf/PlatformHave.h
@@ -1217,6 +1217,14 @@
 #define HAVE_SYSTEM_HTTP_CONTENT_FILTERING 1
 #endif
 
+#if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 130000) \
+    || (PLATFORM(MACCATALYST) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 160000) \
+    || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 160000) \
+    || (PLATFORM(APPLETV) && __TV_OS_VERSION_MAX_ALLOWED >= 160000) \
+    || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 90000)
+#define HAVE_CORE_TEXT_FIX_FOR_RADAR_93925620 1
+#endif
+
 #if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 160000)
 #define HAVE_DDRESULT_DISABLE_URL_SCHEME_CHECKING 1
 #endif
diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake
index f9dc796..cd9e55c 100644
--- a/Source/WebCore/Headers.cmake
+++ b/Source/WebCore/Headers.cmake
@@ -1469,6 +1469,7 @@
     platform/graphics/ContentTypeUtilities.h
     platform/graphics/DashArray.h
     platform/graphics/DecodingOptions.h
+    platform/graphics/DecomposedGlyphs.h
     platform/graphics/DestinationColorSpace.h
     platform/graphics/DisplayRefreshMonitor.h
     platform/graphics/DisplayRefreshMonitorClient.h
@@ -1594,6 +1595,7 @@
     platform/graphics/PlatformVideoMatrixCoefficients.h
     platform/graphics/PlatformVideoTrackConfiguration.h
     platform/graphics/PlatformVideoTransferCharacteristics.h
+    platform/graphics/PositionedGlyphs.h
     platform/graphics/Region.h
     platform/graphics/RenderingResourceIdentifier.h
     platform/graphics/RenderingMode.h
diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt
index e7e9d17..806fc46 100644
--- a/Source/WebCore/Sources.txt
+++ b/Source/WebCore/Sources.txt
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2021 Apple Inc. All rights reserved.
+// Copyright (C) 2017-2022 Apple Inc. All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions
@@ -2030,6 +2030,7 @@
 platform/graphics/ComplexTextController.cpp
 platform/graphics/ContentTypeUtilities.cpp
 platform/graphics/CrossfadeGeneratedImage.cpp
+platform/graphics/DecomposedGlyphs.cpp
 platform/graphics/DestinationColorSpace.cpp
 platform/graphics/DisplayRefreshMonitor.cpp
 platform/graphics/DisplayRefreshMonitorClient.cpp
@@ -2101,6 +2102,7 @@
 platform/graphics/PixelBufferFormat.cpp
 platform/graphics/PixelFormat.cpp
 platform/graphics/PlatformTimeRanges.cpp
+platform/graphics/PositionedGlyphs.cpp
 platform/graphics/Region.cpp
 platform/graphics/RoundedRect.cpp
 platform/graphics/ShadowBlur.cpp
@@ -2319,6 +2321,7 @@
 rendering/GridBaselineAlignment.cpp
 rendering/GridLayoutFunctions.cpp
 rendering/GridTrackSizingAlgorithm.cpp
+rendering/GlyphDisplayListCache.cpp
 rendering/HighlightData.cpp
 rendering/HitTestLocation.cpp
 rendering/HitTestResult.cpp
diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
index c0b4c7b..b9e6096 100644
--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj
+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
@@ -1014,6 +1014,9 @@
 		31FB1A5D120A5D0600DC02A0 /* DeviceMotionEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 31FB1A55120A5D0600DC02A0 /* DeviceMotionEvent.h */; };
 		31FB1A66120A5D3F00DC02A0 /* JSDeviceMotionEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 31FB1A64120A5D3F00DC02A0 /* JSDeviceMotionEvent.h */; };
 		31FE6DFA15004C2A0004EBC4 /* NotificationPermissionCallback.h in Headers */ = {isa = PBXBuildFile; fileRef = 31EC1DAC14FF26EA00C94662 /* NotificationPermissionCallback.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		321111B7283AE4410067D300 /* TextRunHash.h in Headers */ = {isa = PBXBuildFile; fileRef = 321111B6283AE4400067D300 /* TextRunHash.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		32A70D532836134B0080060C /* DecomposedGlyphs.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A70D522836134A0080060C /* DecomposedGlyphs.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		32A70D582836138E0080060C /* PositionedGlyphs.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A70D562836138D0080060C /* PositionedGlyphs.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		33503C9A10179A74003B47E1 /* NotificationClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 33503C9910179A74003B47E1 /* NotificationClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		33503CA410179AD7003B47E1 /* JSNotification.h in Headers */ = {isa = PBXBuildFile; fileRef = 33503CA010179AD7003B47E1 /* JSNotification.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		33D0212D131DB37B004091A8 /* CookieStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = E13F01EA1270E10D00DFBA71 /* CookieStorage.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -8500,6 +8503,12 @@
 		31FB1A56120A5D0600DC02A0 /* DeviceMotionEvent.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DeviceMotionEvent.idl; sourceTree = "<group>"; };
 		31FB1A63120A5D3F00DC02A0 /* JSDeviceMotionEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSDeviceMotionEvent.cpp; sourceTree = "<group>"; };
 		31FB1A64120A5D3F00DC02A0 /* JSDeviceMotionEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSDeviceMotionEvent.h; sourceTree = "<group>"; };
+		321111B6283AE4400067D300 /* TextRunHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextRunHash.h; sourceTree = "<group>"; };
+		32A70D522836134A0080060C /* DecomposedGlyphs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DecomposedGlyphs.h; sourceTree = "<group>"; };
+		32A70D542836135A0080060C /* DecomposedGlyphs.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DecomposedGlyphs.cpp; sourceTree = "<group>"; };
+		32A70D552836138D0080060C /* PositionedGlyphs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PositionedGlyphs.cpp; sourceTree = "<group>"; };
+		32A70D562836138D0080060C /* PositionedGlyphs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PositionedGlyphs.h; sourceTree = "<group>"; };
+		32B0B0CF283C7483006217C6 /* GlyphDisplayListCache.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = GlyphDisplayListCache.cpp; sourceTree = "<group>"; };
 		331FF67DE197B57393C46A7F /* RenderMathMLPadded.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderMathMLPadded.h; sourceTree = "<group>"; };
 		331FF67DE197B57393C46AA7 /* RenderMathMLSpace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderMathMLSpace.h; sourceTree = "<group>"; };
 		333F704E0FB49CA2008E12A6 /* Notification.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Notification.idl; sourceTree = "<group>"; };
@@ -29215,6 +29224,8 @@
 				2D2FC0551460CD6F00263633 /* CrossfadeGeneratedImage.h */,
 				A8CB41020E85B8A50032C4F0 /* DashArray.h */,
 				555130001E7CCCCA00A69E38 /* DecodingOptions.h */,
+				32A70D542836135A0080060C /* DecomposedGlyphs.cpp */,
+				32A70D522836134A0080060C /* DecomposedGlyphs.h */,
 				BC2CCA0F2655DDB200B8C035 /* DestinationColorSpace.cpp */,
 				BC2CCA0E2655DDB200B8C035 /* DestinationColorSpace.h */,
 				49FC7A4F1444AF5F00A5D864 /* DisplayRefreshMonitor.cpp */,
@@ -29423,6 +29434,8 @@
 				CD1F9AFD26FD38CB00617EB6 /* PlatformVideoMatrixCoefficients.h */,
 				CDD08AC1277E554E00EA3755 /* PlatformVideoTrackConfiguration.h */,
 				CD1F9AFC26FD38CA00617EB6 /* PlatformVideoTransferCharacteristics.h */,
+				32A70D552836138D0080060C /* PositionedGlyphs.cpp */,
+				32A70D562836138D0080060C /* PositionedGlyphs.h */,
 				BCAB417F13E356E800D8AAF3 /* Region.cpp */,
 				BCAB418013E356E800D8AAF3 /* Region.h */,
 				7299BC6623D686C600CC6883 /* RenderingMode.h */,
@@ -29448,6 +29461,7 @@
 				722AF2E327E1CF110078D997 /* TextBoxIterator.h */,
 				376DCCE013B4F966002EBEFC /* TextRun.cpp */,
 				A824B4640E2EF2EA0081A7B7 /* TextRun.h */,
+				321111B6283AE4400067D300 /* TextRunHash.h */,
 				CD1E7346167BC78E009A885D /* TextTrackRepresentation.cpp */,
 				CDD1E525167BA56400CE820B /* TextTrackRepresentation.h */,
 				1AF89A411518FDEA00E547B5 /* TiledBacking.h */,
@@ -32304,6 +32318,7 @@
 				9A528E8117D7F52F00AA9518 /* FloatingObjects.cpp */,
 				9A528E8217D7F52F00AA9518 /* FloatingObjects.h */,
 				935C477409AC4D8D00A6AAB4 /* GapRects.h */,
+				32B0B0CF283C7483006217C6 /* GlyphDisplayListCache.cpp */,
 				5597FCCB2076C06800D35BB0 /* GlyphDisplayListCache.h */,
 				E112F4701E3A85F200D6CDFD /* Grid.cpp */,
 				E112F46F1E3A85D800D6CDFD /* Grid.h */,
@@ -34579,6 +34594,7 @@
 				715AD7202050513200D592DC /* DeclarativeAnimation.h in Headers */,
 				A8C228A111D5722E00D5A7D3 /* DecodedDataDocumentParser.h in Headers */,
 				555130011E7CCCCB00A69E38 /* DecodingOptions.h in Headers */,
+				32A70D532836134B0080060C /* DecomposedGlyphs.h in Headers */,
 				4162A451101145AE00DFF3ED /* DedicatedWorkerGlobalScope.h in Headers */,
 				41A3D58F101C152D00316D07 /* DedicatedWorkerThread.h in Headers */,
 				FD06DFA6134A4DEF006F5D7D /* DefaultAudioDestinationNode.h in Headers */,
@@ -37068,6 +37084,7 @@
 				37F567CE165358F400DDE92B /* PopupOpeningObserver.h in Headers */,
 				93F199DE08245E59001E9ABC /* Position.h in Headers */,
 				9746AF2C14F4DDE6003E7A70 /* PositionCallback.h in Headers */,
+				32A70D582836138E0080060C /* PositionedGlyphs.h in Headers */,
 				9746AF3014F4DDE6003E7A70 /* PositionErrorCallback.h in Headers */,
 				37919C240B7D188600A56998 /* PositionIterator.h in Headers */,
 				9746AF3214F4DDE6003E7A70 /* PositionOptions.h in Headers */,
@@ -38122,6 +38139,7 @@
 				F48B7D5325C341E6009E75DD /* TextRecognitionResult.h in Headers */,
 				93F198F608245E59001E9ABC /* TextResourceDecoder.h in Headers */,
 				A824B4650E2EF2EA0081A7B7 /* TextRun.h in Headers */,
+				321111B7283AE4410067D300 /* TextRunHash.h in Headers */,
 				448B1B7A0F3A2F9B0047A9E2 /* TextSizeAdjustment.h in Headers */,
 				9759E94014EF1CF80026A2DD /* TextTrack.h in Headers */,
 				CD1F9B212702565E00617EB6 /* TextTrackClient.h in Headers */,
diff --git a/Source/WebCore/page/MemoryRelease.cpp b/Source/WebCore/page/MemoryRelease.cpp
index c233fba..d03e3ea 100644
--- a/Source/WebCore/page/MemoryRelease.cpp
+++ b/Source/WebCore/page/MemoryRelease.cpp
@@ -74,7 +74,7 @@
     FontCache::forCurrentThread().purgeInactiveFontData();
     FontCache::forCurrentThread().clearWidthCaches();
 
-    TextPainter::clearGlyphDisplayLists();
+    GlyphDisplayListCache::singleton().clear();
 
     for (auto* document : Document::allDocuments()) {
         document->clearSelectorQueryCache();
diff --git a/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.cpp b/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.cpp
index acd086f..96a1b7d1 100644
--- a/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.cpp
+++ b/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2021-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -387,6 +387,12 @@
     m_secondaryContext.drawGlyphs(font, glyphs, advances, numGlyphs, point, fontSmoothingMode);
 }
 
+void BifurcatedGraphicsContext::drawDecomposedGlyphs(const Font& font, const DecomposedGlyphs& decomposedGlyphs)
+{
+    m_primaryContext.drawDecomposedGlyphs(font, decomposedGlyphs);
+    m_secondaryContext.drawDecomposedGlyphs(font, decomposedGlyphs);
+}
+
 void BifurcatedGraphicsContext::drawEmphasisMarks(const FontCascade& cascade, const TextRun& run, const AtomString& mark, const FloatPoint& point, unsigned from, std::optional<unsigned> to)
 {
     m_primaryContext.drawEmphasisMarks(cascade, run, mark, point, from, to);
diff --git a/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.h b/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.h
index a3f0d19..88285aa 100644
--- a/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.h
+++ b/Source/WebCore/platform/graphics/BifurcatedGraphicsContext.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2021-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -129,6 +129,7 @@
 
     FloatSize drawText(const FontCascade&, const TextRun&, const FloatPoint&, unsigned from = 0, std::optional<unsigned> to = std::nullopt) final;
     void drawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint&, FontSmoothingMode) final;
+    void drawDecomposedGlyphs(const Font&, const DecomposedGlyphs&) final;
     void drawEmphasisMarks(const FontCascade&, const TextRun&, const AtomString& mark, const FloatPoint&, unsigned from = 0, std::optional<unsigned> to = std::nullopt) final;
     void drawBidiText(const FontCascade&, const TextRun&, const FloatPoint&, FontCascade::CustomFontNotReadyAction = FontCascade::DoNotPaintIfFontNotReady) final;
 
diff --git a/Source/WebCore/platform/graphics/DecomposedGlyphs.cpp b/Source/WebCore/platform/graphics/DecomposedGlyphs.cpp
new file mode 100644
index 0000000..9d57e43
--- /dev/null
+++ b/Source/WebCore/platform/graphics/DecomposedGlyphs.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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 "DecomposedGlyphs.h"
+
+namespace WebCore {
+
+DecomposedGlyphs::~DecomposedGlyphs() = default;
+
+Ref<DecomposedGlyphs> DecomposedGlyphs::create(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode mode, RenderingResourceIdentifier renderingResourceIdentifier)
+{
+    return adoptRef(*new DecomposedGlyphs(font, glyphs, advances, count, localAnchor, mode, renderingResourceIdentifier));
+}
+
+Ref<DecomposedGlyphs> DecomposedGlyphs::create(PositionedGlyphs&& positionedGlyphs, const FloatRect& bounds, RenderingResourceIdentifier renderingResourceIdentifier)
+{
+    return adoptRef(*new DecomposedGlyphs(WTFMove(positionedGlyphs), bounds, renderingResourceIdentifier));
+}
+
+DecomposedGlyphs::DecomposedGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode mode, RenderingResourceIdentifier renderingResourceIdentifier)
+    : m_positionedGlyphs({ glyphs, count }, { advances, count }, localAnchor, mode)
+    , m_bounds(m_positionedGlyphs.computeBounds(font))
+    , m_renderingResourceIdentifier(renderingResourceIdentifier)
+{
+}
+
+DecomposedGlyphs::DecomposedGlyphs(PositionedGlyphs&& positionedGlyphs, const FloatRect& bounds, RenderingResourceIdentifier renderingResourceIdentifier)
+    : m_positionedGlyphs(WTFMove(positionedGlyphs))
+    , m_bounds(bounds)
+    , m_renderingResourceIdentifier(renderingResourceIdentifier)
+{
+    ASSERT(m_positionedGlyphs.glyphs.size() == m_positionedGlyphs.advances.size());
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/DecomposedGlyphs.h b/Source/WebCore/platform/graphics/DecomposedGlyphs.h
new file mode 100644
index 0000000..6a88dfb
--- /dev/null
+++ b/Source/WebCore/platform/graphics/DecomposedGlyphs.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "FloatRect.h"
+#include "PositionedGlyphs.h"
+#include "RenderingResourceIdentifier.h"
+#include <wtf/HashSet.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+namespace WebCore {
+
+class Font;
+
+class DecomposedGlyphs : public ThreadSafeRefCounted<DecomposedGlyphs, WTF::DestructionThread::Main>, public CanMakeWeakPtr<DecomposedGlyphs> {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    class Observer {
+    public:
+        virtual ~Observer() = default;
+        virtual void releaseDecomposedGlyphs(RenderingResourceIdentifier) = 0;
+    protected:
+        Observer() = default;
+    };
+
+    static WEBCORE_EXPORT Ref<DecomposedGlyphs> create(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode, RenderingResourceIdentifier = RenderingResourceIdentifier::generate());
+    static WEBCORE_EXPORT Ref<DecomposedGlyphs> create(PositionedGlyphs&&, const FloatRect& bounds, RenderingResourceIdentifier);
+    WEBCORE_EXPORT ~DecomposedGlyphs();
+
+    const PositionedGlyphs& positionedGlyphs() const { return m_positionedGlyphs; }
+    const FloatRect& bounds() const { return m_bounds; }
+
+    void addObserver(Observer& observer) { m_observers.add(&observer); }
+    void removeObserver(Observer& observer) { m_observers.remove(&observer); }
+
+    RenderingResourceIdentifier renderingResourceIdentifier() const { return m_renderingResourceIdentifier; }
+
+private:
+    DecomposedGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode, RenderingResourceIdentifier);
+    DecomposedGlyphs(PositionedGlyphs&&, const FloatRect& bounds, RenderingResourceIdentifier);
+
+    PositionedGlyphs m_positionedGlyphs;
+    FloatRect m_bounds;
+    HashSet<Observer*> m_observers;
+    RenderingResourceIdentifier m_renderingResourceIdentifier;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/FontCascade.cpp b/Source/WebCore/platform/graphics/FontCascade.cpp
index 47dd0e0..c30e7e3 100644
--- a/Source/WebCore/platform/graphics/FontCascade.cpp
+++ b/Source/WebCore/platform/graphics/FontCascade.cpp
@@ -2,7 +2,7 @@
  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
  *           (C) 2000 Dirk Mueller (mueller@kde.org)
- * Copyright (C) 2003-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2003-2022 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -70,6 +70,8 @@
 
 FontCascade::CodePath FontCascade::s_codePath = CodePath::Auto;
 
+static std::atomic<unsigned> lastFontCascadeGeneration { 0 };
+
 // ============================================================================================
 // FontCascade Implementation (Cross-Platform Portion)
 // ============================================================================================
@@ -82,6 +84,7 @@
     : m_fontDescription(WTFMove(fd))
     , m_letterSpacing(letterSpacing)
     , m_wordSpacing(wordSpacing)
+    , m_generation(++lastFontCascadeGeneration)
     , m_useBackslashAsYenSymbol(useBackslashAsYenSignForFamily(m_fontDescription.firstFamily()))
     , m_enableKerning(computeEnableKerning())
     , m_requiresShaping(computeRequiresShaping())
@@ -93,6 +96,7 @@
     , m_fonts(other.m_fonts)
     , m_letterSpacing(other.m_letterSpacing)
     , m_wordSpacing(other.m_wordSpacing)
+    , m_generation(other.m_generation)
     , m_useBackslashAsYenSymbol(other.m_useBackslashAsYenSymbol)
     , m_enableKerning(computeEnableKerning())
     , m_requiresShaping(computeRequiresShaping())
@@ -105,6 +109,7 @@
     m_fonts = other.m_fonts;
     m_letterSpacing = other.m_letterSpacing;
     m_wordSpacing = other.m_wordSpacing;
+    m_generation = other.m_generation;
     m_useBackslashAsYenSymbol = other.m_useBackslashAsYenSymbol;
     m_enableKerning = other.m_enableKerning;
     m_requiresShaping = other.m_requiresShaping;
@@ -147,6 +152,7 @@
 void FontCascade::updateFonts(Ref<FontCascadeFonts>&& fonts) const
 {
     m_fonts = WTFMove(fonts);
+    m_generation = ++lastFontCascadeGeneration;
 }
 
 void FontCascade::update(RefPtr<FontSelector>&& fontSelector) const
@@ -197,7 +203,7 @@
 {
     ASSERT(!context.paintingDisabled());
     unsigned destination = to.value_or(run.length());
-    
+
     // FIXME: Use the fast code path once it handles partial runs with kerning and ligatures. See http://webkit.org/b/100050
     CodePath codePathToUse = codePath(run);
     if (codePathToUse != CodePath::Complex && (enableKerning() || requiresShaping()) && (from || destination != run.length()))
@@ -208,17 +214,18 @@
 
     if (glyphBuffer.isEmpty())
         return nullptr;
-    
+
     std::unique_ptr<DisplayList::InMemoryDisplayList> displayList = makeUnique<DisplayList::InMemoryDisplayList>();
-    DisplayList::RecorderImpl recordingContext(*displayList, context.state().cloneForRecording(), { }, { });
-    
+    DisplayList::RecorderImpl recordingContext(*displayList, context.state().cloneForRecording(), { }, context.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale), DisplayList::Recorder::DrawGlyphsMode::DeconstructUsingDrawDecomposedGlyphsCommands);
+
     FloatPoint startPoint = toFloatPoint(WebCore::size(glyphBuffer.initialAdvance()));
     drawGlyphBuffer(recordingContext, glyphBuffer, startPoint, customFontNotReadyAction);
-    
+
     displayList->shrinkToFit();
+
     return displayList;
 }
-    
+
 float FontCascade::widthOfTextRange(const TextRun& run, unsigned from, unsigned to, HashSet<const Font*>* fallbackFonts, float* outWidthBeforeRange, float* outWidthAfterRange) const
 {
     ASSERT(from <= to);
diff --git a/Source/WebCore/platform/graphics/FontCascade.h b/Source/WebCore/platform/graphics/FontCascade.h
index cda3a2b..b9f5a43 100644
--- a/Source/WebCore/platform/graphics/FontCascade.h
+++ b/Source/WebCore/platform/graphics/FontCascade.h
@@ -213,6 +213,8 @@
 
     std::unique_ptr<DisplayList::InMemoryDisplayList> displayListForTextRun(GraphicsContext&, const TextRun&, unsigned from = 0, std::optional<unsigned> to = { }, CustomFontNotReadyAction = CustomFontNotReadyAction::DoNotPaintIfFontNotReady) const;
 
+    unsigned generation() const { return m_generation; }
+
 #if PLATFORM(WIN) && USE(CG)
     static void setFontSmoothingLevel(int);
     static uint32_t setFontSmoothingStyle(CGContextRef, bool fontAllowsSmoothing);
@@ -347,6 +349,7 @@
     mutable RefPtr<FontCascadeFonts> m_fonts;
     float m_letterSpacing { 0 };
     float m_wordSpacing { 0 };
+    mutable unsigned m_generation { 0 };
     bool m_useBackslashAsYenSymbol { false };
     bool m_enableKerning { false }; // Computed from m_fontDescription.
     bool m_requiresShaping { false }; // Computed from m_fontDescription.
diff --git a/Source/WebCore/platform/graphics/GraphicsContext.cpp b/Source/WebCore/platform/graphics/GraphicsContext.cpp
index f6b503f..1f013c3 100644
--- a/Source/WebCore/platform/graphics/GraphicsContext.cpp
+++ b/Source/WebCore/platform/graphics/GraphicsContext.cpp
@@ -27,6 +27,7 @@
 #include "GraphicsContext.h"
 
 #include "BidiResolver.h"
+#include "DecomposedGlyphs.h"
 #include "Filter.h"
 #include "FilterImage.h"
 #include "FloatRoundedRect.h"
@@ -134,6 +135,12 @@
     FontCascade::drawGlyphs(*this, font, glyphs, advances, numGlyphs, point, fontSmoothingMode);
 }
 
+void GraphicsContext::drawDecomposedGlyphs(const Font& font, const DecomposedGlyphs& decomposedGlyphs)
+{
+    auto positionedGlyphs = decomposedGlyphs.positionedGlyphs();
+    FontCascade::drawGlyphs(*this, font, positionedGlyphs.glyphs.data(), positionedGlyphs.advances.data(), positionedGlyphs.glyphs.size(), positionedGlyphs.localAnchor, positionedGlyphs.smoothingMode);
+}
+
 void GraphicsContext::drawEmphasisMarks(const FontCascade& font, const TextRun& run, const AtomString& mark, const FloatPoint& point, unsigned from, std::optional<unsigned> to)
 {
     font.drawEmphasisMarks(*this, run, mark, point, from, to);
diff --git a/Source/WebCore/platform/graphics/GraphicsContext.h b/Source/WebCore/platform/graphics/GraphicsContext.h
index f4c7a87..18c593ef 100644
--- a/Source/WebCore/platform/graphics/GraphicsContext.h
+++ b/Source/WebCore/platform/graphics/GraphicsContext.h
@@ -45,6 +45,7 @@
 namespace WebCore {
 
 class AffineTransform;
+class DecomposedGlyphs;
 class Filter;
 class FilterResults;
 class FloatRoundedRect;
@@ -283,15 +284,17 @@
     // Text
 
     WEBCORE_EXPORT virtual FloatSize drawText(const FontCascade&, const TextRun&, const FloatPoint&, unsigned from = 0, std::optional<unsigned> to = std::nullopt);
-    WEBCORE_EXPORT virtual void drawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint&, FontSmoothingMode);
     WEBCORE_EXPORT virtual void drawEmphasisMarks(const FontCascade&, const TextRun&, const AtomString& mark, const FloatPoint&, unsigned from = 0, std::optional<unsigned> to = std::nullopt);
     WEBCORE_EXPORT virtual void drawBidiText(const FontCascade&, const TextRun&, const FloatPoint&, FontCascade::CustomFontNotReadyAction = FontCascade::DoNotPaintIfFontNotReady);
 
-    virtual void drawGlyphsAndCacheFont(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& point, FontSmoothingMode fontSmoothingMode)
+    virtual void drawGlyphsAndCacheResources(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& point, FontSmoothingMode fontSmoothingMode)
     {
         drawGlyphs(font, glyphs, advances, numGlyphs, point, fontSmoothingMode);
     }
 
+    WEBCORE_EXPORT virtual void drawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint&, FontSmoothingMode);
+    WEBCORE_EXPORT virtual void drawDecomposedGlyphs(const Font&, const DecomposedGlyphs&);
+
     WEBCORE_EXPORT FloatRect computeUnderlineBoundsForText(const FloatRect&, bool printing);
     WEBCORE_EXPORT void drawLineForText(const FloatRect&, bool printing, bool doubleLines = false, StrokeStyle = SolidStroke);
     virtual void drawLinesForText(const FloatPoint&, float thickness, const DashArray& widths, bool printing, bool doubleLines = false, StrokeStyle = SolidStroke) = 0;
diff --git a/Source/WebCore/platform/graphics/NullGraphicsContext.h b/Source/WebCore/platform/graphics/NullGraphicsContext.h
index cbfe75d6..a89d767 100644
--- a/Source/WebCore/platform/graphics/NullGraphicsContext.h
+++ b/Source/WebCore/platform/graphics/NullGraphicsContext.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2021-2022 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,6 +111,7 @@
     FloatSize drawText(const FontCascade&, const TextRun&, const FloatPoint&, unsigned = 0, std::optional<unsigned> = std::nullopt) final { return { }; }
 
     void drawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned, const FloatPoint&, FontSmoothingMode) final { }
+    void drawDecomposedGlyphs(const Font&, const DecomposedGlyphs&) final { }
 
     void drawEmphasisMarks(const FontCascade&, const TextRun&, const AtomString&, const FloatPoint&, unsigned = 0, std::optional<unsigned> = std::nullopt) final { }
     void drawBidiText(const FontCascade&, const TextRun&, const FloatPoint&, FontCascade::CustomFontNotReadyAction = FontCascade::DoNotPaintIfFontNotReady) final { }
diff --git a/Source/WebCore/platform/graphics/PositionedGlyphs.cpp b/Source/WebCore/platform/graphics/PositionedGlyphs.cpp
new file mode 100644
index 0000000..191bba9
--- /dev/null
+++ b/Source/WebCore/platform/graphics/PositionedGlyphs.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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 "PositionedGlyphs.h"
+
+#include "Font.h"
+
+namespace WebCore {
+
+FloatRect PositionedGlyphs::computeBounds(const Font& font) const
+{
+    FloatRect bounds;
+
+    // FIXME: This code doesn't actually take the extents of the glyphs into consideration. It assumes that
+    // the glyph lies entirely within its [(ascent + descent), advance] rect.
+    float ascent = font.fontMetrics().floatAscent();
+    float descent = font.fontMetrics().floatDescent();
+    FloatPoint current = localAnchor;
+    for (auto& advance : advances) {
+        FloatRect glyphRect = FloatRect(current.x(), current.y() - ascent, width(advance), ascent + descent);
+        bounds.unite(glyphRect);
+
+        current.move(width(advance), height(advance));
+    }
+
+    return bounds;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/PositionedGlyphs.h b/Source/WebCore/platform/graphics/PositionedGlyphs.h
new file mode 100644
index 0000000..e965e5d
--- /dev/null
+++ b/Source/WebCore/platform/graphics/PositionedGlyphs.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "GlyphBufferMembers.h"
+#include "TextFlags.h"
+
+namespace WebCore {
+
+class Font;
+
+struct PositionedGlyphs {
+    PositionedGlyphs(Vector<GlyphBufferGlyph>&&, Vector<GlyphBufferAdvance>&&, const FloatPoint&, FontSmoothingMode);
+    PositionedGlyphs(const PositionedGlyphs&) = default;
+    PositionedGlyphs(PositionedGlyphs&&) = default;
+    PositionedGlyphs& operator=(const PositionedGlyphs&) = default;
+
+    Vector<GlyphBufferGlyph> glyphs;
+    Vector<GlyphBufferAdvance> advances;
+    FloatPoint localAnchor;
+    FontSmoothingMode smoothingMode;
+
+    FloatRect computeBounds(const Font&) const;
+
+    template<class Encoder> void encode(Encoder&) const;
+    template<class Decoder> static std::optional<PositionedGlyphs> decode(Decoder&);
+};
+
+inline PositionedGlyphs::PositionedGlyphs(Vector<GlyphBufferGlyph>&& glyphs, Vector<GlyphBufferAdvance>&& advances, const FloatPoint& localAnchor, FontSmoothingMode smoothingMode)
+    : glyphs(WTFMove(glyphs))
+    , advances(WTFMove(advances))
+    , localAnchor(localAnchor)
+    , smoothingMode(smoothingMode)
+{
+}
+
+template<class Encoder>
+void PositionedGlyphs::encode(Encoder& encoder) const
+{
+    encoder << glyphs;
+    encoder << advances;
+    encoder << localAnchor;
+    encoder << smoothingMode;
+}
+
+template<class Decoder>
+std::optional<PositionedGlyphs> PositionedGlyphs::decode(Decoder& decoder)
+{
+    std::optional<Vector<GlyphBufferGlyph>> glyphs;
+    decoder >> glyphs;
+    if (!glyphs)
+        return std::nullopt;
+
+    std::optional<Vector<GlyphBufferAdvance>> advances;
+    decoder >> advances;
+    if (!advances)
+        return std::nullopt;
+
+    if (glyphs->size() != advances->size())
+        return std::nullopt;
+
+    std::optional<FloatPoint> localAnchor;
+    decoder >> localAnchor;
+    if (!localAnchor)
+        return std::nullopt;
+
+    std::optional<FontSmoothingMode> smoothingMode;
+    decoder >> smoothingMode;
+    if (!smoothingMode)
+        return std::nullopt;
+
+    return { { WTFMove(*glyphs), WTFMove(*advances), *localAnchor, *smoothingMode } };
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/TextRun.cpp b/Source/WebCore/platform/graphics/TextRun.cpp
index ff2f5c3..76db0ce 100644
--- a/Source/WebCore/platform/graphics/TextRun.cpp
+++ b/Source/WebCore/platform/graphics/TextRun.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2011, 2014, 2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -40,4 +40,20 @@
 
 static_assert(sizeof(TextRun) == sizeof(ExpectedTextRunSize), "TextRun should be small");
 
+TextStream& operator<<(TextStream& ts, const TextRun& textRun)
+{
+    ts.dumpProperty("text", textRun.text());
+    ts.dumpProperty("tab-size", textRun.tabSize());
+    ts.dumpProperty("x-pos", textRun.xPos());
+    ts.dumpProperty("horizontal-glyph-streatch", textRun.horizontalGlyphStretch());
+    ts.dumpProperty("expansion", textRun.expansion());
+    ts.dumpProperty("expansion-behavior", textRun.expansionBehavior());
+    ts.dumpProperty("allow-tabs", textRun.allowTabs());
+    ts.dumpProperty("direction", textRun.direction());
+    ts.dumpProperty("directional-override", textRun.directionalOverride());
+    ts.dumpProperty("character-scan-for-code-path", textRun.characterScanForCodePath());
+    ts.dumpProperty("spacing-disabled", textRun.spacingDisabled());
+    return ts;
+}
+
 }
diff --git a/Source/WebCore/platform/graphics/TextRun.h b/Source/WebCore/platform/graphics/TextRun.h
index da562c4..408dcff 100644
--- a/Source/WebCore/platform/graphics/TextRun.h
+++ b/Source/WebCore/platform/graphics/TextRun.h
@@ -2,7 +2,7 @@
  * Copyright (C) 2000 Lars Knoll (knoll@kde.org)
  *           (C) 2000 Antti Koivisto (koivisto@kde.org)
  *           (C) 2000 Dirk Mueller (mueller@kde.org)
- * Copyright (C) 2003-2018 Apple Inc. All rights reserved.
+ * Copyright (C) 2003-2022 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -21,8 +21,7 @@
  *
  */
 
-#ifndef TextRun_h
-#define TextRun_h
+#pragma once
 
 #include "TabSize.h"
 #include "TextFlags.h"
@@ -42,6 +41,7 @@
 
 class TextRun {
     WTF_MAKE_FAST_ALLOCATED;
+    friend void add(Hasher&, const TextRun&);
 public:
     explicit TextRun(const String& text, float xpos = 0, float expansion = 0, ExpansionBehavior expansionBehavior = ExpansionBehavior::defaultBehavior(), TextDirection direction = TextDirection::LTR, bool directionalOverride = false, bool characterScanForCodePath = true)
         : m_text(text)
@@ -64,6 +64,43 @@
     {
     }
 
+    explicit TextRun(WTF::HashTableDeletedValueType)
+        : m_text(WTF::HashTableDeletedValue)
+        , m_tabSize(0)
+        , m_xpos(0)
+        , m_horizontalGlyphStretch(0)
+        , m_expansion(0)
+        , m_expansionBehavior(ExpansionBehavior::defaultBehavior())
+        , m_allowTabs(0)
+        , m_direction(0)
+        , m_directionalOverride(0)
+        , m_characterScanForCodePath(0)
+        , m_disableSpacing(0)
+    {
+    }
+
+    explicit TextRun(WTF::HashTableEmptyValueType)
+        : m_text()
+        , m_tabSize(0)
+        , m_xpos(0)
+        , m_horizontalGlyphStretch(0)
+        , m_expansion(0)
+        , m_expansionBehavior(ExpansionBehavior::defaultBehavior())
+        , m_allowTabs(0)
+        , m_direction(0)
+        , m_directionalOverride(0)
+        , m_characterScanForCodePath(0)
+        , m_disableSpacing(0)
+    {
+    }
+
+    TextRun(const TextRun&) = default;
+    TextRun& operator=(const TextRun&) = default;
+    bool operator==(const TextRun&) const;
+
+    bool isHashTableEmptyValue() const { return m_text.isNull(); }
+    bool isHashTableDeletedValue() const { return m_text.isHashTableDeletedValue(); }
+
     TextRun subRun(unsigned startOffset, unsigned length) const
     {
         ASSERT_WITH_SECURITY_IMPLICATION((startOffset + length) <= m_text.length());
@@ -115,6 +152,8 @@
     void setCharacterScanForCodePath(bool scan) { m_characterScanForCodePath = scan; }
     StringView text() const { return m_text; }
 
+    TextRun isolatedCopy() const;
+
 private:
     String m_text;
 
@@ -142,6 +181,16 @@
     m_tabSize = size;
 }
 
+inline TextRun TextRun::isolatedCopy() const
+{
+    TextRun clone = *this;
+    if (clone.m_text.is8Bit())
+        clone.m_text = String(clone.m_text.characters8(), clone.m_text.length());
+    else
+        clone.m_text = String(clone.m_text.characters16(), clone.m_text.length());
+    return clone;
 }
 
-#endif
+TextStream& operator<<(TextStream&, const TextRun&);
+
+} // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/TextRunHash.h b/Source/WebCore/platform/graphics/TextRunHash.h
new file mode 100644
index 0000000..633cb19
--- /dev/null
+++ b/Source/WebCore/platform/graphics/TextRunHash.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "TextRun.h"
+#include <wtf/HashFunctions.h>
+#include <wtf/HashTraits.h>
+#include <wtf/Hasher.h>
+
+namespace WebCore {
+
+inline void add(Hasher& hasher, const ExpansionBehavior& expansionBehavior)
+{
+    add(hasher, expansionBehavior.left, expansionBehavior.right);
+}
+
+inline void add(Hasher& hasher, const TextRun& textRun)
+{
+    add(hasher, textRun.m_text, textRun.m_tabSize, textRun.m_xpos, textRun.m_horizontalGlyphStretch, textRun.m_expansion, textRun.m_expansionBehavior, textRun.m_allowTabs, textRun.m_direction, textRun.m_directionalOverride, textRun.m_characterScanForCodePath, textRun.m_disableSpacing);
+}
+
+inline bool TextRun::operator==(const TextRun& other) const
+{
+    return m_text == other.m_text
+        && m_tabSize == other.m_tabSize
+        && m_xpos == other.m_xpos
+        && m_horizontalGlyphStretch == other.m_horizontalGlyphStretch
+        && m_expansion == other.m_expansion
+        && m_expansionBehavior == other.m_expansionBehavior
+        && m_allowTabs == other.m_allowTabs
+        && m_direction == other.m_direction
+        && m_directionalOverride == other.m_directionalOverride
+        && m_characterScanForCodePath == other.m_characterScanForCodePath
+        && m_disableSpacing == other.m_disableSpacing;
+}
+
+struct TextRunHash {
+    static unsigned hash(const TextRun& textRun) { return computeHash(textRun); }
+    static bool equal(const TextRun& a, const TextRun& b) { return a == b; }
+    static constexpr bool safeToCompareToEmptyOrDeleted = false;
+};
+
+} // namespace WebCore
+
+namespace WTF {
+
+template<> struct DefaultHash<WebCore::TextRun> : WebCore::TextRunHash { };
+
+template<> struct HashTraits<WebCore::TextRun> : GenericHashTraits<WebCore::TextRun> {
+    static bool isDeletedValue(const WebCore::TextRun& value) { return value.isHashTableDeletedValue(); }
+    static bool isEmptyValue(const WebCore::TextRun& value) { return value.isHashTableEmptyValue(); }
+    static void constructDeletedValue(WebCore::TextRun& slot) { new (NotNull, &slot) WebCore::TextRun(WTF::HashTableDeletedValue); }
+    static WebCore::TextRun emptyValue() { return WebCore::TextRun(WTF::HashTableEmptyValue); }
+};
+
+} // namespace WTF
diff --git a/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.cpp b/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.cpp
index 262f0d6..edd2b15 100644
--- a/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.cpp
+++ b/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006 Apple Inc.  All rights reserved.
+ * Copyright (C) 2006-2022 Apple Inc.  All rights reserved.
  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
  * Copyright (C) 2008, 2009 Dirk Schulze <krit@webkit.org>
  * Copyright (C) 2008 Nuanti Ltd.
@@ -37,6 +37,7 @@
 
 #include "AffineTransform.h"
 #include "CairoOperations.h"
+#include "DecomposedGlyphs.h"
 #include "FloatRect.h"
 #include "FloatRoundedRect.h"
 #include "Gradient.h"
@@ -437,6 +438,12 @@
         fontSmoothing);
 }
 
+void GraphicsContextCairo::drawDecomposedGlyphs(const Font& font, const DecomposedGlyphs& decomposedGlyphs)
+{
+    auto positionedGlyphs = decomposedGlyphs.positionedGlyphs();
+    return drawGlyphs(font, positionedGlyphs.glyphs.data(), positionedGlyphs.advances.data(), positionedGlyphs.glyphs.size(), positionedGlyphs.localAnchor, positionedGlyphs.smoothingMode);
+}
+
 cairo_t* GraphicsContextCairo::cr() const
 {
     return m_cr.get();
diff --git a/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.h b/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.h
index 7321470..bb485c0 100644
--- a/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.h
+++ b/Source/WebCore/platform/graphics/cairo/GraphicsContextCairo.h
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2017 Metrological Group B.V.
  * Copyright (C) 2017 Igalia S.L.
+ * Copyright (C) 2022 Apple Inc.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -70,6 +71,7 @@
     void clearRect(const FloatRect&) final;
 
     void drawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint&, FontSmoothingMode) final;
+    void drawDecomposedGlyphs(const Font&, const DecomposedGlyphs&) final;
 
     void drawNativeImage(NativeImage&, const FloatSize&, const FloatRect&, const FloatRect&, const ImagePaintingOptions&) final;
     void drawPattern(NativeImage&, const FloatRect& destRect, const FloatRect& srcRect, const AffineTransform&, const FloatPoint& phase, const FloatSize& spacing, const ImagePaintingOptions&) final;
diff --git a/Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp b/Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp
index c54d2b9..f1da479 100644
--- a/Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp
+++ b/Source/WebCore/platform/graphics/coretext/DrawGlyphsRecorderCoreText.cpp
@@ -82,7 +82,12 @@
     CGContextDelegateSetCallback(contextDelegate.get(), deEndLayer, reinterpret_cast<CGContextDelegateCallback>(&endLayer));
     CGContextDelegateSetCallback(contextDelegate.get(), deDrawGlyphs, reinterpret_cast<CGContextDelegateCallback>(&WebCore::drawGlyphs));
     CGContextDelegateSetCallback(contextDelegate.get(), deDrawImage, reinterpret_cast<CGContextDelegateCallback>(&drawImage));
-    auto context = adoptCF(CGContextCreateWithDelegate(contextDelegate.get(), kCGContextTypeUnknown, nullptr, nullptr));
+#if HAVE(CORE_TEXT_FIX_FOR_RADAR_93925620)
+    auto contextType = kCGContextTypeUnknown;
+#else
+    auto contextType = kCGContextTypeWindow;
+#endif
+    auto context = adoptCF(CGContextCreateWithDelegate(contextDelegate.get(), contextType, nullptr, nullptr));
     return makeUniqueRef<GraphicsContextCG>(context.get());
 }
 
@@ -191,7 +196,11 @@
 {
     if (m_owner.getCTM() == ctm)
         return;
-    m_owner.setCTM(ctm);
+    // Instead of recording a SetCTM command, we compute the transform needed
+    // to change the current CTM to `ctm`. This allows the recorded comamnds
+    // to be re-used by elements drawing the same text in different locations.
+    if (auto inverseOfCurrentCTM = m_owner.getCTM().inverse())
+        m_owner.concatCTM(*inverseOfCurrentCTM * ctm);
 }
 
 void DrawGlyphsRecorder::updateShadow(const DropShadow& dropShadow, ShadowsIgnoreTransforms shadowsIgnoreTransforms)
@@ -303,7 +312,7 @@
         initialPenPosition.move(0, -ascentDelta);
     }
 
-    m_owner.drawGlyphsAndCacheFont(font, glyphs, computeAdvancesFromPositions(positions, count, textMatrix).data(), count, initialPenPosition, m_smoothingMode);
+    m_owner.drawGlyphsAndCacheResources(font, glyphs, computeAdvancesFromPositions(positions, count, textMatrix).data(), count, initialPenPosition, m_smoothingMode);
 
     m_owner.concatCTM(inverseCTMFixup);
 }
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayList.cpp b/Source/WebCore/platform/graphics/displaylists/DisplayList.cpp
index a4997c6..b25935f 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayList.cpp
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayList.cpp
@@ -42,7 +42,7 @@
 CString DisplayList::description() const
 {
     TextStream ts;
-    ts << *this;
+    dump(ts);
     return ts.release().utf8();
 }
 
@@ -248,6 +248,8 @@
         return append<DrawFilteredImageBuffer>(item.get<DrawFilteredImageBuffer>());
     case ItemType::DrawGlyphs:
         return append<DrawGlyphs>(item.get<DrawGlyphs>());
+    case ItemType::DrawDecomposedGlyphs:
+        return append<DrawDecomposedGlyphs>(item.get<DrawDecomposedGlyphs>());
     case ItemType::DrawImageBuffer:
         return append<DrawImageBuffer>(item.get<DrawImageBuffer>());
     case ItemType::DrawNativeImage:
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayList.h b/Source/WebCore/platform/graphics/displaylists/DisplayList.h
index 9a2cf0e..84e1cfa 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayList.h
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayList.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "DecomposedGlyphs.h"
 #include "DisplayListItemBuffer.h"
 #include "DisplayListItemType.h"
 #include "DisplayListResourceHeap.h"
@@ -118,6 +119,11 @@
         m_resourceHeap.add(font.renderingResourceIdentifier(), Ref { font });
     }
 
+    void cacheDecomposedGlyphs(WebCore::DecomposedGlyphs& decomposedGlyphs)
+    {
+        m_resourceHeap.add(decomposedGlyphs.renderingResourceIdentifier(), Ref { decomposedGlyphs });
+    }
+
     static bool shouldDumpForFlags(OptionSet<AsTextFlag>, ItemHandle);
 
     LocalResourceHeap m_resourceHeap;
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListItemBuffer.cpp b/Source/WebCore/platform/graphics/displaylists/DisplayListItemBuffer.cpp
index a630b02..74378f8 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListItemBuffer.cpp
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListItemBuffer.cpp
@@ -115,6 +115,9 @@
     case ItemType::DrawGlyphs:
         ASSERT_NOT_REACHED();
         return;
+    case ItemType::DrawDecomposedGlyphs:
+        ASSERT_NOT_REACHED();
+        return;
     case ItemType::DrawImageBuffer:
         get<DrawImageBuffer>().apply(context);
         return;
@@ -261,6 +264,9 @@
     case ItemType::DrawGlyphs:
         get<DrawGlyphs>().~DrawGlyphs();
         return;
+    case ItemType::DrawDecomposedGlyphs:
+        get<DrawDecomposedGlyphs>().~DrawDecomposedGlyphs();
+        break;
     case ItemType::DrawLinesForText:
         get<DrawLinesForText>().~DrawLinesForText();
         return;
@@ -483,6 +489,8 @@
         return copyInto<DrawFocusRingRects>(itemOffset, *this);
     case ItemType::DrawGlyphs:
         return copyInto<DrawGlyphs>(itemOffset, *this);
+    case ItemType::DrawDecomposedGlyphs:
+        return copyInto<DrawDecomposedGlyphs>(itemOffset, *this);
     case ItemType::DrawImageBuffer:
         return copyInto<DrawImageBuffer>(itemOffset, *this);
     case ItemType::DrawLinesForText:
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.cpp b/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.cpp
index 9b361ba..e2e60df 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.cpp
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.cpp
@@ -80,6 +80,8 @@
         return sizeof(DrawFilteredImageBuffer);
     case ItemType::DrawGlyphs:
         return sizeof(DrawGlyphs);
+    case ItemType::DrawDecomposedGlyphs:
+        return sizeof(DrawDecomposedGlyphs);
     case ItemType::DrawImageBuffer:
         return sizeof(DrawImageBuffer);
     case ItemType::DrawNativeImage:
@@ -210,6 +212,7 @@
     case ItemType::DrawFocusRingPath:
     case ItemType::DrawFocusRingRects:
     case ItemType::DrawGlyphs:
+    case ItemType::DrawDecomposedGlyphs:
     case ItemType::DrawImageBuffer:
     case ItemType::DrawLine:
     case ItemType::DrawLinesForText:
@@ -311,6 +314,7 @@
     case ItemType::DrawDotsForDocumentMarker:
     case ItemType::DrawEllipse:
     case ItemType::DrawFilteredImageBuffer:
+    case ItemType::DrawDecomposedGlyphs:
     case ItemType::DrawImageBuffer:
     case ItemType::DrawNativeImage:
     case ItemType::DrawPattern:
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.h b/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.h
index 3614a37..942c6e7 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.h
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListItemType.h
@@ -57,6 +57,7 @@
     ClipPath,
     DrawFilteredImageBuffer,
     DrawGlyphs,
+    DrawDecomposedGlyphs,
     DrawImageBuffer,
     DrawNativeImage,
     DrawSystemImage,
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListItems.cpp b/Source/WebCore/platform/graphics/displaylists/DisplayListItems.cpp
index 6b2ff00..c896603 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListItems.cpp
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListItems.cpp
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "DisplayListItems.h"
 
+#include "DecomposedGlyphs.h"
 #include "DisplayListReplayer.h"
 #include "Filter.h"
 #include "FontCascade.h"
@@ -176,50 +177,28 @@
     context.drawFilteredImageBuffer(sourceImage, m_sourceImageRect, m_filter, results);
 }
 
-DrawGlyphs::DrawGlyphs(RenderingResourceIdentifier fontIdentifier, Vector<GlyphBufferGlyph, 128>&& glyphs, Vector<GlyphBufferAdvance, 128>&& advances, const FloatRect& bounds, const FloatPoint& localAnchor, FontSmoothingMode smoothingMode)
+DrawGlyphs::DrawGlyphs(RenderingResourceIdentifier fontIdentifier, PositionedGlyphs&& positionedGlyphs, const FloatRect& bounds)
     : m_fontIdentifier(fontIdentifier)
-    , m_glyphs(WTFMove(glyphs))
-    , m_advances(WTFMove(advances))
+    , m_positionedGlyphs(WTFMove(positionedGlyphs))
     , m_bounds(bounds)
-    , m_localAnchor(localAnchor)
-    , m_smoothingMode(smoothingMode)
 {
 }
 
 DrawGlyphs::DrawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode smoothingMode)
     : m_fontIdentifier(font.renderingResourceIdentifier())
-    , m_localAnchor(localAnchor)
-    , m_smoothingMode(smoothingMode)
+    , m_positionedGlyphs { { glyphs, count }, { advances, count }, localAnchor, smoothingMode }
+    , m_bounds(m_positionedGlyphs.computeBounds(font))
 {
-    m_glyphs.reserveInitialCapacity(count);
-    m_advances.reserveInitialCapacity(count);
-    for (unsigned i = 0; i < count; ++i) {
-        m_glyphs.uncheckedAppend(glyphs[i]);
-        m_advances.uncheckedAppend(advances[i]);
-    }
-    computeBounds(font);
 }
 
 void DrawGlyphs::apply(GraphicsContext& context, const Font& font) const
 {
-    context.drawGlyphs(font, m_glyphs.data(), m_advances.data(), m_glyphs.size(), anchorPoint(), m_smoothingMode);
+    return context.drawGlyphs(font, m_positionedGlyphs.glyphs.data(), m_positionedGlyphs.advances.data(), m_positionedGlyphs.glyphs.size(), anchorPoint(), m_positionedGlyphs.smoothingMode);
 }
 
-void DrawGlyphs::computeBounds(const Font& font)
+void DrawDecomposedGlyphs::apply(GraphicsContext& context, const Font& font, const DecomposedGlyphs& decomposedGlyphs) const
 {
-    // FIXME: This code doesn't actually take the extents of the glyphs into consideration. It assumes that
-    // the glyph lies entirely within its [(ascent + descent), advance] rect.
-    float ascent = font.fontMetrics().floatAscent();
-    float descent = font.fontMetrics().floatDescent();
-    FloatPoint current = localAnchor();
-    size_t numGlyphs = m_glyphs.size();
-    for (size_t i = 0; i < numGlyphs; ++i) {
-        GlyphBufferAdvance advance = m_advances[i];
-        FloatRect glyphRect = FloatRect(current.x(), current.y() - ascent, width(advance), ascent + descent);
-        m_bounds.unite(glyphRect);
-
-        current.move(width(advance), height(advance));
-    }
+    return context.drawDecomposedGlyphs(font, decomposedGlyphs);
 }
 
 NO_RETURN_DUE_TO_ASSERT void DrawImageBuffer::apply(GraphicsContext&) const
@@ -612,7 +591,7 @@
 }
 
 #if !LOG_DISABLED
-static TextStream& operator<<(TextStream& ts, ItemType type)
+TextStream& operator<<(TextStream& ts, ItemType type)
 {
     switch (type) {
     case ItemType::Save: ts << "save"; break;
@@ -637,6 +616,7 @@
     case ItemType::ClipPath: ts << "clip-path"; break;
     case ItemType::DrawFilteredImageBuffer: ts << "draw-filtered-image-buffer"; break;
     case ItemType::DrawGlyphs: ts << "draw-glyphs"; break;
+    case ItemType::DrawDecomposedGlyphs: ts << "draw-decomposed-glyphs"; break;
     case ItemType::DrawImageBuffer: ts << "draw-image-buffer"; break;
     case ItemType::DrawNativeImage: ts << "draw-native-image"; break;
     case ItemType::DrawSystemImage: ts << "draw-system-image"; break;
@@ -796,7 +776,14 @@
     ts.dumpProperty("local-anchor", item.localAnchor());
     ts.dumpProperty("anchor-point", item.anchorPoint());
     ts.dumpProperty("length", item.glyphs().size());
+}
 
+void dumpItem(TextStream& ts, const DrawDecomposedGlyphs& item, OptionSet<AsTextFlag> flags)
+{
+    if (flags.contains(AsTextFlag::IncludeResourceIdentifiers)) {
+        ts.dumpProperty("font-identifier", item.fontIdentifier());
+        ts.dumpProperty("draw-glyphs-data-identifier", item.decomposedGlyphsIdentifier());
+    }
 }
 
 void dumpItem(TextStream& ts, const DrawImageBuffer& item, OptionSet<AsTextFlag> flags)
@@ -1087,6 +1074,9 @@
     case ItemType::DrawGlyphs:
         dumpItem(ts, item.get<DrawGlyphs>(), flags);
         break;
+    case ItemType::DrawDecomposedGlyphs:
+        dumpItem(ts, item.get<DrawDecomposedGlyphs>(), flags);
+        break;
     case ItemType::DrawImageBuffer:
         dumpItem(ts, item.get<DrawImageBuffer>(), flags);
         break;
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListItems.h b/Source/WebCore/platform/graphics/displaylists/DisplayListItems.h
index 16f24ff..150ad93 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListItems.h
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListItems.h
@@ -37,6 +37,7 @@
 #include "MediaPlayerIdentifier.h"
 #include "Pattern.h"
 #include "PixelBuffer.h"
+#include "PositionedGlyphs.h"
 #include "RenderingResourceIdentifier.h"
 #include "SharedBuffer.h"
 #include "SystemImage.h"
@@ -583,15 +584,15 @@
     static constexpr bool isDrawingItem = true;
 
     RenderingResourceIdentifier fontIdentifier() { return m_fontIdentifier; }
-    const FloatPoint& localAnchor() const { return m_localAnchor; }
-    FloatPoint anchorPoint() const { return m_localAnchor; }
-    const Vector<GlyphBufferGlyph, 16>& glyphs() const { return m_glyphs; }
+    const FloatPoint& localAnchor() const { return m_positionedGlyphs.localAnchor; }
+    FloatPoint anchorPoint() const { return m_positionedGlyphs.localAnchor; }
+    const Vector<GlyphBufferGlyph>& glyphs() const { return m_positionedGlyphs.glyphs; }
 
     template<class Encoder> void encode(Encoder&) const;
     template<class Decoder> static std::optional<DrawGlyphs> decode(Decoder&);
 
     WEBCORE_EXPORT DrawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode);
-    WEBCORE_EXPORT DrawGlyphs(RenderingResourceIdentifier, Vector<GlyphBufferGlyph, 128>&&, Vector<GlyphBufferAdvance, 128>&&, const FloatRect&, const FloatPoint& localAnchor, FontSmoothingMode);
+    WEBCORE_EXPORT DrawGlyphs(RenderingResourceIdentifier, PositionedGlyphs&&, const FloatRect&);
 
     WEBCORE_EXPORT void apply(GraphicsContext&, const Font&) const;
 
@@ -599,25 +600,17 @@
     std::optional<FloatRect> localBounds(const GraphicsContext&) const { return m_bounds; }
 
 private:
-    void computeBounds(const Font&);
-
     RenderingResourceIdentifier m_fontIdentifier;
-    Vector<GlyphBufferGlyph, 16> m_glyphs;
-    Vector<GlyphBufferAdvance, 16> m_advances;
+    PositionedGlyphs m_positionedGlyphs;
     FloatRect m_bounds;
-    FloatPoint m_localAnchor;
-    FontSmoothingMode m_smoothingMode;
 };
 
 template<class Encoder>
 void DrawGlyphs::encode(Encoder& encoder) const
 {
     encoder << m_fontIdentifier;
-    encoder << m_glyphs;
-    encoder << m_advances;
+    encoder << m_positionedGlyphs;
     encoder << m_bounds;
-    encoder << m_localAnchor;
-    encoder << m_smoothingMode;
 }
 
 template<class Decoder>
@@ -628,17 +621,9 @@
     if (!fontIdentifier)
         return std::nullopt;
 
-    std::optional<Vector<GlyphBufferGlyph, 128>> glyphs;
-    decoder >> glyphs;
-    if (!glyphs)
-        return std::nullopt;
-
-    std::optional<Vector<GlyphBufferAdvance, 128>> advances;
-    decoder >> advances;
-    if (!advances)
-        return std::nullopt;
-
-    if (glyphs->size() != advances->size())
+    std::optional<PositionedGlyphs> positionedGlyphs;
+    decoder >> positionedGlyphs;
+    if (!positionedGlyphs)
         return std::nullopt;
 
     std::optional<FloatRect> bounds;
@@ -646,19 +631,36 @@
     if (!bounds)
         return std::nullopt;
 
-    std::optional<FloatPoint> localAnchor;
-    decoder >> localAnchor;
-    if (!localAnchor)
-        return std::nullopt;
-
-    std::optional<FontSmoothingMode> smoothingMode;
-    decoder >> smoothingMode;
-    if (!smoothingMode)
-        return std::nullopt;
-
-    return {{ *fontIdentifier, WTFMove(*glyphs), WTFMove(*advances), *bounds, *localAnchor, *smoothingMode }};
+    return { { *fontIdentifier, WTFMove(*positionedGlyphs), *bounds } };
 }
 
+class DrawDecomposedGlyphs {
+public:
+    static constexpr ItemType itemType = ItemType::DrawDecomposedGlyphs;
+    static constexpr bool isInlineItem = true;
+    static constexpr bool isDrawingItem = true;
+
+    DrawDecomposedGlyphs(RenderingResourceIdentifier fontIdentifier, RenderingResourceIdentifier decomposedGlyphsIdentifier, const FloatRect& bounds)
+        : m_fontIdentifier(fontIdentifier)
+        , m_decomposedGlyphsIdentifier(decomposedGlyphsIdentifier)
+        , m_bounds(bounds)
+    {
+    }
+
+    RenderingResourceIdentifier fontIdentifier() const { return m_fontIdentifier; }
+    RenderingResourceIdentifier decomposedGlyphsIdentifier() const { return m_decomposedGlyphsIdentifier; }
+
+    WEBCORE_EXPORT void apply(GraphicsContext&, const Font&, const DecomposedGlyphs&) const;
+
+    std::optional<FloatRect> globalBounds() const { return std::nullopt; }
+    std::optional<FloatRect> localBounds(const GraphicsContext&) const { return m_bounds; }
+
+private:
+    RenderingResourceIdentifier m_fontIdentifier;
+    RenderingResourceIdentifier m_decomposedGlyphsIdentifier;
+    FloatRect m_bounds;
+};
+
 class DrawImageBuffer {
 public:
     static constexpr ItemType itemType = ItemType::DrawImageBuffer;
@@ -1992,6 +1994,7 @@
     , DrawFocusRingPath
     , DrawFocusRingRects
     , DrawGlyphs
+    , DrawDecomposedGlyphs
     , DrawImageBuffer
     , DrawLine
     , DrawLinesForText
@@ -2072,6 +2075,7 @@
 WEBCORE_EXPORT void dumpItem(TextStream&, const ClipPath&, OptionSet<AsTextFlag>);
 WEBCORE_EXPORT void dumpItem(TextStream&, const DrawFilteredImageBuffer&, OptionSet<AsTextFlag>);
 WEBCORE_EXPORT void dumpItem(TextStream&, const DrawGlyphs&, OptionSet<AsTextFlag>);
+WEBCORE_EXPORT void dumpItem(TextStream&, const DrawDecomposedGlyphs&, OptionSet<AsTextFlag>);
 WEBCORE_EXPORT void dumpItem(TextStream&, const DrawImageBuffer&, OptionSet<AsTextFlag>);
 WEBCORE_EXPORT void dumpItem(TextStream&, const DrawNativeImage&, OptionSet<AsTextFlag>);
 WEBCORE_EXPORT void dumpItem(TextStream&, const DrawSystemImage&, OptionSet<AsTextFlag>);
@@ -2126,6 +2130,9 @@
     dumpItemHandle(ts, itemHandle, { AsTextFlag::IncludePlatformOperations, AsTextFlag::IncludeResourceIdentifiers });
     return ts;
 }
+
+TextStream& operator<<(TextStream&, ItemType);
+
 #endif
 
 } // namespace DisplayList
@@ -2158,6 +2165,7 @@
     WebCore::DisplayList::ItemType::ClipOutToPath,
     WebCore::DisplayList::ItemType::ClipPath,
     WebCore::DisplayList::ItemType::DrawGlyphs,
+    WebCore::DisplayList::ItemType::DrawDecomposedGlyphs,
     WebCore::DisplayList::ItemType::DrawImageBuffer,
     WebCore::DisplayList::ItemType::DrawNativeImage,
     WebCore::DisplayList::ItemType::DrawSystemImage,
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.cpp b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.cpp
index eed444e..fe388be 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.cpp
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.cpp
@@ -46,10 +46,10 @@
 namespace WebCore {
 namespace DisplayList {
 
-Recorder::Recorder(const GraphicsContextState& state, const FloatRect& initialClip, const AffineTransform& initialCTM, DeconstructDrawGlyphs deconstructDrawGlyphs)
+Recorder::Recorder(const GraphicsContextState& state, const FloatRect& initialClip, const AffineTransform& initialCTM, DrawGlyphsMode drawGlyphsMode)
     : GraphicsContext(state)
     , m_initialScale(initialCTM.xScale())
-    , m_deconstructDrawGlyphs(deconstructDrawGlyphs)
+    , m_drawGlyphsMode(drawGlyphsMode)
 {
     ASSERT(!state.changes());
     m_stateStack.append({ state, initialCTM, initialCTM.mapRect(initialClip) });
@@ -161,23 +161,51 @@
     recordDrawFilteredImageBuffer(sourceImage, sourceImageRect, filter);
 }
 
+bool Recorder::shouldDeconstructDrawGlyphs() const
+{
+    switch (m_drawGlyphsMode) {
+    case DrawGlyphsMode::Normal:
+        return false;
+    case DrawGlyphsMode::DeconstructUsingDrawGlyphsCommands:
+    case DrawGlyphsMode::DeconstructUsingDrawDecomposedGlyphsCommands:
+        return true;
+    }
+    ASSERT_NOT_REACHED();
+    return false;
+}
+
 void Recorder::drawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
 {
-    if (m_deconstructDrawGlyphs == DeconstructDrawGlyphs::Yes) {
+    if (shouldDeconstructDrawGlyphs()) {
         if (!m_drawGlyphsRecorder)
             m_drawGlyphsRecorder = makeUnique<DrawGlyphsRecorder>(*this, m_initialScale);
-
         m_drawGlyphsRecorder->drawGlyphs(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
         return;
     }
 
-    drawGlyphsAndCacheFont(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
+    drawGlyphsAndCacheResources(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
 }
 
-void Recorder::drawGlyphsAndCacheFont(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& localAnchor, FontSmoothingMode smoothingMode)
+void Recorder::drawDecomposedGlyphs(const Font& font, const DecomposedGlyphs& decomposedGlyphs)
 {
     appendStateChangeItemIfNecessary();
     recordResourceUse(const_cast<Font&>(font));
+    recordResourceUse(const_cast<DecomposedGlyphs&>(decomposedGlyphs));
+    recordDrawDecomposedGlyphs(font, decomposedGlyphs);
+}
+
+void Recorder::drawGlyphsAndCacheResources(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& localAnchor, FontSmoothingMode smoothingMode)
+{
+    appendStateChangeItemIfNecessary();
+    recordResourceUse(const_cast<Font&>(font));
+
+    if (m_drawGlyphsMode == DrawGlyphsMode::DeconstructUsingDrawDecomposedGlyphsCommands) {
+        auto decomposedGlyphs = DecomposedGlyphs::create(font, glyphs, advances, numGlyphs, localAnchor, smoothingMode);
+        recordResourceUse(decomposedGlyphs.get());
+        recordDrawDecomposedGlyphs(font, decomposedGlyphs.get());
+        return;
+    }
+
     recordDrawGlyphs(font, glyphs, advances, numGlyphs, localAnchor, smoothingMode);
 }
 
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.h b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.h
index 6004833..6314924 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.h
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.h
@@ -51,9 +51,13 @@
     WTF_MAKE_FAST_ALLOCATED;
     WTF_MAKE_NONCOPYABLE(Recorder);
 public:
-    enum class DeconstructDrawGlyphs : bool { No, Yes };
+    enum class DrawGlyphsMode {
+        Normal,
+        DeconstructUsingDrawGlyphsCommands,
+        DeconstructUsingDrawDecomposedGlyphsCommands,
+    };
 
-    WEBCORE_EXPORT Recorder(const GraphicsContextState&, const FloatRect& initialClip, const AffineTransform&, DeconstructDrawGlyphs = DeconstructDrawGlyphs::No);
+    WEBCORE_EXPORT Recorder(const GraphicsContextState&, const FloatRect& initialClip, const AffineTransform&, DrawGlyphsMode = DrawGlyphsMode::Normal);
     WEBCORE_EXPORT virtual ~Recorder();
 
     virtual void convertToLuminanceMask() = 0;
@@ -83,6 +87,7 @@
     virtual void recordClipPath(const Path&, WindRule) = 0;
     virtual void recordDrawFilteredImageBuffer(ImageBuffer*, const FloatRect& sourceImageRect, Filter&) = 0;
     virtual void recordDrawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode) = 0;
+    virtual void recordDrawDecomposedGlyphs(const Font&, const DecomposedGlyphs&) = 0;
     virtual void recordDrawImageBuffer(ImageBuffer&, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions&) = 0;
     virtual void recordDrawNativeImage(RenderingResourceIdentifier imageIdentifier, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions&) = 0;
     virtual void recordDrawSystemImage(SystemImage&, const FloatRect&) = 0;
@@ -134,6 +139,7 @@
     virtual bool recordResourceUse(ImageBuffer&) = 0;
     virtual bool recordResourceUse(const SourceImage&) = 0;
     virtual bool recordResourceUse(Font&) = 0;
+    virtual bool recordResourceUse(DecomposedGlyphs&) = 0;
 
     struct ContextState {
         GraphicsContextState state;
@@ -213,7 +219,8 @@
     WEBCORE_EXPORT void drawFilteredImageBuffer(ImageBuffer* sourceImage, const FloatRect& sourceImageRect, Filter&, FilterResults&) final;
 
     WEBCORE_EXPORT void drawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned numGlyphs, const FloatPoint& anchorPoint, FontSmoothingMode) final;
-    WEBCORE_EXPORT void drawGlyphsAndCacheFont(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode) final;
+    WEBCORE_EXPORT void drawDecomposedGlyphs(const Font&, const DecomposedGlyphs&) override;
+    WEBCORE_EXPORT void drawGlyphsAndCacheResources(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode) final;
 
     WEBCORE_EXPORT void drawImageBuffer(ImageBuffer&, const FloatRect& destination, const FloatRect& source, const ImagePaintingOptions&) final;
     WEBCORE_EXPORT void drawNativeImage(NativeImage&, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions&) final;
@@ -270,10 +277,12 @@
 
     const AffineTransform& ctm() const;
 
+    bool shouldDeconstructDrawGlyphs() const;
+
     Vector<ContextState, 4> m_stateStack;
     std::unique_ptr<DrawGlyphsRecorder> m_drawGlyphsRecorder;
     float m_initialScale { 1 };
-    const DeconstructDrawGlyphs m_deconstructDrawGlyphs { DeconstructDrawGlyphs::No };
+    const DrawGlyphsMode m_drawGlyphsMode { DrawGlyphsMode::Normal };
 };
 
 } // namespace DisplayList
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.cpp b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.cpp
index 3dd1188..66d0b9f 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.cpp
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.cpp
@@ -42,8 +42,8 @@
 namespace WebCore {
 namespace DisplayList {
 
-RecorderImpl::RecorderImpl(DisplayList& displayList, const GraphicsContextState& state, const FloatRect& initialClip, const AffineTransform& initialCTM, DeconstructDrawGlyphs deconstructDrawGlyphs)
-    : Recorder(state, initialClip, initialCTM, deconstructDrawGlyphs)
+RecorderImpl::RecorderImpl(DisplayList& displayList, const GraphicsContextState& state, const FloatRect& initialClip, const AffineTransform& initialCTM, DrawGlyphsMode drawGlyphsMode)
+    : Recorder(state, initialClip, initialCTM, drawGlyphsMode)
     , m_displayList(displayList)
 {
     LOG_WITH_STREAM(DisplayLists, stream << "\nRecording with clip " << initialClip);
@@ -172,6 +172,11 @@
     append<DrawGlyphs>(font, glyphs, advances, count, localAnchor, mode);
 }
 
+void RecorderImpl::recordDrawDecomposedGlyphs(const Font& font, const DecomposedGlyphs& decomposedGlyphs)
+{
+    append<DrawDecomposedGlyphs>(font.renderingResourceIdentifier(), decomposedGlyphs.renderingResourceIdentifier(), decomposedGlyphs.bounds());
+}
+
 void RecorderImpl::recordDrawImageBuffer(ImageBuffer& imageBuffer, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
 {
     append<DrawImageBuffer>(imageBuffer.renderingResourceIdentifier(), destRect, srcRect, options);
@@ -405,6 +410,12 @@
     return true;
 }
 
+bool RecorderImpl::recordResourceUse(DecomposedGlyphs& decomposedGlyphs)
+{
+    m_displayList.cacheDecomposedGlyphs(decomposedGlyphs);
+    return true;
+}
+
 // FIXME: share with ShadowData
 static inline float shadowPaintingExtent(float blurRadius)
 {
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.h b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.h
index 426ab03..d77d0e4 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.h
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.h
@@ -35,7 +35,7 @@
     WTF_MAKE_FAST_ALLOCATED;
     WTF_MAKE_NONCOPYABLE(RecorderImpl);
 public:
-    WEBCORE_EXPORT RecorderImpl(DisplayList&, const GraphicsContextState&, const FloatRect& initialClip, const AffineTransform&, DeconstructDrawGlyphs = DeconstructDrawGlyphs::No);
+    WEBCORE_EXPORT RecorderImpl(DisplayList&, const GraphicsContextState&, const FloatRect& initialClip, const AffineTransform&, DrawGlyphsMode = DrawGlyphsMode::Normal);
     WEBCORE_EXPORT virtual ~RecorderImpl();
 
     bool isEmpty() const { return m_displayList.isEmpty(); }
@@ -67,6 +67,7 @@
     void recordClipPath(const Path&, WindRule) final;
     void recordDrawFilteredImageBuffer(ImageBuffer*, const FloatRect& sourceImageRect, Filter&) final;
     void recordDrawGlyphs(const Font&, const GlyphBufferGlyph*, const GlyphBufferAdvance*, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode) final;
+    void recordDrawDecomposedGlyphs(const Font&, const DecomposedGlyphs&) final;
     void recordDrawImageBuffer(ImageBuffer&, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions&) final;
     void recordDrawNativeImage(RenderingResourceIdentifier imageIdentifier, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions&) final;
     void recordDrawSystemImage(SystemImage&, const FloatRect&) final;
@@ -118,6 +119,7 @@
     bool recordResourceUse(ImageBuffer&) final;
     bool recordResourceUse(const SourceImage&) final;
     bool recordResourceUse(Font&) final;
+    bool recordResourceUse(DecomposedGlyphs&) final;
 
     template<typename T, class... Args>
     void append(Args&&... args)
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.cpp b/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.cpp
index 97e11cb..704f527 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.cpp
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.cpp
@@ -117,45 +117,68 @@
     return resourceIdentifier;
 }
 
-std::pair<std::optional<StopReplayReason>, std::optional<RenderingResourceIdentifier>> Replayer::applyItem(ItemHandle item)
+inline static std::optional<RenderingResourceIdentifier> applyDrawDecomposedGlyphs(GraphicsContext& context, const ResourceHeap& resourceHeap, DrawDecomposedGlyphs& drawDecomposedGlyphsItem)
+{
+    auto fontIdentifier = drawDecomposedGlyphsItem.fontIdentifier();
+    auto* font = resourceHeap.getFont(fontIdentifier);
+    if (!font)
+        return fontIdentifier;
+
+    auto drawGlyphsIdentifier = drawDecomposedGlyphsItem.decomposedGlyphsIdentifier();
+    auto* decomposedGlyphs = resourceHeap.getDecomposedGlyphs(drawGlyphsIdentifier);
+    if (!decomposedGlyphs)
+        return drawGlyphsIdentifier;
+
+    drawDecomposedGlyphsItem.apply(context, *font, *decomposedGlyphs);
+    return std::nullopt;
+}
+
+auto Replayer::applyItem(ItemHandle item) -> ApplyItemResult
 {
     switch (item.type()) {
     case ItemType::ClipToImageBuffer:
         if (auto missingCachedResourceIdentifier = applyImageBufferItem<ClipToImageBuffer>(m_context, m_resourceHeap, item))
             return { StopReplayReason::MissingCachedResource, WTFMove(missingCachedResourceIdentifier) };
-        return { std::nullopt, std::nullopt };
+        return { };
 
-    case ItemType::DrawGlyphs:
+    case ItemType::DrawGlyphs: {
         if (auto missingCachedResourceIdentifier = applyDrawGlyphs(m_context, m_resourceHeap, item.get<DrawGlyphs>()))
             return { StopReplayReason::MissingCachedResource, WTFMove(missingCachedResourceIdentifier) };
-        return { std::nullopt, std::nullopt };
+        return { };
+    }
+
+    case ItemType::DrawDecomposedGlyphs: {
+        if (auto missingCachedResourceIdentifier = applyDrawDecomposedGlyphs(m_context, m_resourceHeap, item.get<DrawDecomposedGlyphs>()))
+            return { StopReplayReason::MissingCachedResource, WTFMove(missingCachedResourceIdentifier) };
+        return { };
+    }
 
     case ItemType::DrawImageBuffer:
         if (auto missingCachedResourceIdentifier = applyImageBufferItem<DrawImageBuffer>(m_context, m_resourceHeap, item))
             return { StopReplayReason::MissingCachedResource, WTFMove(missingCachedResourceIdentifier) };
-        return { std::nullopt, std::nullopt };
+        return { };
 
     case ItemType::DrawNativeImage:
         if (auto missingCachedResourceIdentifier = applyNativeImageItem<DrawNativeImage>(m_context, m_resourceHeap, item))
             return { StopReplayReason::MissingCachedResource, WTFMove(missingCachedResourceIdentifier) };
-        return { std::nullopt, std::nullopt };
+        return { };
 
     case ItemType::DrawPattern:
         if (auto missingCachedResourceIdentifier = applySourceImageItem<DrawPattern>(m_context, m_resourceHeap, item))
             return { StopReplayReason::MissingCachedResource, WTFMove(missingCachedResourceIdentifier) };
-        return { std::nullopt, std::nullopt };
+        return { };
 
     case ItemType::SetState:
         if (auto missingCachedResourceIdentifier = applySetStateItem(m_context, m_resourceHeap, item))
             return { StopReplayReason::MissingCachedResource, WTFMove(missingCachedResourceIdentifier) };
-        return { std::nullopt, std::nullopt };
+        return { };
 
     default:
         item.apply(m_context);
-        return { std::nullopt, std::nullopt };
+        return { };
     }
 
-    return { std::nullopt, std::nullopt };
+    return { };
 }
 
 ReplayResult Replayer::replay(const FloatRect& initialClip, bool trackReplayList)
@@ -187,9 +210,10 @@
 
         LOG_WITH_STREAM(DisplayLists, stream << "applying " << i++ << " " << item);
 
-        if (auto [reasonForStopping, missingCachedResourceIdentifier] = applyItem(item); reasonForStopping) {
-            result.reasonForStopping = *reasonForStopping;
-            result.missingCachedResourceIdentifier = WTFMove(missingCachedResourceIdentifier);
+        auto applyResult = applyItem(item);
+        if (applyResult.stopReason) {
+            result.reasonForStopping = *applyResult.stopReason;
+            result.missingCachedResourceIdentifier = WTFMove(applyResult.resourceIdentifier);
             break;
         }
 
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.h b/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.h
index 2584a53..def5568 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.h
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListReplayer.h
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "GraphicsContext.h"
 #include "InMemoryDisplayList.h"
 #include <wtf/Noncopyable.h>
 #include <wtf/text/WTFString.h>
@@ -32,7 +33,6 @@
 namespace WebCore {
 
 class FloatRect;
-class GraphicsContext;
 
 namespace DisplayList {
 
@@ -61,7 +61,11 @@
     WEBCORE_EXPORT ReplayResult replay(const FloatRect& initialClip = { }, bool trackReplayList = false);
 
 private:
-    std::pair<std::optional<StopReplayReason>, std::optional<RenderingResourceIdentifier>> applyItem(ItemHandle);
+    struct ApplyItemResult {
+        std::optional<StopReplayReason> stopReason;
+        std::optional<RenderingResourceIdentifier> resourceIdentifier;
+    };
+    ApplyItemResult applyItem(ItemHandle);
 
     GraphicsContext& m_context;
     const DisplayList& m_displayList;
diff --git a/Source/WebCore/platform/graphics/displaylists/DisplayListResourceHeap.h b/Source/WebCore/platform/graphics/displaylists/DisplayListResourceHeap.h
index cfbb6be..5e427d7 100644
--- a/Source/WebCore/platform/graphics/displaylists/DisplayListResourceHeap.h
+++ b/Source/WebCore/platform/graphics/displaylists/DisplayListResourceHeap.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2021-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "DecomposedGlyphs.h"
 #include "Font.h"
 #include "ImageBuffer.h"
 #include "NativeImage.h"
@@ -44,6 +45,7 @@
     virtual NativeImage* getNativeImage(RenderingResourceIdentifier) const = 0;
     virtual std::optional<SourceImage> getSourceImage(RenderingResourceIdentifier) const = 0;
     virtual Font* getFont(RenderingResourceIdentifier) const = 0;
+    virtual DecomposedGlyphs* getDecomposedGlyphs(RenderingResourceIdentifier) const = 0;
 };
 
 class LocalResourceHeap : public ResourceHeap {
@@ -63,6 +65,11 @@
         m_resources.add(renderingResourceIdentifier, WTFMove(font));
     }
 
+    void add(RenderingResourceIdentifier renderingResourceIdentifier, Ref<DecomposedGlyphs>&& decomposedGlyphs)
+    {
+        m_resources.add(renderingResourceIdentifier, WTFMove(decomposedGlyphs));
+    }
+
     ImageBuffer* getImageBuffer(RenderingResourceIdentifier renderingResourceIdentifier) const final
     {
         return get<ImageBuffer>(renderingResourceIdentifier);
@@ -92,6 +99,11 @@
         return get<Font>(renderingResourceIdentifier);
     }
 
+    DecomposedGlyphs* getDecomposedGlyphs(RenderingResourceIdentifier renderingResourceIdentifier) const final
+    {
+        return get<DecomposedGlyphs>(renderingResourceIdentifier);
+    }
+
     void clear()
     {
         m_resources.clear();
@@ -108,7 +120,7 @@
         return std::get<Ref<T>>(iterator->value).ptr();
     }
 
-    using Resource = std::variant<std::monostate, Ref<ImageBuffer>, Ref<NativeImage>, Ref<Font>>;
+    using Resource = std::variant<std::monostate, Ref<ImageBuffer>, Ref<NativeImage>, Ref<Font>, Ref<DecomposedGlyphs>>;
     HashMap<RenderingResourceIdentifier, Resource> m_resources;
 };
 
diff --git a/Source/WebCore/platform/graphics/harfbuzz/DrawGlyphsRecorderHarfBuzz.cpp b/Source/WebCore/platform/graphics/harfbuzz/DrawGlyphsRecorderHarfBuzz.cpp
index c328b8f..05dc97d 100644
--- a/Source/WebCore/platform/graphics/harfbuzz/DrawGlyphsRecorderHarfBuzz.cpp
+++ b/Source/WebCore/platform/graphics/harfbuzz/DrawGlyphsRecorderHarfBuzz.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -40,7 +40,7 @@
 
 void DrawGlyphsRecorder::drawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
 {
-    m_owner.drawGlyphsAndCacheFont(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
+    m_owner.drawGlyphsAndCacheResources(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
 }
 
 } // namespace WebCore
diff --git a/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.cpp b/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.cpp
index e7e6e6a..0dfca23 100644
--- a/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.cpp
+++ b/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.cpp
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2018 Metrological Group B.V.
  * Copyright (C) 2018 Igalia S.L.
+ * Copyright (C) 2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -30,6 +31,7 @@
 #include "NicosiaCairoOperationRecorder.h"
 
 #include "CairoOperations.h"
+#include "DecomposedGlyphs.h"
 #include "Filter.h"
 #include "FilterResults.h"
 #include "FloatRoundedRect.h"
@@ -540,6 +542,12 @@
         state.strokeThickness(), state.dropShadow().offset, state.dropShadow().color, fontSmoothing));
 }
 
+void CairoOperationRecorder::drawDecomposedGlyphs(const Font& font, const DecomposedGlyphs& decomposedGlyphs)
+{
+    auto positionedGlyphs = decomposedGlyphs.positionedGlyphs();
+    return drawGlyphs(font, positionedGlyphs.glyphs.data(), positionedGlyphs.advances.data(), positionedGlyphs.glyphs.size(), positionedGlyphs.localAnchor, positionedGlyphs.smoothingMode);
+}
+
 void CairoOperationRecorder::drawImageBuffer(ImageBuffer& buffer, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
 {
     struct DrawImageBuffer final : PaintingOperation, OperationData<RefPtr<cairo_surface_t>, FloatRect, FloatRect, ImagePaintingOptions, float, Cairo::ShadowState> {
diff --git a/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.h b/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.h
index d6527c4..d20f0e5 100644
--- a/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.h
+++ b/Source/WebCore/platform/graphics/nicosia/cairo/NicosiaCairoOperationRecorder.h
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2018 Metrological Group B.V.
  * Copyright (C) 2018 Igalia S.L.
+ * Copyright (C) 2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -63,6 +64,7 @@
     void clearRect(const WebCore::FloatRect&) override;
 
     void drawGlyphs(const WebCore::Font&, const WebCore::GlyphBufferGlyph*, const WebCore::GlyphBufferAdvance*, unsigned numGlyphs, const WebCore::FloatPoint&, WebCore::FontSmoothingMode) override;
+    void drawDecomposedGlyphs(const WebCore::Font&, const WebCore::DecomposedGlyphs&) override;
 
     void drawImageBuffer(WebCore::ImageBuffer&, const WebCore::FloatRect& destination, const WebCore::FloatRect& source, const WebCore::ImagePaintingOptions&) override;
     void drawFilteredImageBuffer(WebCore::ImageBuffer*, const WebCore::FloatRect&, WebCore::Filter&, WebCore::FilterResults&) override;
diff --git a/Source/WebCore/platform/graphics/win/DrawGlyphsRecorderWin.cpp b/Source/WebCore/platform/graphics/win/DrawGlyphsRecorderWin.cpp
index ad4ba1d..b133abb 100644
--- a/Source/WebCore/platform/graphics/win/DrawGlyphsRecorderWin.cpp
+++ b/Source/WebCore/platform/graphics/win/DrawGlyphsRecorderWin.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -40,7 +40,7 @@
 
 void DrawGlyphsRecorder::drawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
 {
-    m_owner.drawGlyphsAndCacheFont(font, glyphs, advances, numGlyphs, startPoint, m_smoothingMode);
+    m_owner.drawGlyphsAndCacheResources(font, glyphs, advances, numGlyphs, startPoint, m_smoothingMode);
 }
 
 } // namespace WebCore
diff --git a/Source/WebCore/platform/text/TextDirection.h b/Source/WebCore/platform/text/TextDirection.h
index 85fe97d..8c1b746 100644
--- a/Source/WebCore/platform/text/TextDirection.h
+++ b/Source/WebCore/platform/text/TextDirection.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020, 2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,6 +25,8 @@
 
 #pragma once
 
+#include <wtf/text/TextStream.h>
+
 namespace WebCore {
 
 enum class TextDirection : bool { LTR, RTL };
@@ -34,4 +36,13 @@
     return direction == TextDirection::LTR;
 }
 
+inline TextStream& operator<<(TextStream& ts, TextDirection textDirection)
+{
+    switch (textDirection) {
+    case TextDirection::LTR: ts << "ltr"; break;
+    case TextDirection::RTL: ts << "rtl"; break;
+    }
+    return ts;
+}
+
 }
diff --git a/Source/WebCore/platform/text/TextFlags.cpp b/Source/WebCore/platform/text/TextFlags.cpp
index c543eeb..c9711a8 100644
--- a/Source/WebCore/platform/text/TextFlags.cpp
+++ b/Source/WebCore/platform/text/TextFlags.cpp
@@ -30,6 +30,24 @@
 
 namespace WebCore {
 
+WTF::TextStream& operator<<(TextStream& ts, ExpansionBehavior::Behavior behavior)
+{
+    switch (behavior) {
+    case ExpansionBehavior::Behavior::Forbid: ts << "forbid"; break;
+    case ExpansionBehavior::Behavior::Allow: ts << "allow"; break;
+    case ExpansionBehavior::Behavior::Force: ts << "force"; break;
+    }
+    return ts;
+}
+
+WTF::TextStream& operator<<(TextStream& ts, ExpansionBehavior expansionBehavior)
+{
+    ts << expansionBehavior.left;
+    ts << ' ';
+    ts << expansionBehavior.right;
+    return ts;
+}
+
 WTF::TextStream& operator<<(TextStream& ts, Kerning kerning)
 {
     switch (kerning) {
diff --git a/Source/WebCore/platform/text/TextFlags.h b/Source/WebCore/platform/text/TextFlags.h
index c0800cd7..5ed5923 100644
--- a/Source/WebCore/platform/text/TextFlags.h
+++ b/Source/WebCore/platform/text/TextFlags.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2003, 2006, 2017 Apple Inc.  All rights reserved.
+ * Copyright (C) 2003, 2006, 2017, 2022 Apple Inc.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -78,6 +78,11 @@
     {
     }
 
+    bool operator==(const ExpansionBehavior& other) const
+    {
+        return left == other.left && right == other.right;
+    }
+
     static ExpansionBehavior defaultBehavior()
     {
         return { };
@@ -108,6 +113,9 @@
     Behavior right : bitsOfKind;
 };
 
+WTF::TextStream& operator<<(WTF::TextStream&, ExpansionBehavior::Behavior);
+WTF::TextStream& operator<<(WTF::TextStream&, ExpansionBehavior);
+
 enum FontSynthesisValues {
     FontSynthesisNone = 0x0,
     FontSynthesisWeight = 0x1,
diff --git a/Source/WebCore/rendering/GlyphDisplayListCache.cpp b/Source/WebCore/rendering/GlyphDisplayListCache.cpp
new file mode 100644
index 0000000..db5c153
--- /dev/null
+++ b/Source/WebCore/rendering/GlyphDisplayListCache.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 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 "GlyphDisplayListCache.h"
+
+#include "DisplayListItems.h"
+#include "DisplayListIterator.h"
+
+namespace WebCore {
+
+static bool canShareDisplayListWithItem(DisplayList::ItemType itemType)
+{
+    using DisplayList::ItemType;
+
+    switch (itemType) {
+    case ItemType::Translate:
+    case ItemType::Scale:
+    case ItemType::ConcatenateCTM:
+    case ItemType::DrawDecomposedGlyphs:
+    case ItemType::DrawImageBuffer:
+    case ItemType::DrawNativeImage:
+    case ItemType::BeginTransparencyLayer:
+    case ItemType::EndTransparencyLayer:
+        return true;
+    case ItemType::Save:
+    case ItemType::Restore:
+    case ItemType::Rotate:
+    case ItemType::SetCTM:
+    case ItemType::SetInlineFillColor:
+    case ItemType::SetInlineStrokeColor:
+    case ItemType::SetStrokeThickness:
+    case ItemType::SetState:
+    case ItemType::SetLineCap:
+    case ItemType::SetLineDash:
+    case ItemType::SetLineJoin:
+    case ItemType::SetMiterLimit:
+    case ItemType::ClearShadow:
+    case ItemType::Clip:
+    case ItemType::ClipOut:
+    case ItemType::ClipToImageBuffer:
+    case ItemType::ClipOutToPath:
+    case ItemType::ClipPath:
+    case ItemType::DrawFilteredImageBuffer:
+    case ItemType::DrawSystemImage:
+    case ItemType::DrawGlyphs:
+    case ItemType::DrawPattern:
+    case ItemType::DrawRect:
+    case ItemType::DrawLine:
+    case ItemType::DrawLinesForText:
+    case ItemType::DrawDotsForDocumentMarker:
+    case ItemType::DrawEllipse:
+    case ItemType::DrawPath:
+    case ItemType::DrawFocusRingPath:
+    case ItemType::DrawFocusRingRects:
+    case ItemType::FillRect:
+    case ItemType::FillRectWithColor:
+    case ItemType::FillRectWithGradient:
+    case ItemType::FillCompositedRect:
+    case ItemType::FillRoundedRect:
+    case ItemType::FillRectWithRoundedHole:
+#if ENABLE(INLINE_PATH_DATA)
+    case ItemType::FillLine:
+    case ItemType::FillArc:
+    case ItemType::FillQuadCurve:
+    case ItemType::FillBezierCurve:
+#endif
+    case ItemType::FillPath:
+    case ItemType::FillEllipse:
+#if ENABLE(VIDEO)
+    case ItemType::PaintFrameForMedia:
+#endif
+    case ItemType::StrokeRect:
+    case ItemType::StrokeLine:
+#if ENABLE(INLINE_PATH_DATA)
+    case ItemType::StrokeArc:
+    case ItemType::StrokeQuadCurve:
+    case ItemType::StrokeBezierCurve:
+#endif
+    case ItemType::StrokePath:
+    case ItemType::StrokeEllipse:
+    case ItemType::ClearRect:
+#if USE(CG)
+    case ItemType::ApplyStrokePattern:
+    case ItemType::ApplyFillPattern:
+#endif
+    case ItemType::ApplyDeviceScaleFactor:
+        return false;
+    }
+    ASSERT_NOT_REACHED();
+    return false;
+}
+
+struct GlyphDisplayListCacheKey {
+    const TextRun& textRun;
+    const FontCascade& font;
+    GraphicsContext& context;
+};
+
+static void add(Hasher& hasher, const GlyphDisplayListCacheKey& key)
+{
+    add(hasher, key.textRun, key.context.scaleFactor().width(), key.context.scaleFactor().height(), key.font.generation(), key.context.shouldSubpixelQuantizeFonts());
+}
+
+struct GlyphDisplayListCacheKeyTranslator {
+    static unsigned hash(const GlyphDisplayListCacheKey& key)
+    {
+        return computeHash(key);
+    }
+
+    static bool equal(GlyphDisplayListCacheEntry* entry, const GlyphDisplayListCacheKey& key)
+    {
+        return entry->m_textRun == key.textRun
+            && entry->m_scaleFactor == key.context.scaleFactor()
+            && entry->m_fontCascadeGeneration == key.font.generation()
+            && entry->m_shouldSubpixelQuantizeFont == key.context.shouldSubpixelQuantizeFonts();
+    }
+};
+
+GlyphDisplayListCache& GlyphDisplayListCache::singleton()
+{
+    static NeverDestroyed<GlyphDisplayListCache> cache;
+    return cache;
+}
+
+void GlyphDisplayListCache::clear()
+{
+    m_entriesForLayoutRun.clear();
+    m_entries.clear();
+}
+
+unsigned GlyphDisplayListCache::size() const
+{
+    return m_entries.size();
+}
+
+size_t GlyphDisplayListCache::sizeInBytes() const
+{
+    size_t sizeInBytes = 0;
+    for (auto entry : m_entries)
+        sizeInBytes += entry->displayList().sizeInBytes();
+    return sizeInBytes;
+}
+
+DisplayList::DisplayList* GlyphDisplayListCache::get(const void* run, const FontCascade& font, GraphicsContext& context, const TextRun& textRun)
+{
+    if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
+        if (!m_entries.isEmpty()) {
+            LOG(MemoryPressure, "GlyphDisplayListCache::%s - Under memory pressure - size: %d - sizeInBytes: %ld", __FUNCTION__, size(), sizeInBytes());
+            clear();
+        }
+        return nullptr;
+    }
+
+    if (font.isLoadingCustomFonts() || !font.fonts())
+        return nullptr;
+
+    if (auto entry = m_entriesForLayoutRun.get(run))
+        return &entry->displayList();
+
+    if (auto entry = m_entries.find<GlyphDisplayListCacheKeyTranslator>(GlyphDisplayListCacheKey { textRun, font, context }); entry != m_entries.end())
+        return &m_entriesForLayoutRun.add(run, Ref { **entry }).iterator->value->displayList();
+
+    if (auto displayList = font.displayListForTextRun(context, textRun)) {
+        auto entry = GlyphDisplayListCacheEntry::create(WTFMove(displayList), textRun, font, context);
+        if (canShareDisplayList(entry->displayList()))
+            m_entries.add(entry.ptr());
+        return &m_entriesForLayoutRun.add(run, WTFMove(entry)).iterator->value->displayList();
+    }
+
+    return nullptr;
+}
+
+DisplayList::DisplayList* GlyphDisplayListCache::getIfExists(const void* run)
+{
+    if (auto entry = m_entriesForLayoutRun.get(run))
+        return &entry->displayList();
+    return nullptr;
+}
+
+void GlyphDisplayListCache::remove(const void* run)
+{
+    m_entriesForLayoutRun.remove(run);
+}
+
+bool GlyphDisplayListCache::canShareDisplayList(const DisplayList::InMemoryDisplayList& displayList)
+{
+    for (auto displayListItem : displayList) {
+        if (!canShareDisplayListWithItem(displayListItem.value().item.type()))
+            return false;
+    }
+    return true;
+}
+
+GlyphDisplayListCacheEntry::~GlyphDisplayListCacheEntry()
+{
+    GlyphDisplayListCache::singleton().m_entries.remove(this);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/rendering/GlyphDisplayListCache.h b/Source/WebCore/rendering/GlyphDisplayListCache.h
index 6f22646..1f32980 100644
--- a/Source/WebCore/rendering/GlyphDisplayListCache.h
+++ b/Source/WebCore/rendering/GlyphDisplayListCache.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2018-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,10 +25,13 @@
 
 #pragma once
 
+#include "DisplayList.h"
+#include "FloatSizeHash.h"
 #include "FontCascade.h"
 #include "InMemoryDisplayList.h"
 #include "Logging.h"
 #include "TextRun.h"
+#include "TextRunHash.h"
 #include <wtf/HashMap.h>
 #include <wtf/MemoryPressureHandler.h>
 #include <wtf/NeverDestroyed.h>
@@ -41,68 +44,94 @@
 struct Box;
 }
 
-template<typename LayoutRun>
+class GlyphDisplayListCacheEntry : public RefCounted<GlyphDisplayListCacheEntry>, public CanMakeWeakPtr<GlyphDisplayListCacheEntry> {
+    WTF_MAKE_FAST_ALLOCATED;
+    friend struct GlyphDisplayListCacheKeyTranslator;
+    friend void add(Hasher&, const GlyphDisplayListCacheEntry&);
+public:
+    static Ref<GlyphDisplayListCacheEntry> create(std::unique_ptr<DisplayList::InMemoryDisplayList>&& displayList, const TextRun& textRun, const FontCascade& font, GraphicsContext& context)
+    {
+        return adoptRef(*new GlyphDisplayListCacheEntry(WTFMove(displayList), textRun, font, context));
+    }
+
+    ~GlyphDisplayListCacheEntry();
+
+    bool operator==(const GlyphDisplayListCacheEntry& other) const
+    {
+        return m_textRun == other.m_textRun
+            && m_scaleFactor == other.m_scaleFactor
+            && m_fontCascadeGeneration == other.m_fontCascadeGeneration
+            && m_shouldSubpixelQuantizeFont == other.m_shouldSubpixelQuantizeFont;
+    }
+
+    DisplayList::InMemoryDisplayList& displayList() { return *m_displayList.get(); }
+
+private:
+    GlyphDisplayListCacheEntry(std::unique_ptr<DisplayList::InMemoryDisplayList>&& displayList, const TextRun& textRun, const FontCascade& font, GraphicsContext& context)
+        : m_displayList(WTFMove(displayList))
+        , m_textRun(textRun.isolatedCopy())
+        , m_scaleFactor(context.scaleFactor())
+        , m_fontCascadeGeneration(font.generation())
+        , m_shouldSubpixelQuantizeFont(context.shouldSubpixelQuantizeFonts())
+    {
+        ASSERT(m_displayList.get());
+    }
+
+    std::unique_ptr<DisplayList::InMemoryDisplayList> m_displayList;
+
+    TextRun m_textRun;
+    FloatSize m_scaleFactor;
+    unsigned m_fontCascadeGeneration;
+    bool m_shouldSubpixelQuantizeFont;
+};
+
+inline void add(Hasher& hasher, const GlyphDisplayListCacheEntry& entry)
+{
+    add(hasher, entry.m_textRun, entry.m_scaleFactor.width(), entry.m_scaleFactor.height(), entry.m_fontCascadeGeneration, entry.m_shouldSubpixelQuantizeFont);
+}
+
+struct GlyphDisplayListCacheEntryHash {
+    static unsigned hash(GlyphDisplayListCacheEntry* entry) { return computeHash(*entry); }
+    static bool equal(GlyphDisplayListCacheEntry* a, GlyphDisplayListCacheEntry* b) { return *a == *b; }
+    static constexpr bool safeToCompareToEmptyOrDeleted = false;
+};
+
 class GlyphDisplayListCache {
+    WTF_MAKE_FAST_ALLOCATED;
+    friend class GlyphDisplayListCacheEntry;
 public:
     GlyphDisplayListCache() = default;
 
-    static GlyphDisplayListCache& singleton()
-    {
-        static_assert(std::is_same_v<LayoutRun, LegacyInlineTextBox> || std::is_same_v<LayoutRun, InlineDisplay::Box>);
-        static NeverDestroyed<GlyphDisplayListCache> cache;
-        return cache;
-    }
+    static GlyphDisplayListCache& singleton();
 
-    DisplayList::DisplayList* get(const LayoutRun& run, const FontCascade& font, GraphicsContext& context, const TextRun& textRun)
-    {
-        if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
-            if (!m_glyphRunMap.isEmpty()) {
-                LOG(MemoryPressure, "GlyphDisplayListCache::%s - Under memory pressure - size: %d - sizeInBytes: %ld", __FUNCTION__, size(), sizeInBytes());
-                clear();
-            }
-            return nullptr;
-        }
+    DisplayList::DisplayList* get(const LegacyInlineTextBox& run, const FontCascade& font, GraphicsContext& context, const TextRun& textRun) { return get(&run, font, context, textRun); }
+    DisplayList::DisplayList* get(const InlineDisplay::Box& run, const FontCascade& font, GraphicsContext& context, const TextRun& textRun) { return get(&run, font, context, textRun); }
 
-        if (auto displayList = m_glyphRunMap.get(&run))
-            return displayList;
+    DisplayList::DisplayList* getIfExists(const LegacyInlineTextBox& run) { return getIfExists(&run); }
+    DisplayList::DisplayList* getIfExists(const InlineDisplay::Box& run) { return getIfExists(&run); }
 
-        if (auto displayList = font.displayListForTextRun(context, textRun))
-            return m_glyphRunMap.add(&run, WTFMove(displayList)).iterator->value.get();
+    void remove(const LegacyInlineTextBox& run) { remove(&run); }
+    void remove(const InlineDisplay::Box& run) { remove(&run); }
 
-        return nullptr;
-    }
+    void clear();
+    unsigned size() const;
+    size_t sizeInBytes() const;
 
-    DisplayList::DisplayList* getIfExists(const LayoutRun& run)
-    {
-        return m_glyphRunMap.get(&run);
-    }
-
-    void remove(const LayoutRun& run)
-    {
-        m_glyphRunMap.remove(&run);
-    }
-
-    void clear()
-    {
-        m_glyphRunMap.clear();
-    }
-
-    unsigned size() const
-    {
-        return m_glyphRunMap.size();
-    }
-    
-    size_t sizeInBytes() const
-    {
-        size_t sizeInBytes = 0;
-        for (const auto& entry : m_glyphRunMap)
-            sizeInBytes += entry.value->sizeInBytes();
-        return sizeInBytes;
-    }
-    
 private:
-    using GlyphRunMap = HashMap<const LayoutRun*, std::unique_ptr<DisplayList::InMemoryDisplayList>>;
-    GlyphRunMap m_glyphRunMap;
+    static bool canShareDisplayList(const DisplayList::InMemoryDisplayList&);
+
+    DisplayList::DisplayList* get(const void* run, const FontCascade&, GraphicsContext&, const TextRun&);
+    DisplayList::DisplayList* getIfExists(const void* run);
+    void remove(const void* run);
+
+    HashMap<const void*, Ref<GlyphDisplayListCacheEntry>> m_entriesForLayoutRun;
+    HashSet<GlyphDisplayListCacheEntry*> m_entries;
 };
-    
-}
+
+} // namespace WebCore
+
+namespace WTF {
+
+template<> struct DefaultHash<WebCore::GlyphDisplayListCacheEntry*> : WebCore::GlyphDisplayListCacheEntryHash { };
+
+} // namespace WTF
diff --git a/Source/WebCore/rendering/RenderLayerCompositor.cpp b/Source/WebCore/rendering/RenderLayerCompositor.cpp
index 23da9e5..b75936a 100644
--- a/Source/WebCore/rendering/RenderLayerCompositor.cpp
+++ b/Source/WebCore/rendering/RenderLayerCompositor.cpp
@@ -85,6 +85,10 @@
 #include "RenderTreeAsText.h"
 #endif
 
+#if ENABLE(MODEL_ELEMENT)
+#include "RenderModel.h"
+#endif
+
 #if ENABLE(3D_TRANSFORMS)
 // This symbol is used to determine from a script whether 3D rendering is enabled (via 'nm').
 WEBCORE_EXPORT bool WebCoreHas3DRendering = true;
diff --git a/Source/WebCore/rendering/TextPainter.cpp b/Source/WebCore/rendering/TextPainter.cpp
index 4f3342f..a052c6f 100644
--- a/Source/WebCore/rendering/TextPainter.cpp
+++ b/Source/WebCore/rendering/TextPainter.cpp
@@ -1,7 +1,7 @@
 /*
  * (C) 1999 Lars Knoll (knoll@kde.org)
  * (C) 2000 Dirk Mueller (mueller@kde.org)
- * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2004-2022 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -23,6 +23,7 @@
 #include "config.h"
 #include "TextPainter.h"
 
+#include "DisplayListRecorderImpl.h"
 #include "DisplayListReplayer.h"
 #include "FilterOperations.h"
 #include "GraphicsContext.h"
@@ -218,15 +219,6 @@
     paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, start, end, m_style, m_shadow, m_shadowColorFilter);
 }
 
-void TextPainter::clearGlyphDisplayLists()
-{
-    GlyphDisplayListCache<LegacyInlineTextBox>::singleton().clear();
-#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
-    if (RuntimeEnabledFeatures::sharedFeatures().inlineFormattingContextIntegrationEnabled())
-        GlyphDisplayListCache<InlineDisplay::Box>::singleton().clear();
-#endif
-}
-
 static bool forceUseGlyphDisplayListForTesting = false;
 
 bool TextPainter::shouldUseGlyphDisplayList(const PaintInfo& paintInfo)
diff --git a/Source/WebCore/rendering/TextPainter.h b/Source/WebCore/rendering/TextPainter.h
index bf7b301..daa4dc4 100644
--- a/Source/WebCore/rendering/TextPainter.h
+++ b/Source/WebCore/rendering/TextPainter.h
@@ -66,13 +66,15 @@
         if (!TextPainter::shouldUseGlyphDisplayList(paintInfo))
             TextPainter::removeGlyphDisplayList(run);
         else
-            m_glyphDisplayList = GlyphDisplayListCache<LayoutRun>::singleton().get(run, m_font, m_context, textRun);
+            m_glyphDisplayList = GlyphDisplayListCache::singleton().get(run, m_font, m_context, textRun);
     }
 
     template<typename LayoutRun>
-    static void removeGlyphDisplayList(const LayoutRun& run) { GlyphDisplayListCache<LayoutRun>::singleton().remove(run); }
+    static void removeGlyphDisplayList(const LayoutRun& run)
+    {
+        GlyphDisplayListCache::singleton().remove(run);
+    }
 
-    static void clearGlyphDisplayLists();
     static bool shouldUseGlyphDisplayList(const PaintInfo&);
     WEBCORE_EXPORT static void setForceUseGlyphDisplayListForTesting(bool);
     WEBCORE_EXPORT static String cachedGlyphDisplayListsForTextNodeAsText(Text&, OptionSet<DisplayList::AsTextFlag>);
@@ -81,7 +83,7 @@
     template<typename LayoutRun>
     static DisplayList::DisplayList* glyphDisplayListIfExists(const LayoutRun& run)
     {
-        return GlyphDisplayListCache<LayoutRun>::singleton().getIfExists(run);
+        return GlyphDisplayListCache::singleton().getIfExists(run);
     }
 
     void paintTextOrEmphasisMarks(const FontCascade&, const TextRun&, const AtomString& emphasisMark, float emphasisMarkOffset,
diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp
index c0a410f..6dc70e9 100644
--- a/Source/WebCore/testing/Internals.cpp
+++ b/Source/WebCore/testing/Internals.cpp
@@ -3205,6 +3205,16 @@
     return { };
 }
 
+static OptionSet<DisplayList::AsTextFlag> toDisplayListFlags(unsigned short flags)
+{
+    OptionSet<DisplayList::AsTextFlag> displayListFlags;
+    if (flags & Internals::DISPLAY_LIST_INCLUDE_PLATFORM_OPERATIONS)
+        displayListFlags.add(DisplayList::AsTextFlag::IncludePlatformOperations);
+    if (flags & Internals::DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS)
+        displayListFlags.add(DisplayList::AsTextFlag::IncludeResourceIdentifiers);
+    return displayListFlags;
+}
+
 ExceptionOr<String> Internals::displayListForElement(Element& element, unsigned short flags)
 {
     Document* document = contextDocument();
@@ -3216,10 +3226,6 @@
     if (!element.renderer())
         return Exception { InvalidAccessError };
 
-    OptionSet<DisplayList::AsTextFlag> displayListFlags;
-    if (flags & DISPLAY_LIST_INCLUDE_PLATFORM_OPERATIONS)
-        displayListFlags.add(DisplayList::AsTextFlag::IncludePlatformOperations);
-
     if (!element.renderer()->hasLayer())
         return Exception { InvalidAccessError };
 
@@ -3227,7 +3233,7 @@
     if (!layer->isComposited())
         return Exception { InvalidAccessError };
 
-    return layer->backing()->displayListAsText(displayListFlags);
+    return layer->backing()->displayListAsText(toDisplayListFlags(flags));
 }
 
 ExceptionOr<String> Internals::replayDisplayListForElement(Element& element, unsigned short flags)
@@ -3241,10 +3247,6 @@
     if (!element.renderer())
         return Exception { InvalidAccessError };
 
-    OptionSet<DisplayList::AsTextFlag> displayListFlags;
-    if (flags & DISPLAY_LIST_INCLUDE_PLATFORM_OPERATIONS)
-        displayListFlags.add(DisplayList::AsTextFlag::IncludePlatformOperations);
-
     if (!element.renderer()->hasLayer())
         return Exception { InvalidAccessError };
 
@@ -3252,7 +3254,7 @@
     if (!layer->isComposited())
         return Exception { InvalidAccessError };
 
-    return layer->backing()->replayDisplayListAsText(displayListFlags);
+    return layer->backing()->replayDisplayListAsText(toDisplayListFlags(flags));
 }
 
 void Internals::setForceUseGlyphDisplayListForTesting(bool enabled)
@@ -3274,11 +3276,7 @@
     if (!node.renderer())
         return Exception { InvalidAccessError };
 
-    OptionSet<DisplayList::AsTextFlag> displayListFlags;
-    if (flags & DISPLAY_LIST_INCLUDE_PLATFORM_OPERATIONS)
-        displayListFlags.add(DisplayList::AsTextFlag::IncludePlatformOperations);
-
-    return TextPainter::cachedGlyphDisplayListsForTextNodeAsText(downcast<Text>(node), displayListFlags);
+    return TextPainter::cachedGlyphDisplayListsForTextNodeAsText(downcast<Text>(node), toDisplayListFlags(flags));
 }
 
 ExceptionOr<void> Internals::garbageCollectDocumentResources() const
diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h
index 6c00e6c..aa05ec3 100644
--- a/Source/WebCore/testing/Internals.h
+++ b/Source/WebCore/testing/Internals.h
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2012 Google Inc. All rights reserved.
- * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2022 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -477,6 +477,7 @@
     enum {
         // Values need to be kept in sync with Internals.idl.
         DISPLAY_LIST_INCLUDE_PLATFORM_OPERATIONS = 1,
+        DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS = 2,
     };
     ExceptionOr<String> displayListForElement(Element&, unsigned short flags);
     ExceptionOr<String> replayDisplayListForElement(Element&, unsigned short flags);
diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl
index 1282b1b..b45d6fe 100644
--- a/Source/WebCore/testing/Internals.idl
+++ b/Source/WebCore/testing/Internals.idl
@@ -610,6 +610,7 @@
 
     // Flags for displayListForElement.
     const unsigned short DISPLAY_LIST_INCLUDE_PLATFORM_OPERATIONS = 1;
+    const unsigned short DISPLAY_LIST_INCLUDE_RESOURCE_IDENTIFIERS = 2;
     // Returns the recorded display list.
     DOMString displayListForElement(Element element, optional unsigned short flags = 0);
     // Returns the display list that was actually painted.
diff --git a/Source/WebKit/GPUProcess/graphics/QualifiedResourceHeap.h b/Source/WebKit/GPUProcess/graphics/QualifiedResourceHeap.h
index 5f51c45..a82f3c1 100644
--- a/Source/WebKit/GPUProcess/graphics/QualifiedResourceHeap.h
+++ b/Source/WebKit/GPUProcess/graphics/QualifiedResourceHeap.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Apple Inc.  All rights reserved.
+ * Copyright (C) 2021-2022 Apple Inc.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -59,6 +59,11 @@
         add(renderingResourceIdentifier, WTFMove(font), m_fontCount);
     }
 
+    void add(QualifiedRenderingResourceIdentifier renderingResourceIdentifier, Ref<WebCore::DecomposedGlyphs>&& decomposedGlyphs)
+    {
+        add(renderingResourceIdentifier, WTFMove(decomposedGlyphs), m_decomposedGlyphsCount);
+    }
+
     WebCore::ImageBuffer* getImageBuffer(WebCore::RenderingResourceIdentifier renderingResourceIdentifier) const final
     {
         return get<WebCore::ImageBuffer>({ renderingResourceIdentifier, m_webProcessIdentifier });
@@ -79,6 +84,11 @@
         return get<WebCore::Font>({ renderingResourceIdentifier, m_webProcessIdentifier });
     }
 
+    WebCore::DecomposedGlyphs* getDecomposedGlyphs(WebCore::RenderingResourceIdentifier renderingResourceIdentifier) const final
+    {
+        return get<WebCore::DecomposedGlyphs>({ renderingResourceIdentifier, m_webProcessIdentifier });
+    }
+
     WebCore::ImageBuffer* getImageBuffer(QualifiedRenderingResourceIdentifier renderingResourceIdentifier) const
     {
         return get<WebCore::ImageBuffer>(renderingResourceIdentifier);
@@ -108,6 +118,11 @@
         return get<WebCore::Font>(renderingResourceIdentifier);
     }
 
+    WebCore::DecomposedGlyphs* getDecomposedGlyphs(QualifiedRenderingResourceIdentifier renderingResourceIdentifier) const
+    {
+        return get<WebCore::DecomposedGlyphs>(renderingResourceIdentifier);
+    }
+
     bool removeImageBuffer(QualifiedRenderingResourceIdentifier renderingResourceIdentifier)
     {
         return remove<WebCore::ImageBuffer>(renderingResourceIdentifier, m_imageBufferCount);
@@ -123,6 +138,11 @@
         return remove<WebCore::Font>(renderingResourceIdentifier, m_fontCount);
     }
 
+    bool removeDecomposedGlyphs(QualifiedRenderingResourceIdentifier renderingResourceIdentifier)
+    {
+        return remove<WebCore::DecomposedGlyphs>(renderingResourceIdentifier, m_decomposedGlyphsCount);
+    }
+
     void deleteAllFonts()
     {
         checkInvariants();
@@ -193,6 +213,7 @@
         unsigned imageBufferCount = 0;
         unsigned nativeImageCount = 0;
         unsigned fontCount = 0;
+        unsigned decomposedGlyphsCount = 0;
         for (const auto& pair : m_resources) {
             WTF::switchOn(pair.value, [&] (std::monostate) {
                 ASSERT_NOT_REACHED();
@@ -202,22 +223,25 @@
                 ++nativeImageCount;
             }, [&] (const Ref<WebCore::Font>&) {
                 ++fontCount;
+            }, [&] (const Ref<WebCore::DecomposedGlyphs>&) {
+                ++decomposedGlyphsCount;
             });
         }
         ASSERT(imageBufferCount == m_imageBufferCount);
         ASSERT(nativeImageCount == m_nativeImageCount);
         ASSERT(fontCount == m_fontCount);
-        ASSERT(m_resources.size() == m_imageBufferCount + m_nativeImageCount + m_fontCount);
+        ASSERT(decomposedGlyphsCount == m_decomposedGlyphsCount);
+        ASSERT(m_resources.size() == m_imageBufferCount + m_nativeImageCount + m_fontCount + m_decomposedGlyphsCount);
 #endif
     }
 
-    using Resource = std::variant<std::monostate, Ref<WebCore::ImageBuffer>, Ref<WebCore::NativeImage>, Ref<WebCore::Font>>;
+    using Resource = std::variant<std::monostate, Ref<WebCore::ImageBuffer>, Ref<WebCore::NativeImage>, Ref<WebCore::Font>, Ref<WebCore::DecomposedGlyphs>>;
     HashMap<QualifiedRenderingResourceIdentifier, Resource> m_resources;
     WebCore::ProcessIdentifier m_webProcessIdentifier;
     unsigned m_imageBufferCount { 0 };
     unsigned m_nativeImageCount { 0 };
     unsigned m_fontCount { 0 };
-
+    unsigned m_decomposedGlyphsCount { 0 };
 };
 
 } // namespace WebKit
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.cpp b/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.cpp
index c7c95b9..a303a5d 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.cpp
+++ b/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.cpp
@@ -257,6 +257,30 @@
     handleItem(WTFMove(item), *font);
 }
 
+void RemoteDisplayListRecorder::drawDecomposedGlyphs(RenderingResourceIdentifier fontIdentifier, RenderingResourceIdentifier decomposedGlyphsIdentifier, const FloatRect& bounds)
+{
+    // Immediately turn the RenderingResourceIdentifier (which is error-prone) to a QualifiedRenderingResourceIdentifier,
+    // and use a helper function to make sure that don't accidentally use the RenderingResourceIdentifier (because the helper function can't see it).
+    drawDecomposedGlyphsWithQualifiedIdentifiers({ fontIdentifier, m_webProcessIdentifier }, { decomposedGlyphsIdentifier, m_webProcessIdentifier }, bounds);
+}
+
+void RemoteDisplayListRecorder::drawDecomposedGlyphsWithQualifiedIdentifiers(QualifiedRenderingResourceIdentifier fontIdentifier, QualifiedRenderingResourceIdentifier decomposedGlyphsIdentifier, const FloatRect& bounds)
+{
+    RefPtr font = resourceCache().cachedFont(fontIdentifier);
+    if (!font) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    RefPtr decomposedGlyphs = resourceCache().cachedDecomposedGlyphs(decomposedGlyphsIdentifier);
+    if (!decomposedGlyphsIdentifier) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    handleItem(DisplayList::DrawDecomposedGlyphs(fontIdentifier.object(), decomposedGlyphsIdentifier.object(), bounds), *font, *decomposedGlyphs);
+}
+
 void RemoteDisplayListRecorder::drawImageBuffer(RenderingResourceIdentifier imageBufferIdentifier, const FloatRect& destinationRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
 {
     // Immediately turn the RenderingResourceIdentifier (which is error-prone) to a QualifiedRenderingResourceIdentifier,
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.h b/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.h
index 3c47249..59798fb 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.h
+++ b/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.h
@@ -80,6 +80,7 @@
     void clipOutToPath(const WebCore::Path&);
     void clipPath(const WebCore::Path&, WebCore::WindRule);
     void drawGlyphs(WebCore::DisplayList::DrawGlyphs&&);
+    void drawDecomposedGlyphs(WebCore::RenderingResourceIdentifier fontIdentifier, WebCore::RenderingResourceIdentifier decomposedGlyphsIdentifier, const WebCore::FloatRect& bounds);
     void drawFilteredImageBuffer(std::optional<WebCore::RenderingResourceIdentifier> sourceImageIdentifier, const WebCore::FloatRect& sourceImageRect, IPC::FilterReference);
     void drawImageBuffer(WebCore::RenderingResourceIdentifier imageBufferIdentifier, const WebCore::FloatRect& destinationRect, const WebCore::FloatRect& srcRect, const WebCore::ImagePaintingOptions&);
     void drawNativeImage(WebCore::RenderingResourceIdentifier imageIdentifier, const WebCore::FloatSize& imageSize, const WebCore::FloatRect& destRect, const WebCore::FloatRect& srcRect, const WebCore::ImagePaintingOptions&);
@@ -135,6 +136,7 @@
     void setStateWithQualifiedIdentifiers(WebCore::DisplayList::SetState&&, QualifiedRenderingResourceIdentifier strokePatternImageIdentifier, QualifiedRenderingResourceIdentifier fillPatternImageIdentifier);
     void clipToImageBufferWithQualifiedIdentifier(QualifiedRenderingResourceIdentifier, const WebCore::FloatRect& destinationRect);
     void drawGlyphsWithQualifiedIdentifier(WebCore::DisplayList::DrawGlyphs&&, QualifiedRenderingResourceIdentifier fontIdentifier);
+    void drawDecomposedGlyphsWithQualifiedIdentifiers(QualifiedRenderingResourceIdentifier fontIdentifier, QualifiedRenderingResourceIdentifier decomposedGlyphsIdentifier, const WebCore::FloatRect& bounds);
     void drawImageBufferWithQualifiedIdentifier(QualifiedRenderingResourceIdentifier imageBufferIdentifier, const WebCore::FloatRect& destinationRect, const WebCore::FloatRect& srcRect, const WebCore::ImagePaintingOptions&);
     void drawNativeImageWithQualifiedIdentifier(QualifiedRenderingResourceIdentifier imageIdentifier, const WebCore::FloatSize& imageSize, const WebCore::FloatRect& destRect, const WebCore::FloatRect& srcRect, const WebCore::ImagePaintingOptions&);
     void drawPatternWithQualifiedIdentifier(QualifiedRenderingResourceIdentifier imageIdentifier, const WebCore::FloatRect& destRect, const WebCore::FloatRect& tileRect, const WebCore::AffineTransform&, const WebCore::FloatPoint&, const WebCore::FloatSize& spacing, const WebCore::ImagePaintingOptions&);
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.messages.in b/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.messages.in
index 2802ffe..6e20da4 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.messages.in
+++ b/Source/WebKit/GPUProcess/graphics/RemoteDisplayListRecorder.messages.in
@@ -45,6 +45,7 @@
     ClipOutToPath(WebCore::Path path)
     ClipPath(WebCore::Path path, enum:uint8_t WebCore::WindRule windRule)
     DrawGlyphs(WebCore::DisplayList::DrawGlyphs item)
+    DrawDecomposedGlyphs(WebCore::RenderingResourceIdentifier fontIdentifier, WebCore::RenderingResourceIdentifier decomposedGlyphsIdentifier, WebCore::FloatRect bounds)
     DrawFilteredImageBuffer(std::optional<WebCore::RenderingResourceIdentifier> sourceImageIdentifier, WebCore::FloatRect sourceImageRect, IPC::FilterReference filter)
     DrawImageBuffer(WebCore::RenderingResourceIdentifier imageBufferIdentifier, WebCore::FloatRect destinationRect, WebCore::FloatRect srcRect, struct WebCore::ImagePaintingOptions options)
     DrawNativeImage(WebCore::RenderingResourceIdentifier imageIdentifier, WebCore::FloatSize imageSize, WebCore::FloatRect destRect, WebCore::FloatRect srcRect, struct WebCore::ImagePaintingOptions options)
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp b/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp
index 5244311..bf4f873 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp
+++ b/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp
@@ -369,10 +369,23 @@
 void RemoteRenderingBackend::cacheFontWithQualifiedIdentifier(Ref<Font>&& font, QualifiedRenderingResourceIdentifier fontResourceIdentifier)
 {
     ASSERT(!RunLoop::isMain());
-
     m_remoteResourceCache.cacheFont(WTFMove(font), fontResourceIdentifier);
 }
 
+void RemoteRenderingBackend::cacheDecomposedGlyphs(Ref<DecomposedGlyphs>&& decomposedGlyphs)
+{
+    // Immediately turn the RenderingResourceIdentifier (which is error-prone) to a QualifiedRenderingResourceIdentifier,
+    // and use a helper function to make sure that don't accidentally use the RenderingResourceIdentifier (because the helper function can't see it).
+    auto renderingResourceIdentifier = decomposedGlyphs->renderingResourceIdentifier();
+    cacheDecomposedGlyphsWithQualifiedIdentifier(WTFMove(decomposedGlyphs), { renderingResourceIdentifier, m_gpuConnectionToWebProcess->webProcessIdentifier() });
+}
+
+void RemoteRenderingBackend::cacheDecomposedGlyphsWithQualifiedIdentifier(Ref<DecomposedGlyphs>&& decomposedGlyphs, QualifiedRenderingResourceIdentifier decomposedGlyphsIdentifier)
+{
+    ASSERT(!RunLoop::isMain());
+    m_remoteResourceCache.cacheDecomposedGlyphs(WTFMove(decomposedGlyphs), decomposedGlyphsIdentifier);
+}
+
 void RemoteRenderingBackend::deleteAllFonts()
 {
     ASSERT(!RunLoop::isMain());
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.h b/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.h
index 70d85a4..54e081a 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.h
+++ b/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.h
@@ -119,6 +119,7 @@
     void getShareableBitmapForImageBuffer(WebCore::RenderingResourceIdentifier, WebCore::PreserveResolution, CompletionHandler<void(ShareableBitmap::Handle&&)>&&);
     void getFilteredImageForImageBuffer(WebCore::RenderingResourceIdentifier, IPC::FilterReference&&, CompletionHandler<void(ShareableBitmap::Handle&&)>&&);
     void cacheNativeImage(const ShareableBitmap::Handle&, WebCore::RenderingResourceIdentifier);
+    void cacheDecomposedGlyphs(Ref<WebCore::DecomposedGlyphs>&&);
     void cacheFont(Ref<WebCore::Font>&&);
     void deleteAllFonts();
     void releaseRemoteResource(WebCore::RenderingResourceIdentifier);
@@ -132,6 +133,7 @@
     void getDataForImageBufferWithQualifiedIdentifier(const String& mimeType, std::optional<double> quality, QualifiedRenderingResourceIdentifier, CompletionHandler<void(Vector<uint8_t>&&)>&&);
     void getShareableBitmapForImageBufferWithQualifiedIdentifier(QualifiedRenderingResourceIdentifier, WebCore::PreserveResolution, CompletionHandler<void(ShareableBitmap::Handle&&)>&&);
     void cacheNativeImageWithQualifiedIdentifier(const ShareableBitmap::Handle&, QualifiedRenderingResourceIdentifier);
+    void cacheDecomposedGlyphsWithQualifiedIdentifier(Ref<WebCore::DecomposedGlyphs>&&, QualifiedRenderingResourceIdentifier);
     void releaseRemoteResourceWithQualifiedIdentifier(QualifiedRenderingResourceIdentifier);
     void cacheFontWithQualifiedIdentifier(Ref<WebCore::Font>&&, QualifiedRenderingResourceIdentifier);
 
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.messages.in b/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.messages.in
index 717afa8..2d83ea5 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.messages.in
+++ b/Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.messages.in
@@ -34,6 +34,7 @@
     GetFilteredImageForImageBuffer(WebCore::RenderingResourceIdentifier imageBuffer, IPC::FilterReference filter) -> (WebKit::ShareableBitmap::Handle handle) Synchronous NotStreamEncodableReply
     CacheNativeImage(WebKit::ShareableBitmap::Handle handle, WebCore::RenderingResourceIdentifier renderingResourceIdentifier) NotStreamEncodable
     CacheFont(IPC::FontReference font) NotStreamEncodable
+    CacheDecomposedGlyphs(Ref<WebCore::DecomposedGlyphs> decomposedGlyphs) NotStreamEncodable
     DeleteAllFonts()
     ReleaseRemoteResource(WebCore::RenderingResourceIdentifier renderingResourceIdentifier)
 
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.cpp b/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.cpp
index 01f3d13..f9d0f06 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.cpp
+++ b/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.cpp
@@ -53,6 +53,12 @@
     m_resourceHeap.add(renderingResourceIdentifier, WTFMove(image));
 }
 
+void RemoteResourceCache::cacheDecomposedGlyphs(Ref<DecomposedGlyphs>&& decomposedGlyphs, QualifiedRenderingResourceIdentifier renderingResourceIdentifier)
+{
+    ASSERT(renderingResourceIdentifier.object() == decomposedGlyphs->renderingResourceIdentifier());
+    m_resourceHeap.add(renderingResourceIdentifier, WTFMove(decomposedGlyphs));
+}
+
 NativeImage* RemoteResourceCache::cachedNativeImage(QualifiedRenderingResourceIdentifier renderingResourceIdentifier) const
 {
     return m_resourceHeap.getNativeImage(renderingResourceIdentifier);
@@ -74,6 +80,11 @@
     return m_resourceHeap.getFont(renderingResourceIdentifier);
 }
 
+DecomposedGlyphs* RemoteResourceCache::cachedDecomposedGlyphs(QualifiedRenderingResourceIdentifier renderingResourceIdentifier) const
+{
+    return m_resourceHeap.getDecomposedGlyphs(renderingResourceIdentifier);
+}
+
 void RemoteResourceCache::deleteAllFonts()
 {
     m_resourceHeap.deleteAllFonts();
@@ -83,7 +94,8 @@
 {
     if (m_resourceHeap.removeImageBuffer(renderingResourceIdentifier)
         || m_resourceHeap.removeNativeImage(renderingResourceIdentifier)
-        || m_resourceHeap.removeFont(renderingResourceIdentifier))
+        || m_resourceHeap.removeFont(renderingResourceIdentifier)
+        || m_resourceHeap.removeDecomposedGlyphs(renderingResourceIdentifier))
         return true;
 
     // Caching the remote resource should have happened before releasing it.
diff --git a/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.h b/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.h
index f8bd484..b07bda3 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.h
+++ b/Source/WebKit/GPUProcess/graphics/RemoteResourceCache.h
@@ -40,12 +40,17 @@
     RemoteResourceCache(WebCore::ProcessIdentifier webProcessIdentifier);
 
     void cacheImageBuffer(Ref<WebCore::ImageBuffer>&&, QualifiedRenderingResourceIdentifier);
-    WebCore::ImageBuffer* cachedImageBuffer(QualifiedRenderingResourceIdentifier) const;
     void cacheNativeImage(Ref<WebCore::NativeImage>&&, QualifiedRenderingResourceIdentifier);
-    WebCore::NativeImage* cachedNativeImage(QualifiedRenderingResourceIdentifier) const;
-    std::optional<WebCore::SourceImage> cachedSourceImage(QualifiedRenderingResourceIdentifier) const;
     void cacheFont(Ref<WebCore::Font>&&, QualifiedRenderingResourceIdentifier);
+    void cacheDecomposedGlyphs(Ref<WebCore::DecomposedGlyphs>&&, QualifiedRenderingResourceIdentifier);
+
+    WebCore::ImageBuffer* cachedImageBuffer(QualifiedRenderingResourceIdentifier) const;
+    WebCore::NativeImage* cachedNativeImage(QualifiedRenderingResourceIdentifier) const;
     WebCore::Font* cachedFont(QualifiedRenderingResourceIdentifier) const;
+    WebCore::DecomposedGlyphs* cachedDecomposedGlyphs(QualifiedRenderingResourceIdentifier) const;
+
+    std::optional<WebCore::SourceImage> cachedSourceImage(QualifiedRenderingResourceIdentifier) const;
+
     void deleteAllFonts();
     bool releaseRemoteResource(QualifiedRenderingResourceIdentifier);
 
diff --git a/Source/WebKit/Scripts/webkit/messages.py b/Source/WebKit/Scripts/webkit/messages.py
index 117d519..3b9dafc 100644
--- a/Source/WebKit/Scripts/webkit/messages.py
+++ b/Source/WebKit/Scripts/webkit/messages.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2021 Apple Inc. All rights reserved.
+# Copyright (C) 2010-2022 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp
index 3d89f35..99ffa8b 100644
--- a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp
+++ b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2011-2022 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,6 +44,7 @@
 #include <WebCore/Cursor.h>
 #include <WebCore/DOMCacheEngine.h>
 #include <WebCore/DatabaseDetails.h>
+#include <WebCore/DecomposedGlyphs.h>
 #include <WebCore/DictationAlternative.h>
 #include <WebCore/DictionaryPopupInfo.h>
 #include <WebCore/DisplayListItems.h>
@@ -1013,6 +1014,33 @@
     return Font::create(platformData.value(), origin.value(), isInterstitial.value(), visibility.value(), isTextOrientationFallback.value(), renderingResourceIdentifier);
 }
 
+void ArgumentCoder<DecomposedGlyphs>::encode(Encoder& encoder, const DecomposedGlyphs& decomposedGlyphs)
+{
+    encoder << decomposedGlyphs.positionedGlyphs();
+    encoder << decomposedGlyphs.bounds();
+    encoder << decomposedGlyphs.renderingResourceIdentifier();
+}
+
+std::optional<Ref<DecomposedGlyphs>> ArgumentCoder<DecomposedGlyphs>::decode(Decoder& decoder)
+{
+    std::optional<PositionedGlyphs> positionedGlyphs;
+    decoder >> positionedGlyphs;
+    if (!positionedGlyphs)
+        return std::nullopt;
+
+    std::optional<FloatRect> bounds;
+    decoder >> bounds;
+    if (!bounds)
+        return std::nullopt;
+
+    std::optional<RenderingResourceIdentifier> renderingResourceIdentifier;
+    decoder >> renderingResourceIdentifier;
+    if (!renderingResourceIdentifier)
+        return std::nullopt;
+
+    return DecomposedGlyphs::create(WTFMove(*positionedGlyphs), *bounds, *renderingResourceIdentifier);
+}
+
 void ArgumentCoder<Cursor>::encode(Encoder& encoder, const Cursor& cursor)
 {
     encoder << cursor.type();
diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.h b/Source/WebKit/Shared/WebCoreArgumentCoders.h
index 79deab3..ede3693 100644
--- a/Source/WebKit/Shared/WebCoreArgumentCoders.h
+++ b/Source/WebKit/Shared/WebCoreArgumentCoders.h
@@ -128,6 +128,7 @@
 class Cursor;
 class DatabaseDetails;
 class DragData;
+class DecomposedGlyphs;
 class File;
 class FilterOperation;
 class FilterOperations;
@@ -405,6 +406,11 @@
     static std::optional<WebCore::FontPlatformData> decodePlatformData(Decoder&);
 };
 
+template<> struct ArgumentCoder<WebCore::DecomposedGlyphs> {
+    static void encode(Encoder&, const WebCore::DecomposedGlyphs&);
+    static std::optional<Ref<WebCore::DecomposedGlyphs>> decode(Decoder&);
+};
+
 template<> struct ArgumentCoder<WebCore::ResourceRequest> {
     static void encode(Encoder&, const WebCore::ResourceRequest&);
     static WARN_UNUSED_RETURN bool decode(Decoder&, WebCore::ResourceRequest&);
diff --git a/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.cpp b/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.cpp
index 14f3686..97cda8a 100644
--- a/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.cpp
+++ b/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.cpp
@@ -45,7 +45,7 @@
 using namespace WebCore;
 
 RemoteDisplayListRecorderProxy::RemoteDisplayListRecorderProxy(ImageBuffer& imageBuffer, RemoteRenderingBackendProxy& renderingBackend, const FloatRect& initialClip, const AffineTransform& initialCTM)
-    : DisplayList::Recorder({ }, initialClip, initialCTM, DeconstructDrawGlyphs::Yes)
+    : DisplayList::Recorder({ }, initialClip, initialCTM, DrawGlyphsMode::DeconstructUsingDrawGlyphsCommands)
     , m_destinationBufferIdentifier(imageBuffer.renderingResourceIdentifier())
     , m_imageBuffer(imageBuffer)
     , m_renderingBackend(renderingBackend)
@@ -185,6 +185,11 @@
     send(Messages::RemoteDisplayListRecorder::DrawGlyphs(DisplayList::DrawGlyphs { font, glyphs, advances, count, localAnchor, mode }));
 }
 
+void RemoteDisplayListRecorderProxy::recordDrawDecomposedGlyphs(const Font& font, const DecomposedGlyphs& decomposedGlyphs)
+{
+    send(Messages::RemoteDisplayListRecorder::DrawDecomposedGlyphs(font.renderingResourceIdentifier(), decomposedGlyphs.renderingResourceIdentifier(), decomposedGlyphs.bounds()));
+}
+
 void RemoteDisplayListRecorderProxy::recordDrawImageBuffer(ImageBuffer& imageBuffer, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
 {
     send(Messages::RemoteDisplayListRecorder::DrawImageBuffer(imageBuffer.renderingResourceIdentifier(), destRect, srcRect, options));
@@ -434,6 +439,17 @@
     return true;
 }
 
+bool RemoteDisplayListRecorderProxy::recordResourceUse(DecomposedGlyphs& decomposedGlyphs)
+{
+    if (UNLIKELY(!m_renderingBackend)) {
+        ASSERT_NOT_REACHED();
+        return false;
+    }
+
+    m_renderingBackend->remoteResourceCacheProxy().recordDecomposedGlyphsUse(decomposedGlyphs);
+    return true;
+}
+
 void RemoteDisplayListRecorderProxy::flushContext(GraphicsContextFlushIdentifier identifier)
 {
     send(Messages::RemoteDisplayListRecorder::FlushContext(identifier));
diff --git a/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.h b/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.h
index de65de6..5d34167 100644
--- a/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.h
+++ b/Source/WebKit/WebProcess/GPU/graphics/RemoteDisplayListRecorderProxy.h
@@ -84,6 +84,7 @@
     void recordClipPath(const WebCore::Path&, WebCore::WindRule) final;
     void recordDrawFilteredImageBuffer(WebCore::ImageBuffer*, const WebCore::FloatRect& sourceImageRect, WebCore::Filter&) final;
     void recordDrawGlyphs(const WebCore::Font&, const WebCore::GlyphBufferGlyph*, const WebCore::GlyphBufferAdvance*, unsigned count, const WebCore::FloatPoint& localAnchor, WebCore::FontSmoothingMode) final;
+    void recordDrawDecomposedGlyphs(const WebCore::Font&, const WebCore::DecomposedGlyphs&) final;
     void recordDrawImageBuffer(WebCore::ImageBuffer&, const WebCore::FloatRect& destRect, const WebCore::FloatRect& srcRect, const WebCore::ImagePaintingOptions&) final;
     void recordDrawNativeImage(WebCore::RenderingResourceIdentifier imageIdentifier, const WebCore::FloatSize& imageSize, const WebCore::FloatRect& destRect, const WebCore::FloatRect& srcRect, const WebCore::ImagePaintingOptions&) final;
     void recordDrawSystemImage(WebCore::SystemImage&, const WebCore::FloatRect&);
@@ -133,6 +134,7 @@
     bool recordResourceUse(WebCore::ImageBuffer&) final;
     bool recordResourceUse(const WebCore::SourceImage&) final;
     bool recordResourceUse(WebCore::Font&) final;
+    bool recordResourceUse(WebCore::DecomposedGlyphs&) final;
 
     RefPtr<WebCore::ImageBuffer> createImageBuffer(const WebCore::FloatSize&, float resolutionScale, const WebCore::DestinationColorSpace&, std::optional<WebCore::RenderingMode>, std::optional<WebCore::RenderingMethod>) const final;
     RefPtr<WebCore::ImageBuffer> createAlignedImageBuffer(const WebCore::FloatSize&, const WebCore::DestinationColorSpace&, std::optional<WebCore::RenderingMethod>) const final;
diff --git a/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.cpp b/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.cpp
index 187d84a..b7432e0 100644
--- a/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.cpp
+++ b/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.cpp
@@ -261,6 +261,11 @@
     sendToStream(Messages::RemoteRenderingBackend::CacheFont(WTFMove(font)));
 }
 
+void RemoteRenderingBackendProxy::cacheDecomposedGlyphs(Ref<DecomposedGlyphs>&& decomposedGlyphs)
+{
+    sendToStream(Messages::RemoteRenderingBackend::CacheDecomposedGlyphs(WTFMove(decomposedGlyphs)));
+}
+
 void RemoteRenderingBackendProxy::deleteAllFonts()
 {
     sendToStream(Messages::RemoteRenderingBackend::DeleteAllFonts());
diff --git a/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.h b/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.h
index 8ce18ae..02d15b9 100644
--- a/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.h
+++ b/Source/WebKit/WebProcess/GPU/graphics/RemoteRenderingBackendProxy.h
@@ -93,6 +93,7 @@
     RefPtr<WebCore::Image> getFilteredImage(WebCore::RenderingResourceIdentifier, WebCore::Filter&);
     void cacheNativeImage(const ShareableBitmap::Handle&, WebCore::RenderingResourceIdentifier);
     void cacheFont(Ref<WebCore::Font>&&);
+    void cacheDecomposedGlyphs(Ref<WebCore::DecomposedGlyphs>&&);
     void deleteAllFonts();
     void releaseRemoteResource(WebCore::RenderingResourceIdentifier);
     void markSurfacesVolatile(Vector<WebCore::RenderingResourceIdentifier>&&, CompletionHandler<void(bool madeAllVolatile)>&&);
diff --git a/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.cpp b/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.cpp
index e915c29..aaf07d1 100644
--- a/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.cpp
+++ b/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.cpp
@@ -43,6 +43,7 @@
 {
     clearNativeImageMap();
     clearImageBufferBackends();
+    clearDecomposedGlyphsMap();
 }
 
 void RemoteResourceCacheProxy::cacheImageBuffer(WebCore::ImageBuffer& imageBuffer)
@@ -132,6 +133,14 @@
     }
 }
 
+void RemoteResourceCacheProxy::recordDecomposedGlyphsUse(DecomposedGlyphs& decomposedGlyphs)
+{
+    if (m_decomposedGlyphs.add(decomposedGlyphs.renderingResourceIdentifier(), decomposedGlyphs).isNewEntry) {
+        decomposedGlyphs.addObserver(*this);
+        m_remoteRenderingBackendProxy.cacheDecomposedGlyphs(decomposedGlyphs);
+    }
+}
+
 void RemoteResourceCacheProxy::releaseNativeImage(RenderingResourceIdentifier renderingResourceIdentifier)
 {
     auto iterator = m_nativeImages.find(renderingResourceIdentifier);
@@ -155,6 +164,13 @@
     m_numberOfFontsUsedInCurrentRenderingUpdate = 0;
 }
 
+void RemoteResourceCacheProxy::releaseDecomposedGlyphs(RenderingResourceIdentifier renderingResourceIdentifier)
+{
+    bool removed = m_decomposedGlyphs.remove(renderingResourceIdentifier);
+    RELEASE_ASSERT(removed);
+    m_remoteRenderingBackendProxy.releaseRemoteResource(renderingResourceIdentifier);
+}
+
 void RemoteResourceCacheProxy::releaseAllRemoteFonts()
 {
     for (auto& fontState : m_fonts)
@@ -178,6 +194,11 @@
     }
 }
 
+void RemoteResourceCacheProxy::clearDecomposedGlyphsMap()
+{
+    m_decomposedGlyphs.clear();
+}
+
 void RemoteResourceCacheProxy::finalizeRenderingUpdateForFonts()
 {
     static constexpr unsigned minimumRenderingUpdateCountToKeepFontAlive = 4;
@@ -212,6 +233,7 @@
     clearNativeImageMap();
     clearFontMap();
     clearImageBufferBackends();
+    clearDecomposedGlyphsMap();
 
     for (auto& imageBuffer : m_imageBuffers.values()) {
         if (!imageBuffer)
diff --git a/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.h b/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.h
index af2d6d2..6da6ee8 100644
--- a/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.h
+++ b/Source/WebKit/WebProcess/GPU/graphics/RemoteResourceCacheProxy.h
@@ -28,6 +28,7 @@
 #if ENABLE(GPU_PROCESS)
 
 #include "RenderingUpdateID.h"
+#include <WebCore/DecomposedGlyphs.h>
 #include <WebCore/NativeImage.h>
 #include <WebCore/RenderingResourceIdentifier.h>
 #include <wtf/HashMap.h>
@@ -41,7 +42,7 @@
 
 class RemoteRenderingBackendProxy;
 
-class RemoteResourceCacheProxy : public WebCore::NativeImage::Observer {
+class RemoteResourceCacheProxy : public WebCore::NativeImage::Observer, public WebCore::DecomposedGlyphs::Observer {
 public:
     RemoteResourceCacheProxy(RemoteRenderingBackendProxy&);
     ~RemoteResourceCacheProxy();
@@ -53,6 +54,7 @@
     void recordNativeImageUse(WebCore::NativeImage&);
     void recordFontUse(WebCore::Font&);
     void recordImageBufferUse(WebCore::ImageBuffer&);
+    void recordDecomposedGlyphsUse(WebCore::DecomposedGlyphs&);
 
     void finalizeRenderingUpdate();
 
@@ -66,9 +68,12 @@
     using ImageBufferHashMap = HashMap<WebCore::RenderingResourceIdentifier, WeakPtr<WebCore::ImageBuffer>>;
     using NativeImageHashMap = HashMap<WebCore::RenderingResourceIdentifier, WeakPtr<WebCore::NativeImage>>;
     using FontHashMap = HashMap<WebCore::RenderingResourceIdentifier, RenderingUpdateID>;
+    using DecomposedGlyphsHashMap = HashMap<WebCore::RenderingResourceIdentifier, WeakPtr<WebCore::DecomposedGlyphs>>;
     
     void releaseNativeImage(WebCore::RenderingResourceIdentifier) override;
+    void releaseDecomposedGlyphs(WebCore::RenderingResourceIdentifier) override;
     void clearNativeImageMap();
+    void clearDecomposedGlyphsMap();
 
     void finalizeRenderingUpdateForFonts();
     void prepareForNextRenderingUpdate();
@@ -78,6 +83,7 @@
     ImageBufferHashMap m_imageBuffers;
     NativeImageHashMap m_nativeImages;
     FontHashMap m_fonts;
+    DecomposedGlyphsHashMap m_decomposedGlyphs;
 
     unsigned m_numberOfFontsUsedInCurrentRenderingUpdate { 0 };