← All reports

Fetch dictionaries declared by rel=compression-dictionary and Link headers

Component: WebCore loader | 6248fa7

Source/WebCore/dom/Document.cpp

+void Document::queueCompressionDictionaryLoad(Function<void()>&& load)
+{
+ if (!m_loadEventFinished) {
+ m_pendingCompressionDictionaryLoads.append(WTF::move(load));
+ return;
+ }
+ eventLoop().queueTask(TaskSource::Networking, WTF::move(load));
+}
+
+void Document::flushPendingCompressionDictionaryLoads()
+{
+ ASSERT(m_loadEventFinished);
+ if (!settings().compressionDictionaryEnabled())
+ return;
+ if (RefPtr documentLoader = loader()) {
+ auto linkHeader = documentLoader->response().httpHeaderField(HTTPHeaderName::Link);
+ if (!linkHeader.isEmpty()) {
+ m_pendingCompressionDictionaryLoads.append([document = Ref { *this }, linkHeader = WTF::move(linkHeader)] {
+ LinkLoader::loadCompressionDictionariesFromHeader(linkHeader, document->url(), document);
+ });
+ }
+ }
+ for (auto& load : std::exchange(m_pendingCompressionDictionaryLoads, { }))
+ eventLoop().queueTask(TaskSource::Networking, WTF::move(load));
+}

Source/WebCore/loader/LinkLoader.cpp

+void LinkLoader::loadCompressionDictionaryLink(const LinkLoadParameters& params, Document& document)
+{
+ RefPtr client = m_client.get();
+ if (!client || !client->shouldLoadLink())
+ return;
+ document.queueCompressionDictionaryLoad([protectedThis = Ref { *this }, params, weakDocument = WeakPtr { document }] {
+ RefPtr document = weakDocument.get();
+ if (!document)
+ return;
+ auto resourceClient = loadCompressionDictionaryIfNeeded(params, *document, protectedThis.ptr());
+ ...
+ });
+}

Compression Dictionary Transport lets a page register a "dictionary" resource that later HTTP responses can be compressed against, reducing transfer size. This commit implements fetching those dictionaries when declared via <link rel=compression-dictionary> or a Link header, adding a new compression-dictionary fetch destination, CSP connect-src gating, and deferred queuing in Document until the load event fires.

The fetch is always a CORS request gated by connect-src. Dictionaries from <link> elements are deliberately queued until after the window load event so they do not compete with critical-path resources, while Link-header-declared dictionaries on subresources are explicitly not registered. The new CompressionDictionary value on FetchOptions::Destination is propagated through the serialization lists so the destination crosses the WebContent↔NetworkProcess IPC boundary, and NetworkLoadChecker/CachedResourceLoader CSP logic is extended to recognize it. Gated behind a new CompressionDictionaryEnabled preference, testable and off by default.

HTMLLinkElement::process / Link header on document response
        │  rel=compression-dictionary
        ▼
  LinkLoader::loadCompressionDictionaryLink
        ▼
  Document::queueCompressionDictionaryLoad ──► (held until load event)
        ▼ flushPendingCompressionDictionaryLoads
  LinkLoader::loadCompressionDictionaryIfNeeded
        ▼
  CachedResourceLoader::allowedByContentSecurityPolicy (connect-src)
        ▼  IPC (FetchOptions with new CompressionDictionary destination)
  NetworkProcess: NetworkLoadChecker::isAllowedByContentSecurityPolicy
        ▼
  CORS fetch of dictionary resource (nothing done with body yet)

This lands the fetch/loader plumbing for a new web-exposed feature before the decompression logic exists, expanding the CSP-gated loader surface reachable from page content ahead of the security-sensitive decode path landing later.

The forward-facing pattern is a load deferred past the point where its originating policy context was captured, so the CSP check and the fetch happen at different times against possibly different state. Narrow: the queue here holds closures until the load event fires — the match tell is a deferred load capturing a WeakPtr<Document> where the policy decision (allowedByContentSecurityPolicy) runs inside the deferred body rather than at queue time, since a document whose CSP changed or which detached in between is exactly the divergence case. Wider: audit WebCore's other load-event-deferred or event-loop-queued loader paths — preload scanning, <link rel=prefetch>, lazy-loaded images, and speculative preconnects — for the same capture-then-check-later ordering, plus their cancellation paths: LinkLoader::~LinkLoader/cancelLoad must handle a crossorigin attribute change that cancels and re-issues an in-flight request without freeing the client the in-flight load still points at. Widest: adding an enum value to a type that crosses an IPC boundary and feeds several switch statements is its own class — trace FetchOptions::Destination's new CompressionDictionary value through every consumer in NetworkLoadChecker, CachedResourceLoader, and the serialization lists, looking for a default: that silently permits rather than rejects, which is the CSP-gating gap this shape produces.