Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: b48581fbde83d2d64cdb46b2d7b8720deaabd336
https://github.com/WebKit/WebKit/commit/b48581fbde83d2d64cdb46b2d7b8720deaabd336
Author: Chris Dumez <[email protected]>
Date: 2026-09-01 (Tue, 01 Sep 2026)
Changed paths:
M Source/WTF/wtf/URLParser.cpp
M Source/WebCore/page/Quirks.cpp
M Source/WebCore/platform/network/ResourceRequestBase.cpp
M Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm
M Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
M Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm
Log Message:
-----------
Avoid redundant URL parsing and decoding on the navigation path
https://bugs.webkit.org/show_bug.cgi?id=323082
rdar://185796614
Reviewed by Per Arne Vollan.
A single navigation re-parses, re-serializes and form-decodes its URL roughly a
dozen times between WebCore, WebKit and CFNetwork. Almost all of it is
redundant. This is invisible for ordinary URLs and pathological for long ones.
Hang reports show MobileSafari's main thread spending 2-3.6 seconds entirely
on-CPU decoding a single DecidePolicyForNavigationActionAsync message, and the
WebContent process spending the same interval producing it. Nothing is blocked;
the time goes into converting one navigation URL -- whose query string is
megabytes long -- back and forth between WTF::URL and NSURL/CFURL.
Quirks::needsConsistentQueryParameterFilteringQuirk (added in 306714@main) built
a case-folded copy of the entire URL and re-parsed it, purely to compare a host
and a registrable domain -- two O(URL length) passes plus two allocations, on
every navigation, since the outer setting defaults to true. Compare
case-insensitively against the original URL instead. Note that URLParser only
ASCII-lowercases hosts for special schemes; opaque hosts keep their case, so
these comparisons must be case-insensitive rather than assuming a lowercase
host. The remaining fold-case, which feeds a WebKitAdditions function whose
input contract we should not change, moves behind the internal setting that
gates it (default off), so it is not evaluated in shipping configurations.
WebPage::applyLinkDecorationFilteringWithResult consulted that quirk twice
before checking whether the URL even has a query. Hoist the hasQuery() early
return above it; both branches return the same value.
The same function also reported DidFilterLinkDecoration::Yes whenever filtering
merely ran, including when it removed nothing. That flag's sole consumer is
ResourceLoadStatisticsStore::logCrossSiteLoadWithLinkDecoration, which reads it
as "a known tracking parameter was removed" and relaxes the destination's
storage removal frequency from Short to Long when it is set. Reporting Yes for a
no-op filtering pass therefore weakened storage removal for cross-site
navigations from prevalent domains. Return No when nothing was removed. This
also stops PolicyChecker::checkNavigationPolicy (which gained this call in
307522@main) from calling setURL() with an unchanged URL on every navigation.
ResourceRequestBase::setURL cleared m_platformRequestUpdated unconditionally,
discarding the cached NSURLRequest even when setting an identical URL. Add the
same no-op guard the other setters in that file already use.
WebPage::platformCanHandleRequest materialized an NSURLRequest solely to ask
[NSURLConnection canHandleRequest:] a question that is answered by the scheme.
CFNetwork's built-in protocols always handle http(s)/file/data/about, and a
custom NSURLProtocol can only add handling via +canInitWithRequest:, never
remove it, so the answer for those schemes is true regardless of what is
registered. Return early for them.
That last change has a second effect worth calling out. nsURLRequest() is const
but caches into a mutable member, so calling it permanently attaches an
NSURLRequest to the request -- and encodingRequiresPlatformData() keys off
exactly that to choose between serializing a plain RequestData and the far more
expensive CoreIPCNSURLRequest path. Since platformCanHandleRequest was the only
caller of nsURLRequest() in the WebProcess, it was single-handedly forcing every
GET navigation onto the platform path. Verified with MiniBrowser against a local
http page with a long query: with the early return the navigation request
serializes 0 times via the platform path, without it 2 times.
Finally, URLParser::formURLDecode made four full allocating copies of its input
(replace '+', UTF-8 convert, percent-decode, UTF-16 convert) with no
content-dependent fast path, and removeQueryParameters calls it on the value of
every query parameter. Add a fast path for ASCII input containing no '%', which
decodes to itself. The ASCII restriction matters: the UTF-8 round trip is what
rejects unpaired surrogates.
* Source/WTF/wtf/URLParser.cpp:
(WTF::URLParser::formURLDecode):
* Source/WebCore/page/Quirks.cpp:
(WebCore::Quirks::needsConsistentQueryParameterFilteringQuirk):
* Source/WebCore/platform/network/ResourceRequestBase.cpp:
(WebCore::ResourceRequestBase::setURL):
* Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
(WebKit::WebPage::applyLinkDecorationFilteringWithResult):
* Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::platformCanHandleRequest):
* Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm:
(WebKit::WebPage::platformCanHandleRequest):
Canonical link: https://commits.webkit.org/320292@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications