Skip to content

Conversation

@calvaris
Copy link
Contributor

@calvaris calvaris commented Oct 1, 2025

92993a7

[WTF] Make CStringView handle only null termination related methods and add some helpers for spans to StringCommon
https://bugs.webkit.org/show_bug.cgi?id=299946

Reviewed by Darin Adler.

Improved several comparisons, like starts, ends, contains, find, both case and aware and not. Also some more useful
conversions and parsing. For this, some more tweaks and templating was needed as many string management methods consider
Latin1Character only and CStringView handles char8_t.

Fly-by: fix return value of isEmpty to be bool instead of size_t and renamed length into lengthInBytes.

GStreamer code using CStringView already was adapted to this.

Tests: Tools/TestWebKitAPI/Tests/WTF/CString.cpp
       Tools/TestWebKitAPI/Tests/WTF/CStringView.cpp
       Tools/TestWebKitAPI/Tests/WTF/StringBuilder.cpp
       Tools/TestWebKitAPI/Tests/WTF/StringCommon.cpp
* Source/WTF/wtf/StdLibExtras.h:
(WTF::find):
(WTF::contains):
(WTF::ByteCastTraits<T>::cast):
(WTF::ByteCastTraits<T::cast):
(WTF::byteCast):
* Source/WTF/wtf/text/ASCIILiteral.h:
(WTF::StringLiterals::operator_spanChar8):
* Source/WTF/wtf/text/CString.cpp:
(WTF::convertASCIICase):
(WTF::convertToASCIILowercase):
(WTF::convertToASCIIUppercase):
* Source/WTF/wtf/text/CString.h:
* Source/WTF/wtf/text/CStringView.cpp:
(WTF::CStringView::dump const):
(WTF::CStringView::toString const): Deleted.
* Source/WTF/wtf/text/CStringView.h:
(WTF::operator==):
* Source/WTF/wtf/text/ParsingUtilities.h:
(WTF::requires):
* Source/WTF/wtf/text/StringCommon.h:
(WTF::unsafeSpanChar8):
(WTF::equal):
(WTF::requires):
(WTF::equalIgnoringASCIICase):
(WTF::findIgnoringASCIICase):
(WTF::containsIgnoringASCIICase):
(WTF::find):
(WTF::contains):
(WTF::reverseFind):
(WTF::equalLettersIgnoringASCIICaseWithLength):
(WTF::startsWith):
(WTF::endsWith):
(WTF::endsWithLettersIgnoringASCIICaseCommon):
(WTF::endsWithLettersIgnoringASCIICase):
(WTF::startsWithLettersIgnoringASCIICaseCommon):
(WTF::startsWithLettersIgnoringASCIICase):
* Source/WTF/wtf/text/StringToIntegerConversion.h:
(WTF::parseInteger):
(WTF::parseIntegerAllowingTrailingJunk):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerMediaEndpoint.cpp:
(WebCore::GStreamerMediaEndpoint::requestPad):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerRtpReceiverBackend.cpp:
(WebCore::GStreamerRtpReceiverBackend::getParameters):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerRtpTransceiverBackend.cpp:
(WebCore::GStreamerRtpTransceiverBackend::setCodecPreferences):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerStatsCollector.cpp:
(WebCore::RTCStatsReport::Stats::Stats):
(WebCore::RTCStatsReport::RtpStreamStats::RtpStreamStats):
(WebCore::RTCStatsReport::CodecStats::CodecStats):
(WebCore::RTCStatsReport::RemoteInboundRtpStreamStats::RemoteInboundRtpStreamStats):
(WebCore::RTCStatsReport::RemoteOutboundRtpStreamStats::RemoteOutboundRtpStreamStats):
(WebCore::RTCStatsReport::InboundRtpStreamStats::InboundRtpStreamStats):
(WebCore::RTCStatsReport::OutboundRtpStreamStats::OutboundRtpStreamStats):
(WebCore::RTCStatsReport::TransportStats::TransportStats):
(WebCore::RTCStatsReport::IceCandidateStats::IceCandidateStats):
(WebCore::RTCStatsReport::IceCandidatePairStats::IceCandidatePairStats):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerWebRTCUtils.cpp:
(WebCore::toRTCEncodingParameters):
(WebCore::toRTCCodecParameters):
(WebCore::toRTCRtpSendParameters):
(WebCore::capsFromRtpCapabilities):
(WebCore::capsFromSDPMedia):
(WebCore::extractMidAndRidFromRTPBuffer):
* Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp:
(WebCore::doCapsHaveType):
* Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp:
(WebCore::GStreamerRegistryScanner::fillVideoRtpCapabilities):
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::handleMessage):
(WebCore::MediaPlayerPrivateGStreamer::loadNextLocation):
* Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
(transformInPlace):
* Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp:
(WebCore::AppendPipeline::parseDemuxerSrcPadCaps):
* Source/WebCore/platform/gstreamer/GStreamerElementHarness.cpp:
(WebCore::MermaidBuilder::describeCaps):
* Source/WebCore/platform/gstreamer/VideoEncoderPrivateGStreamer.cpp:
(annexBStreamFormatCapsFieldValue):
(videoEncoderSetEncoder):
(webkit_video_encoder_class_init):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerAudioRTPPacketizer.cpp:
(WebCore::GStreamerAudioRTPPacketizer::create):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerCaptureDeviceManager.cpp:
(WebCore::GStreamerCaptureDeviceManager::captureDeviceFromGstDevice):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerIncomingTrackProcessor.cpp:
(WebCore::GStreamerIncomingTrackProcessor::configure):
(WebCore::GStreamerIncomingTrackProcessor::incomingTrackProcessor):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerMockDeviceProvider.cpp:
(webkitGstMockDeviceProviderSwitchDefaultDevice):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerRTPPacketizer.cpp:
(WebCore::GStreamerRTPPacketizer::rtpStreamId const):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoCapturer.cpp:
(WebCore::GStreamerVideoCapturer::reconfigure):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoRTPPacketizer.cpp:
(WebCore::GStreamerVideoRTPPacketizer::create):
* Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingAudioSourceGStreamer.cpp:
(WebCore::RealtimeOutgoingAudioSourceGStreamer::setInitialParameters):
* Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingMediaSourceGStreamer.cpp:
(WebCore::RealtimeOutgoingMediaSourceGStreamer::setParameters):
* Tools/TestWebKitAPI/Tests/WTF/CString.cpp:
(TEST(WTF, CStringNullStringConstructor)):
(TEST(WTF, CStringEmptyEmptyConstructor)):
(TEST(WTF, CStringViewASCIICaseConversions)):
* Tools/TestWebKitAPI/Tests/WTF/CStringView.cpp:
(TestWebKitAPI::TEST(WTF, CStringViewSize)):
(TestWebKitAPI::TEST(WTF, CStringViewFrom)):
(TestWebKitAPI::TEST(WTF, CStringViewEquality)):
(TestWebKitAPI::TEST(WTF, CStringViewLength)): Deleted.
* Tools/TestWebKitAPI/Tests/WTF/StringBuilder.cpp:
(TestWebKitAPI::TEST(StringBuilderTest, Append)):
* Tools/TestWebKitAPI/Tests/WTF/StringCommon.cpp:
(TestWebKitAPI::TEST(WTF_StringCommon, Equal)):
(TestWebKitAPI::TEST(WTF_StringCommon, EqualIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, StartsWith)):
(TestWebKitAPI::TEST(WTF_StringCommon, EndsWith)):
(TestWebKitAPI::TEST(WTF_StringCommon, Find)):
(TestWebKitAPI::TEST(WTF_StringCommon, ReverseFind)):
(TestWebKitAPI::TEST(WTF_StringCommon, Contains)):
(TestWebKitAPI::TEST(WTF_StringCommon, StartsWithLettersIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, EndsWithLettersIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, FindIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, ContainsIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, CharactersAreAllASCII)):

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

db41f57

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows
❌ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 win
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 🧪 win-tests
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ❌ 🧪 api-mac ✅ 🧪 api-wpe
✅ 🧪 ios-wk2-wpt ✅ 🧪 mac-wk1 ✅ 🛠 wpe-cairo
✅ 🛠 🧪 jsc ✅ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🛠 gtk
✅ 🛠 🧪 jsc-arm64 ✅ 🛠 vision ✅ 🧪 mac-AS-debug-wk2 ✅ 🧪 gtk-wk2
✅ 🛠 vision-sim ✅ 🧪 mac-wk2-stress ✅ 🧪 api-gtk
✅ 🛠 🧪 merge ✅ 🧪 vision-wk2 ✅ 🧪 mac-intel-wk2 ✅ 🛠 playstation
✅ 🛠 tv ✅ 🛠 mac-safer-cpp ✅ 🛠 jsc-armv7
✅ 🛠 tv-sim ✅ 🧪 jsc-armv7-tests
✅ 🛠 watch
✅ 🛠 watch-sim

@calvaris calvaris self-assigned this Oct 1, 2025
@calvaris calvaris added the New Bugs Unclassified bugs are placed in this component until the correct component can be determined. label Oct 1, 2025
@cdumez cdumez requested a review from darinadler October 1, 2025 11:30

StringView CStringView::unsafeToASCIIStringView() const
{
ASSERT(charactersAreAllASCII(span8()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks dangerous. Why are these functions useful? It would help if we did some adoption in the same PR to understand how they'd be used and to make sure this is the best way.

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Oct 1, 2025
Copy link
Contributor

@geoffreygaren geoffreygaren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Chris. At first glance, this doesn’t seem like a good idea. Perhaps a use case would change my mind. I’m not sure.

A few other comments:

We use “as” rather than “to” when we want to say that something is already something else. “To” implies conversion.

If it is generally useful and usable to attest that a string is known to be ASCII, don’t we want that capability for String and CString too? Do we need a type that represents that attestation, so we can pass it around?


String CStringView::unsafeToASCIIStringWithoutCopying() const
{
return unsafeToASCIIStringView().toStringWithoutCopying();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createWithoutCopying is not lifetime safe. We should not expand usage, and we should probably work to remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if lifetime compiler marks could help here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there were a lifetime mark here, the compiler would signal an error. The returned String has unbounded lifetime, so it always escapes the lifetime bound of any view type.

@calvaris
Copy link
Contributor Author

calvaris commented Oct 2, 2025

I agree with Chris. At first glance, this doesn’t seem like a good idea. Perhaps a use case would change my mind. I’m not sure.

Some use cases. Sometimes we read environment variables and wrap them CStringView or we invoke gstStructureGetString(structure, "needed-field"_s) that returns a CStringView.

  1. Read env var and compare it to "true" or "1". operator== does the job nice and easy.
  2. Read and env var but you want to compare ignoring case to enable "True" or "True". WTF::equalLettersIgnoringASCIICase(quirks.unsafeAsASCIIStringView(), "true"_s) would be handy.
  3. You read a codec name from a string that can be "avc", "AVC" or whatever combination and you want to keep that value lowercase to ease further comparisons: auto codecName = gstStructureGetString(structure, "needed-field"_s).unsafeAsStringView().convertToASCIIUppercase(). I could avoid converting to a String first and save a check and memcopy because I know it is going to be ASCII.
  4. You have a string and you want to parse it: auto value = parseIntegerAllowingTrailingJunk<int64_t>(minPTime.unsafeToASCIIStringView())
  5. You need to check if certain string begins with something: fieldName.unsafeToASCIIStringView().startsWith("extmap-"_s). Or ends. Or contains.
  6. You are using makeString(cStringView.unsafeToASCIIStringView(), '-', nextId++).
  7. You want to split a CStringView in pieces separated by , .

Examples of this are at #51259 . That did not land yet because @philn , with good criteria, thought that we could be regressing in perf even when increasing in code safety.

A few other comments:

We use “as” rather than “to” when we want to say that something is already something else. “To” implies conversion.

Sure thing.

If it is generally useful and usable to attest that a string is known to be ASCII, don’t we want that capability for String and CString too? Do we need a type that represents that attestation, so we can pass it around?

Well, I don't know, I just take baby steps now. In the first iteration of the CStringView patch I provided all these operations by adding CStringView to the string operations but considering how templated that is it was providing too much, including interactions with other string management classes we wanted to avoid. Prove of that was that the first iteration had to be reverted due to a build error for a code that was not being used and created a compiler error.

@geoffreygaren
Copy link
Contributor

  1. Read env var and compare it to "true" or "1". operator== does the job nice and easy.
  2. Read and env var but you want to compare ignoring case to enable "True" or "True". WTF::equalLettersIgnoringASCIICase(quirks.unsafeAsASCIIStringView(), "true"_s) would be handy.

Is getenv() guaranteed to return ASCII? I don’t think it is.

I think this is how we ended up with ASCIILiteral as our optimization for ASCII: it is pretty rare for an API that returns an arbitrary string to guarantee that the string will be ASCII. But when we have a literal, we can sometimes guarantee it.

@calvaris
Copy link
Contributor Author

calvaris commented Oct 3, 2025

  1. Read env var and compare it to "true" or "1". operator== does the job nice and easy.
  2. Read and env var but you want to compare ignoring case to enable "True" or "True". WTF::equalLettersIgnoringASCIICase(quirks.unsafeAsASCIIStringView(), "true"_s) would be handy.

Is getenv() guaranteed to return ASCII? I don’t think it is.

The issue here is not if environment variables can be or not UTF8, which they can. The issue is the use to do with them. If you're planning to parse an integer out of them then it does not matter because we are already checking if the characters are ASCII digits and if not, it will fail. Same for comparisons, unless I am missing anything, "watermelon🍉" begins with ASCII "watermelon". I am not saying you don't have to convert an env variable whose content you're going to use as is.

@geoffreygaren
Copy link
Contributor

geoffreygaren commented Oct 3, 2025

  1. Read env var and compare it to "true" or "1". operator== does the job nice and easy.
  2. Read and env var but you want to compare ignoring case to enable "True" or "True". WTF::equalLettersIgnoringASCIICase(quirks.unsafeAsASCIIStringView(), "true"_s) would be handy.

Is getenv() guaranteed to return ASCII? I don’t think it is.

The issue here is not if environment variables can be or not UTF8, which they can. The issue is the use to do with them. If you're planning to parse an integer out of them then it does not matter because we are already checking if the characters are ASCII digits and if not, it will fail.

To compare an environment variable to “true”, I’d recommend a helper function whose signature is bool operator==(CStringView, ASCIILiteral);. That way, the fact that you can optimize comparison by doing it byte-by-byte rather than character-by-character is an implementation detail of comparison, and there is no need to have a type that attests that it is ASCII when it is not, or that it has reference-counted lifetime when it does not.

@calvaris calvaris removed the merging-blocked Applied to prevent a change from being merged label Oct 8, 2025
@calvaris calvaris changed the title [WTF] Add helpers to CStringView to deal with ASCII only cases [WTF] Add helpers to CStringView to handle some more comparisons and conversions Oct 8, 2025
@calvaris calvaris requested a review from philn as a code owner October 8, 2025 16:37
@calvaris
Copy link
Contributor Author

calvaris commented Oct 8, 2025

I think I managed to add the corresponding methods, their tests and include them in GStreamer port code that does not seem to be regressing in any way.

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Oct 8, 2025
@calvaris calvaris removed the merging-blocked Applied to prevent a change from being merged label Oct 9, 2025
{ "4d001f"_s, 0x4d001f },
};

for (auto& profileLevelId : profiles) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for (auto& [profileLevelId, spsAsInteger]: profiles)?

{ "4d001f"_s, 0x4d001f },
};

for (auto& profileLevelId : profiles) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for (auto& [profileLevelId, spsAsInteger]: profiles)?

@darinadler
Copy link
Member

I don't think why we should continue to build this class CStringView with so many member functions; for 8-bit character "views" we should be using std:span<const char> or the same for each other character types like Latin1Character. And make functions that work on those for all these operations. The only minimal stuff we should put in a class would be things that are different for null-terminated strings, which should be almost nothing. Continuing to build out this class seems like a poor direction for WTF. The reason StringView exists is the dual nature of either Latin1Character or char16_t and since that does not apply here we don't need to build up a class and require using it when we have a std:span already.

"640c1f"_s,
"42001f"_s,
"4d001f"_s,
static Vector<std::pair<ASCIILiteral, unsigned>> profiles = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be a vector.

Suggested change
static Vector<std::pair<ASCIILiteral, unsigned>> profiles = {
static constexpr std::array<std::pair<ASCIILiteral, unsigned>> profiles = {

Comment on lines 86 to 87
WTF_EXPORT_PRIVATE String convertToASCIILowercase() const;
WTF_EXPORT_PRIVATE String convertToASCIIUppercase() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want these functions to return a String rather than a CString or some other 8-bit string type? I don't think that's good. We don't need to make a hybrid 8/16-bit character string out of an 8-bit character string as a side effect of changing the capitalization of some characters. At least one of the callers just turns around and converts this back into a newly allocated ASCII string so it's allocating and destroying a String for no good reason.

bool isEmpty() const { return m_spanWithNullTerminator.size() <= 1; }
WTF_EXPORT_PRIVATE String toString() const;

WTF_EXPORT_PRIVATE bool containsOnlyASCII() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions should work on spans rather than adding them all to CStringView; I suggest non-member functions. We would like them to work on 8-bit strings that don't have null terminators, too. The only member functions we should add to CStringView are ones that depend on the null termination. We don't just want extra versions of everything you can do to an 8-bit string in this class and then another copy for non-null-terminated strings.

Copy link
Contributor Author

@calvaris calvaris Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I underestood correctly, do you want me to move all comparison functions to be non member functions? Answering your other comment here as well: making them operate on just spans can be very inconvenient when you have to deal with this kind of strings a lot. Whenever I have to compare or convert, I have to get the span, then operate, then maybe get another result...

I've spoke about this with my mates of the GTK/WPE ports and we agree this class is useful. If you don't find it useful, I am open to move it to the glib directory, but we do need it. Just say the word.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me try to be explicit. You’re saying that this is useful:

x.containsOnlyASCII()

But this is very inconvenient?

containsOnlyASCII(x.span())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are both really convenient!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really hope we don’t have dueling style battles with people lined up along “port” lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not referring to that example only, that's obvious. I wrote that after putting my child to sleep without having time to give it a proper thought about other cases. I'll write the code today and see how it goes. I hope you're right! (And it usually happens).

Another concern is that I, as a developer, usually go to the header of a class to know if I can do certain operation. If we move all this out of the class, it will be "undocumented". I guess a comment in the code regarding being able to create the span and perform the same operations would be interesting.

I really hope we don’t have dueling style battles with people lined up along “port” lines.

It would be a nice team building activity for the next WKCM 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I addressed all changes you suggested to my understanding. I don't think I could detach startsWith, endsWith, find, reverseFind and their ignore case counterparts because they do rely on sizes and I haven't seen any other helpers in WTF I can base them on. Please let me know if you find this ok or you would like me to perform any other changes.

@calvaris
Copy link
Contributor Author

calvaris commented Nov 6, 2025

I think I addressed everything. Please have another look. I suspect what could be left is just small things.

Comment on lines 1059 to 1060
template<typename T, typename U>
concept EqualityComparable = std::is_same_v<std::remove_const_t<T>, std::remove_const_t<U>> || (!std::is_same_v<std::remove_const_t<T>, char8_t> && !std::is_same_v<std::remove_const_t<U>, char8_t>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this isn't quite the right name for the concept because EqualityComparable sounds like it just answers whether two types support ==.

I tried to come up with one example of a better name: TriviallyComparableCodeUnits.

template<typename T, typename U>
concept EqualityComparable = std::is_same_v<std::remove_const_t<T>, std::remove_const_t<U>> || (!std::is_same_v<std::remove_const_t<T>, char8_t> && !std::is_same_v<std::remove_const_t<U>, char8_t>);

template<typename T> concept OneByteType = sizeof(T) == 1 && ((std::is_integral_v<T> && !std::same_as<T, bool>) || std::same_as<T, std::byte>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should format new code consistently with the multiple line style used just above that we discussed in WebKit-dev or Slack. I don't think the name OneByteType is ideal given we already have a concept named ByteType that is almost identical. The difference between the two seems to be related to const and not the number one.

Suggested change
template<typename T> concept OneByteType = sizeof(T) == 1 && ((std::is_integral_v<T> && !std::same_as<T, bool>) || std::same_as<T, std::byte>);
template<typename T>
concept OneByteType = sizeof(T) == 1 && ((std::is_integral_v<T> && !std::same_as<T, bool>) || std::same_as<T, std::byte>);

Comment on lines 180 to 185
std::span<char> characters;
auto result = CString::newUninitialized(input.size(), characters);
size_t i = 0;
for (auto character : input)
characters[i++] = byteCast<char>(type == ASCIICase::Lower ? toASCIILower(character) : toASCIIUpper(character));
return result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::span<char> characters;
auto result = CString::newUninitialized(input.size(), characters);
size_t i = 0;
for (auto character : input)
characters[i++] = byteCast<char>(type == ASCIICase::Lower ? toASCIILower(character) : toASCIIUpper(character));
return result;
std::span<char8_t> characters;
auto result = CString::newUninitialized(input.size(), characters);
size_t i = 0;
for (auto character : input)
characters[i++] = type == ASCIICase::Lower ? toASCIILower(character) : toASCIIUpper(character);
return byteCast<char>(result);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you suggest is not possible because of error: non-const lvalue reference to type 'span<char>' cannot bind to a value of unrelated type 'span<char8_t>'. I'll keep this as it was.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense; my suggestion was wrong.

Do we need the byteCast<char>? I’m pretty sure a char can be assigned to a char8_t without a cast.


// This is a class designed to contain a UTF8 string, untouched. Interactions with other string classes in WebKit should
// be handled with care or perform a string conversion through the String class, with the exception of ASCIILiteral
// be handled by using the span or by performing a string conversion through the String class, with the exception of ASCIILiteral
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I am still not sure why this comment is worded this way.

using CodeUnitMatchFunction = bool (*)(char16_t);

template<typename CharacterTypeA, typename CharacterTypeB> bool equalIgnoringASCIICase(std::span<const CharacterTypeA>, std::span<const CharacterTypeB>);
template<typename CharacterTypeA, typename CharacterTypeB> requires(EqualityComparable<CharacterTypeA, CharacterTypeB>) bool equalIgnoringASCIICase(std::span<const CharacterTypeA>, std::span<const CharacterTypeB>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
template<typename CharacterTypeA, typename CharacterTypeB> requires(EqualityComparable<CharacterTypeA, CharacterTypeB>) bool equalIgnoringASCIICase(std::span<const CharacterTypeA>, std::span<const CharacterTypeB>);
template<typename CharacterTypeA, typename CharacterTypeB>
requires(EqualityComparable<CharacterTypeA, CharacterTypeB>)
bool equalIgnoringASCIICase(std::span<const CharacterTypeA>, std::span<const CharacterTypeB>);

continue;

auto packetizer = getPacketizerForRid(rid.toString());
auto packetizer = getPacketizerForRid(String(rid.span()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to explicitly write String() here? Doesn't it compile without it?

CString string;
constexpr size_t zeroLength = 0;
ASSERT_TRUE(string.isNull());
ASSERT_TRUE(string.isEmpty());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be using EXPECT, not ASSERT. No idea why the existing code uses the wrong macro. The assert macros stop when the first one fails, which is not what we want in these tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely agree with you. However, I'd leave for some other patch because it would be a breakage of "commit atomicity".

CString stringFromEmptySpanWithNonNullPointer(unsafeMakeSpan(emptyString, 0));
ASSERT_FALSE(stringFromEmptySpanWithNonNullPointer.isNull());
ASSERT_TRUE(stringFromEmptySpanWithNonNullPointer.isEmpty());
ASSERT_EQ(stringFromEmptySpanWithNonNullPointer.length(), static_cast<size_t>(0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ASSERT_EQ(stringFromEmptySpanWithNonNullPointer.length(), static_cast<size_t>(0));
EXPECT_EQ(static_cast<size_t>(0), stringFromEmptySpanWithNonNullPointer.length());

CStringView string;
EXPECT_EQ(string.length(), static_cast<size_t>(0));
EXPECT_EQ(string.span8().size(), static_cast<size_t>(0));
EXPECT_EQ(string.lengthInBytes(), 0UL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not good for portability to rely on 0UL, which is unsigned long, being the same as size_t.

Suggested change
EXPECT_EQ(string.lengthInBytes(), 0UL);
EXPECT_EQ(0uz, string.lengthInBytes());

@calvaris
Copy link
Contributor Author

calvaris commented Nov 11, 2025

I think I managed to address your comments, which are most likely cosmetic and makes me hopeful that we are very close.

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Nov 11, 2025
@calvaris calvaris removed the merging-blocked Applied to prevent a change from being merged label Nov 12, 2025
Comment on lines +99 to +106
static ASCIILiteral annexBStreamFormatCapsFieldValue(CStringView name)
{
static HashMap<String, ASCIILiteral> map = {
{ "video/x-h264"_s, "byte-stream"_s },
{ "video/x-h265"_s, "byte-stream"_s },
{ "video/x-av1"_s, "annexb"_s }
};
return map.get(name.toStringWithoutCopying());
return map.get(name.span());
Copy link
Contributor Author

@calvaris calvaris Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one doubt regarding this piece of code. It's really a pity to allocate a String when we could be living with just ASCIILiterals. The problem is that, regardless of if you change the parameter to make it ASCIILiteral, at some point you have to convert from CStringView to ASCIILiteral and that can't be done right now. Hence you need to convert to String instead.

One option would be turning the hashmap to store a CStringView built from an ASCIILiteral, but CStringView can't do hashes and I think it's a good idea because lifetime of the stored string could be a problem if people decide to use it with less care. I don't think this is a good idea.

Another option would be having a method CStringView::asASCIILiteral that checks if it's really ASCII and ASSERTing otherwise. We might handle something with LIFETIME_BOUND and maybe mitigate any wrong use of the asASCIILiteral method. I guess for this we would have to create another method in ASCIILiteral to be able to accept a span because ASCIILiteral::unsafeFromUTF8 does a pass to calculate the length of the string, which we already know.

None of these options makes me completely happy, neither converting to String, but I guess we need to take the best balancing safety and efficiency.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not try to convert a CStringView into an ASCIILiteral because UTF-8 isn't ASCII and there is no guarantee that the string is a literal. But I agree that it's not good to convert to String just to use a HashMap. For this kind of table we want to use a SortedArrayMap and we should make that work with the appropriate span type; it would be relatively straightforward. I think it’s a good idea to come back and deal with that later.

Copy link
Member

@darinadler darinadler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems OK to land like this; there are some things I spotted that could be even better and so I wrote a few comments about them.

}

template<typename T, std::size_t TExtent, typename U, std::size_t UExtent>
template<typename T, std::size_t TExtent, typename U, std::size_t UExtent> requires(TriviallyComparableOneByteCodeUnits<T, U>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
template<typename T, std::size_t TExtent, typename U, std::size_t UExtent> requires(TriviallyComparableOneByteCodeUnits<T, U>)
template<typename T, std::size_t TExtent, typename U, std::size_t UExtent>
requires(TriviallyComparableOneByteCodeUnits<T, U>)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swear I went through the whole patch trying to make it like this but I guess my eyes were faster than my brain.

Comment on lines 44 to 45
// This is a class designed to contain a UTF8 string, untouched. It contains char8_t to avoid mixing incompatible
// encodings at compile time.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should mention the null terminator, which is the reason we don't just use span<const char8_t> for this.

The reason for this class is to let us pass around pointer to a null-terminated run of UTF-8 characters and have the type system document that it’s null-terminated.

unsigned hash() const;
bool isNull() const { return m_spanWithNullTerminator.empty(); }

// This method is designed to interface with external C functions handling UTF8 strings. Interactions with other
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We normally don't use the word method since C++ calls them functions or, more specifically, member functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still remember what I learned in object oriented programming at university, learning Java, many years ago 😅

if (a.isEmpty() || b.isEmpty())
return a.utf8() == b.characters();
return equalSpans(a.span8(), byteCast<char8_t>(b.span()));
return a.data() == b.characters();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not new to this pull request: Pretty sure this is wrong and will cause two empty strings to compare as non-equal if their data pointers don’t point to the same address. We should add tests and fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know now where I copied this from, but I am pretty sure it's somewhere else. I'll leave as it is in ASCIILiteral then, with just the equalSpans. I'll also add a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the test and, as usual, you were right!

#include <wtf/UnalignedAccess.h>
#include <wtf/text/ASCIIFastPath.h>
#include <wtf/text/ASCIILiteral.h>
#include <wtf/unicode/UTF8Conversion.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an include to a widely included header like this one will be a small unwanted compilation slowdown. I wonder if there's a way we can avoid it. Maybe we should put the UTF-8 functions into a separate header?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, they should be in this header file, but I guess I can move the implementation to the .cpp file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or not, because I had forgotten you can't have template implementations in cpp files unless you instantiate them explicitly, which is not what we want.

I don't think putting things in a separate header is a good idea because in the end we'd have functions whose overloads would be in different files. You'd need to include those files in the implementation and maybe add a comment for them to know that the implementation of that is in a different header file. IMHO, the trade off between a slight increase of build time vs the fuzz of having those functions in a different header is not worth it and I'd accept the increase.

Comment on lines +99 to +106
static ASCIILiteral annexBStreamFormatCapsFieldValue(CStringView name)
{
static HashMap<String, ASCIILiteral> map = {
{ "video/x-h264"_s, "byte-stream"_s },
{ "video/x-h265"_s, "byte-stream"_s },
{ "video/x-av1"_s, "annexb"_s }
};
return map.get(name.toStringWithoutCopying());
return map.get(name.span());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not try to convert a CStringView into an ASCIILiteral because UTF-8 isn't ASCII and there is no guarantee that the string is a literal. But I agree that it's not good to convert to String just to use a HashMap. For this kind of table we want to use a SortedArrayMap and we should make that work with the appropriate span type; it would be relatively straightforward. I think it’s a good idea to come back and deal with that later.

Comment on lines 180 to 185
std::span<char> characters;
auto result = CString::newUninitialized(input.size(), characters);
size_t i = 0;
for (auto character : input)
characters[i++] = byteCast<char>(type == ASCIICase::Lower ? toASCIILower(character) : toASCIIUpper(character));
return result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense; my suggestion was wrong.

Do we need the byteCast<char>? I’m pretty sure a char can be assigned to a char8_t without a cast.

size_t length() const { return m_spanWithNullTerminator.size() > 0 ? m_spanWithNullTerminator.size() - 1 : 0; }
std::span<const char8_t> span8() const LIFETIME_BOUND { return m_spanWithNullTerminator.first(length()); }
// strings should be handled by using the span.
const char* data() const LIFETIME_BOUND { return reinterpret_cast<const char*>(m_spanWithNullTerminator.data()); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel so dumb asking you to rename this to data(). I think it would be good to name it data() if it returned const char8_t*, but the fact that it returns just const char* makes me realize there was a reason to have it named utf8() before. I don’t know what to say; I’m really sorry for steering you wrong, and not sure what the best name for this function is.

Copy link
Contributor Author

@calvaris calvaris Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't worry. It's a big patch, it's difficult to keep track of things. Thanks a lot for reviewing. I'll rename it back to utf8().

auto components = msIdAttribute.toString().split(' ');
auto components = String(msIdAttribute.span()).split(' ');
if (components.size() == 2)
m_sdpMsIdAndTrackId = { components[0], components[1] };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be done more efficiently if we write WTFMove(components[0]), WTFMove(components[1]).

@calvaris calvaris added the safe-merge-queue Applied to automatically send a pull-request to merge-queue after passing EWS checks label Nov 13, 2025
@calvaris
Copy link
Contributor Author

I think I addressed everything in the best way I could, safe-merge-queue tag set!

@darinadler darinadler removed the New Bugs Unclassified bugs are placed in this component until the correct component can be determined. label Nov 13, 2025
@webkit-ews-buildbot
Copy link
Collaborator

Failed api-mac checks. Please resolve failures and re-apply safe-merge-queue label.

Rejecting #51619 from merge queue.

@webkit-ews-buildbot webkit-ews-buildbot added merging-blocked Applied to prevent a change from being merged and removed safe-merge-queue Applied to automatically send a pull-request to merge-queue after passing EWS checks labels Nov 13, 2025
@webkit-ews-buildbot
Copy link
Collaborator

Safe-Merge-Queue: Build #75007.

@calvaris
Copy link
Contributor Author

I run the api tests failures on mac and they seem to pass with and without patch. Landing...

@calvaris calvaris added merge-queue Applied to send a pull request to merge-queue and removed merging-blocked Applied to prevent a change from being merged labels Nov 13, 2025
…nd add some helpers for spans to StringCommon

https://bugs.webkit.org/show_bug.cgi?id=299946

Reviewed by Darin Adler.

Improved several comparisons, like starts, ends, contains, find, both case and aware and not. Also some more useful
conversions and parsing. For this, some more tweaks and templating was needed as many string management methods consider
Latin1Character only and CStringView handles char8_t.

Fly-by: fix return value of isEmpty to be bool instead of size_t and renamed length into lengthInBytes.

GStreamer code using CStringView already was adapted to this.

Tests: Tools/TestWebKitAPI/Tests/WTF/CString.cpp
       Tools/TestWebKitAPI/Tests/WTF/CStringView.cpp
       Tools/TestWebKitAPI/Tests/WTF/StringBuilder.cpp
       Tools/TestWebKitAPI/Tests/WTF/StringCommon.cpp
* Source/WTF/wtf/StdLibExtras.h:
(WTF::find):
(WTF::contains):
(WTF::ByteCastTraits<T>::cast):
(WTF::ByteCastTraits<T::cast):
(WTF::byteCast):
* Source/WTF/wtf/text/ASCIILiteral.h:
(WTF::StringLiterals::operator_spanChar8):
* Source/WTF/wtf/text/CString.cpp:
(WTF::convertASCIICase):
(WTF::convertToASCIILowercase):
(WTF::convertToASCIIUppercase):
* Source/WTF/wtf/text/CString.h:
* Source/WTF/wtf/text/CStringView.cpp:
(WTF::CStringView::dump const):
(WTF::CStringView::toString const): Deleted.
* Source/WTF/wtf/text/CStringView.h:
(WTF::operator==):
* Source/WTF/wtf/text/ParsingUtilities.h:
(WTF::requires):
* Source/WTF/wtf/text/StringCommon.h:
(WTF::unsafeSpanChar8):
(WTF::equal):
(WTF::requires):
(WTF::equalIgnoringASCIICase):
(WTF::findIgnoringASCIICase):
(WTF::containsIgnoringASCIICase):
(WTF::find):
(WTF::contains):
(WTF::reverseFind):
(WTF::equalLettersIgnoringASCIICaseWithLength):
(WTF::startsWith):
(WTF::endsWith):
(WTF::endsWithLettersIgnoringASCIICaseCommon):
(WTF::endsWithLettersIgnoringASCIICase):
(WTF::startsWithLettersIgnoringASCIICaseCommon):
(WTF::startsWithLettersIgnoringASCIICase):
* Source/WTF/wtf/text/StringToIntegerConversion.h:
(WTF::parseInteger):
(WTF::parseIntegerAllowingTrailingJunk):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerMediaEndpoint.cpp:
(WebCore::GStreamerMediaEndpoint::requestPad):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerRtpReceiverBackend.cpp:
(WebCore::GStreamerRtpReceiverBackend::getParameters):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerRtpTransceiverBackend.cpp:
(WebCore::GStreamerRtpTransceiverBackend::setCodecPreferences):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerStatsCollector.cpp:
(WebCore::RTCStatsReport::Stats::Stats):
(WebCore::RTCStatsReport::RtpStreamStats::RtpStreamStats):
(WebCore::RTCStatsReport::CodecStats::CodecStats):
(WebCore::RTCStatsReport::RemoteInboundRtpStreamStats::RemoteInboundRtpStreamStats):
(WebCore::RTCStatsReport::RemoteOutboundRtpStreamStats::RemoteOutboundRtpStreamStats):
(WebCore::RTCStatsReport::InboundRtpStreamStats::InboundRtpStreamStats):
(WebCore::RTCStatsReport::OutboundRtpStreamStats::OutboundRtpStreamStats):
(WebCore::RTCStatsReport::TransportStats::TransportStats):
(WebCore::RTCStatsReport::IceCandidateStats::IceCandidateStats):
(WebCore::RTCStatsReport::IceCandidatePairStats::IceCandidatePairStats):
* Source/WebCore/Modules/mediastream/gstreamer/GStreamerWebRTCUtils.cpp:
(WebCore::toRTCEncodingParameters):
(WebCore::toRTCCodecParameters):
(WebCore::toRTCRtpSendParameters):
(WebCore::capsFromRtpCapabilities):
(WebCore::capsFromSDPMedia):
(WebCore::extractMidAndRidFromRTPBuffer):
* Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp:
(WebCore::doCapsHaveType):
* Source/WebCore/platform/graphics/gstreamer/GStreamerRegistryScanner.cpp:
(WebCore::GStreamerRegistryScanner::fillVideoRtpCapabilities):
* Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::handleMessage):
(WebCore::MediaPlayerPrivateGStreamer::loadNextLocation):
* Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
(transformInPlace):
* Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp:
(WebCore::AppendPipeline::parseDemuxerSrcPadCaps):
* Source/WebCore/platform/gstreamer/GStreamerElementHarness.cpp:
(WebCore::MermaidBuilder::describeCaps):
* Source/WebCore/platform/gstreamer/VideoEncoderPrivateGStreamer.cpp:
(annexBStreamFormatCapsFieldValue):
(videoEncoderSetEncoder):
(webkit_video_encoder_class_init):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerAudioRTPPacketizer.cpp:
(WebCore::GStreamerAudioRTPPacketizer::create):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerCaptureDeviceManager.cpp:
(WebCore::GStreamerCaptureDeviceManager::captureDeviceFromGstDevice):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerIncomingTrackProcessor.cpp:
(WebCore::GStreamerIncomingTrackProcessor::configure):
(WebCore::GStreamerIncomingTrackProcessor::incomingTrackProcessor):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerMockDeviceProvider.cpp:
(webkitGstMockDeviceProviderSwitchDefaultDevice):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerRTPPacketizer.cpp:
(WebCore::GStreamerRTPPacketizer::rtpStreamId const):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoCapturer.cpp:
(WebCore::GStreamerVideoCapturer::reconfigure):
* Source/WebCore/platform/mediastream/gstreamer/GStreamerVideoRTPPacketizer.cpp:
(WebCore::GStreamerVideoRTPPacketizer::create):
* Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingAudioSourceGStreamer.cpp:
(WebCore::RealtimeOutgoingAudioSourceGStreamer::setInitialParameters):
* Source/WebCore/platform/mediastream/gstreamer/RealtimeOutgoingMediaSourceGStreamer.cpp:
(WebCore::RealtimeOutgoingMediaSourceGStreamer::setParameters):
* Tools/TestWebKitAPI/Tests/WTF/CString.cpp:
(TEST(WTF, CStringNullStringConstructor)):
(TEST(WTF, CStringEmptyEmptyConstructor)):
(TEST(WTF, CStringViewASCIICaseConversions)):
* Tools/TestWebKitAPI/Tests/WTF/CStringView.cpp:
(TestWebKitAPI::TEST(WTF, CStringViewSize)):
(TestWebKitAPI::TEST(WTF, CStringViewFrom)):
(TestWebKitAPI::TEST(WTF, CStringViewEquality)):
(TestWebKitAPI::TEST(WTF, CStringViewLength)): Deleted.
* Tools/TestWebKitAPI/Tests/WTF/StringBuilder.cpp:
(TestWebKitAPI::TEST(StringBuilderTest, Append)):
* Tools/TestWebKitAPI/Tests/WTF/StringCommon.cpp:
(TestWebKitAPI::TEST(WTF_StringCommon, Equal)):
(TestWebKitAPI::TEST(WTF_StringCommon, EqualIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, StartsWith)):
(TestWebKitAPI::TEST(WTF_StringCommon, EndsWith)):
(TestWebKitAPI::TEST(WTF_StringCommon, Find)):
(TestWebKitAPI::TEST(WTF_StringCommon, ReverseFind)):
(TestWebKitAPI::TEST(WTF_StringCommon, Contains)):
(TestWebKitAPI::TEST(WTF_StringCommon, StartsWithLettersIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, EndsWithLettersIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, FindIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, ContainsIgnoringASCIICase)):
(TestWebKitAPI::TEST(WTF_StringCommon, CharactersAreAllASCII)):

Canonical link: https://commits.webkit.org/303011@main
@webkit-commit-queue
Copy link
Collaborator

Committed 303011@main (92993a7): https://commits.webkit.org/303011@main

Reviewed commits have been landed. Closing PR #51619 and removing active labels.

@webkit-commit-queue webkit-commit-queue merged commit 92993a7 into WebKit:main Nov 13, 2025
@webkit-commit-queue webkit-commit-queue removed the merge-queue Applied to send a pull request to merge-queue label Nov 13, 2025
@calvaris calvaris deleted the eng/299946 branch November 14, 2025 05:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants